-
Notifications
You must be signed in to change notification settings - Fork 903
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(menusurface): Add menusurface class (forked from MWC/MDC).
PiperOrigin-RevId: 461889186
- Loading branch information
1 parent
e64cdbe
commit 5f51f26
Showing
9 changed files
with
1,402 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/** | ||
* @license | ||
* Copyright 2022 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
export const deepActiveElementPath = (doc = window.document): Element[] => { | ||
let activeElement = doc.activeElement; | ||
const path: Element[] = []; | ||
|
||
if (!activeElement) { | ||
return path; | ||
} | ||
|
||
while (activeElement) { | ||
path.push(activeElement); | ||
if (activeElement.shadowRoot) { | ||
activeElement = activeElement.shadowRoot.activeElement; | ||
} else { | ||
break; | ||
} | ||
} | ||
|
||
return path; | ||
}; | ||
|
||
export const doesElementContainFocus = (element: HTMLElement): boolean => { | ||
const activePath = deepActiveElementPath(); | ||
|
||
if (!activePath.length) { | ||
return false; | ||
} | ||
|
||
const deepActiveElement = activePath[activePath.length - 1]; | ||
const focusEv = | ||
new Event('check-if-focused', {bubbles: true, composed: true}); | ||
let composedPath: EventTarget[] = []; | ||
const listener = (ev: Event) => { | ||
composedPath = ev.composedPath(); | ||
}; | ||
|
||
document.body.addEventListener('check-if-focused', listener); | ||
deepActiveElement.dispatchEvent(focusEv); | ||
document.body.removeEventListener('check-if-focused', listener); | ||
|
||
return composedPath.indexOf(element) !== -1; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* @license | ||
* Copyright 2022 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
// stylelint-disable selector-class-pattern -- | ||
// Selector '.md3-*' should only be used in this project. | ||
|
||
$fade-in-duration: 0.03s !default; | ||
$fade-out-duration: 0.075s !default; | ||
$scale-duration: 0.12s !default; | ||
$min-distance-from-edge: 32px !default; | ||
$z-index: 8 !default; // One above mdc-dialog | ||
$shape-radius: medium !default; | ||
$deceleration-curve-timing-function: cubic-bezier(0, 0, 0.2, 1) !default; | ||
|
||
@mixin core-styles() { | ||
// postcss-bem-linter: define menu-surface | ||
.md3-menu-surface { | ||
@include base_(); | ||
// TODO(b/239422022): Remove in favor of theming API. | ||
// @include elevation-mixins.elevation($z-value: 8); | ||
@include fill-color(surface); | ||
@include ink-color(on-surface); | ||
@include shape-radius($shape-radius); | ||
|
||
// TODO(b/239421773): Fix this for RTL. | ||
transform-origin: top left; | ||
} | ||
|
||
.md3-menu-surface--anchor { | ||
position: relative; | ||
overflow: visible; | ||
} | ||
|
||
.md3-menu-surface--fixed { | ||
position: fixed; | ||
} | ||
|
||
.md3-menu-surface--fullwidth { | ||
width: 100%; | ||
} | ||
// postcss-bem-linter: end | ||
} | ||
|
||
@mixin ink-color($color) { | ||
color: $color; | ||
} | ||
|
||
@mixin fill-color($color) { | ||
background-color: $color; | ||
} | ||
|
||
@mixin shape-radius($radius) { | ||
border-radius: $radius; | ||
} | ||
|
||
// Used by filled variants of GM components to conditionally flatten the top | ||
// corners of the menu. | ||
@mixin flatten-top-when-opened-below() { | ||
.md3-menu-surface--is-open-below { | ||
border-top-left-radius: 0px; | ||
border-top-right-radius: 0px; | ||
} | ||
} | ||
|
||
// | ||
// Private | ||
// | ||
|
||
@mixin base_() { | ||
display: none; | ||
position: absolute; | ||
box-sizing: border-box; | ||
|
||
$max-width: calc(100vw - #{$min-distance-from-edge}); | ||
$max-height: calc(100vh - #{$min-distance-from-edge}); | ||
|
||
max-width: $max-width; | ||
max-height: $max-height; | ||
margin: 0; | ||
padding: 0; | ||
transform: scale(1); | ||
transform-origin: top left; | ||
opacity: 0; | ||
overflow: auto; | ||
will-change: transform, opacity; | ||
z-index: $z-index; | ||
|
||
transition: opacity $fade-in-duration linear, | ||
transform $scale-duration $deceleration-curve-timing-function, | ||
height 250ms $deceleration-curve-timing-function; | ||
|
||
&:focus { | ||
outline: none; | ||
} | ||
|
||
&--animating-open { | ||
display: inline-block; | ||
transform: scale(0.8); | ||
opacity: 0; | ||
} | ||
|
||
// Render this after `--animating-open` to override `opacity` & `transform` | ||
// CSS properties. | ||
&--open { | ||
display: inline-block; | ||
transform: scale(1); | ||
opacity: 1; | ||
} | ||
|
||
&--animating-closed { | ||
display: inline-block; | ||
opacity: 0; | ||
|
||
transition: opacity $fade-out-duration linear; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* @license | ||
* Copyright 2022 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import {MDCMenuDimensions, MDCMenuDistance, MDCMenuPoint} from './types'; | ||
|
||
/** | ||
* Defines the shape of the adapter expected by the foundation. | ||
* Implement this adapter for your framework of choice to delegate updates to | ||
* the component in your framework of choice. See architecture documentation | ||
* for more details. | ||
* https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md | ||
*/ | ||
export interface MDCMenuSurfaceAdapter { | ||
addClass(className: string): void; | ||
removeClass(className: string): void; | ||
hasClass(className: string): boolean; | ||
hasAnchor(): boolean; | ||
|
||
isElementInContainer(el: Element): boolean; | ||
isFocused(): boolean; | ||
isRtl(): boolean; | ||
|
||
getInnerDimensions(): MDCMenuDimensions; | ||
getAnchorDimensions(): DOMRect|null; | ||
getWindowDimensions(): MDCMenuDimensions; | ||
getBodyDimensions(): MDCMenuDimensions; | ||
getWindowScroll(): MDCMenuPoint; | ||
setPosition(position: Partial<MDCMenuDistance>): void; | ||
setMaxHeight(height: string): void; | ||
setTransformOrigin(origin: string): void; | ||
getOwnerDocument?(): Document; | ||
|
||
/** Saves the element that was focused before the menu surface was opened. */ | ||
saveFocus(): void; | ||
|
||
/** | ||
* Restores focus to the element that was focused before the menu surface was | ||
* opened. | ||
*/ | ||
restoreFocus(): void; | ||
|
||
/** Emits an event when the menu surface is closed. */ | ||
notifyClose(): void; | ||
|
||
/** Emits an event when the menu surface is closing. */ | ||
notifyClosing(): void; | ||
|
||
/** Emits an event when the menu surface is opened. */ | ||
notifyOpen(): void; | ||
|
||
/** Emits an event when the menu surface is opening. */ | ||
notifyOpening(): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/** | ||
* @license | ||
* Copyright 2022 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
const cssClasses = { | ||
ANCHOR: 'md3-menu-surface--anchor', | ||
ANIMATING_CLOSED: 'md3-menu-surface--animating-closed', | ||
ANIMATING_OPEN: 'md3-menu-surface--animating-open', | ||
FIXED: 'md3-menu-surface--fixed', | ||
IS_OPEN_BELOW: 'md3-menu-surface--is-open-below', | ||
OPEN: 'md3-menu-surface--open', | ||
ROOT: 'md3-menu-surface', | ||
}; | ||
|
||
// tslint:disable:object-literal-sort-keys | ||
const strings = { | ||
CLOSED_EVENT: 'MDCMenuSurface:closed', | ||
CLOSING_EVENT: 'MDCMenuSurface:closing', | ||
OPENED_EVENT: 'MDCMenuSurface:opened', | ||
OPENING_EVENT: 'MDCMenuSurface:opening', | ||
FOCUSABLE_ELEMENTS: [ | ||
'button:not(:disabled)', | ||
'[href]:not([aria-disabled="true"])', | ||
'input:not(:disabled)', | ||
'select:not(:disabled)', | ||
'textarea:not(:disabled)', | ||
'[tabindex]:not([tabindex="-1"]):not([aria-disabled="true"])', | ||
].join(', '), | ||
}; | ||
// tslint:enable:object-literal-sort-keys | ||
|
||
const numbers = { | ||
/** Total duration of menu-surface open animation. */ | ||
TRANSITION_OPEN_DURATION: 120, | ||
|
||
/** Total duration of menu-surface close animation. */ | ||
TRANSITION_CLOSE_DURATION: 75, | ||
|
||
/** | ||
* Margin left to the edge of the viewport when menu-surface is at maximum | ||
* possible height. Also used as a viewport margin. | ||
*/ | ||
MARGIN_TO_EDGE: 32, | ||
|
||
/** | ||
* Ratio of anchor width to menu-surface width for switching from corner | ||
* positioning to center positioning. | ||
*/ | ||
ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO: 0.67, | ||
|
||
/** | ||
* Amount of time to wait before restoring focus when closing the menu | ||
* surface. This is important because if a touch event triggered the menu | ||
* close, and the subsequent mouse event occurs after focus is restored, then | ||
* the restored focus would be lost. | ||
*/ | ||
TOUCH_EVENT_WAIT_MS: 30, | ||
}; | ||
|
||
/** | ||
* Enum for bits in the {@see Corner) bitmap. | ||
*/ | ||
enum CornerBit { | ||
BOTTOM = 1, | ||
CENTER = 2, | ||
RIGHT = 4, | ||
FLIP_RTL = 8, | ||
} | ||
|
||
/** | ||
* Enum for representing an element corner for positioning the menu-surface. | ||
* | ||
* The START constants map to LEFT if element directionality is left | ||
* to right and RIGHT if the directionality is right to left. | ||
* Likewise END maps to RIGHT or LEFT depending on the directionality. | ||
*/ | ||
enum Corner { | ||
TOP_LEFT = 0, | ||
TOP_RIGHT = CornerBit.RIGHT, | ||
BOTTOM_LEFT = CornerBit.BOTTOM, | ||
BOTTOM_RIGHT = | ||
CornerBit.BOTTOM | CornerBit.RIGHT, // tslint:disable-line:no-bitwise | ||
TOP_START = CornerBit.FLIP_RTL, | ||
TOP_END = | ||
CornerBit.FLIP_RTL | CornerBit.RIGHT, // tslint:disable-line:no-bitwise | ||
BOTTOM_START = | ||
CornerBit.BOTTOM | CornerBit.FLIP_RTL, // tslint:disable-line:no-bitwise | ||
BOTTOM_END = CornerBit.BOTTOM | CornerBit.RIGHT | | ||
CornerBit.FLIP_RTL, // tslint:disable-line:no-bitwise | ||
} | ||
|
||
export {cssClasses, strings, numbers, CornerBit, Corner}; |
Oops, something went wrong.