Skip to content

Commit

Permalink
feat(menusurface): Add menusurface class (forked from MWC/MDC).
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 461889186
  • Loading branch information
joyzhong authored and copybara-github committed Jul 19, 2022
1 parent e64cdbe commit 5f51f26
Show file tree
Hide file tree
Showing 9 changed files with 1,402 additions and 0 deletions.
47 changes: 47 additions & 0 deletions compat/base/utils.ts
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;
};
119 changes: 119 additions & 0 deletions menusurface/lib/_mixins.scss
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;
}
}
56 changes: 56 additions & 0 deletions menusurface/lib/adapter.ts
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;
}
94 changes: 94 additions & 0 deletions menusurface/lib/constants.ts
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};
Loading

0 comments on commit 5f51f26

Please sign in to comment.