Skip to content

Commit

Permalink
refactor(menu,select)!: rename fixed to positioning
Browse files Browse the repository at this point in the history
This will enable forwards compatibility for `positioning="top-layer"` with popover.

BREAKING CHANGE: refactor `fixed` property to `positioning="fixed"` in Menu and `menuFixed` to `menuPositioning="fixed"`

PiperOrigin-RevId: 567723646
  • Loading branch information
Elliott Marquez authored and copybara-github committed Sep 22, 2023
1 parent 2a1d877 commit 63b0142
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 71 deletions.
2 changes: 1 addition & 1 deletion catalog/stories/components/knob-ui-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export function selectDropdown<T extends string>({
<label>
<md-filled-select
@change="${valueChanged}"
menu-fixed
menu-positioning="fixed"
style=${styleMap(sharedTextFieldStyles)}>
${listItems}
</md-filled-select>
Expand Down
2 changes: 1 addition & 1 deletion dialog/internal/_dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
overflow: hidden;
// needed to display scrollbars on Chrome linux. Also needs to be > 0 so
// that content that is position: fixed in the content can render above the
// actions bar. e.g. <md-select menu-fixed>
// actions bar. e.g. <md-select positioning="menu-fixed">
z-index: 1;
}

Expand Down
2 changes: 1 addition & 1 deletion docs/components/figures/menu/usage-fixed.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<div style="margin: 16px">
<md-filled-button id="usage-fixed-anchor">Open fixed menu</md-filled-button>
</div>
<md-menu fixed id="usage-fixed" anchor="usage-fixed-anchor">
<md-menu positioning="fixed" id="usage-fixed" anchor="usage-fixed-anchor">
<md-menu-item headline="Apple"></md-menu-item>
<md-menu-item headline="Banana"></md-menu-item>
<md-menu-item headline="Cucumber"></md-menu-item>
Expand Down
7 changes: 3 additions & 4 deletions docs/components/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ Internally menu uses `position: absolute` by default. Though there are cases
when the anchor and the node cannot share a common ancestor that is `position:
relative`, or sometimes, menu will render below another item due to limitations
with `position: absolute`. In most of these cases, you would want to use the
`fixed` attribute to position the menu relative to the window instead of
relative to the parent.
`positioning="fixed"` attribute to position the menu relative to the window
instead of relative to the parent.

> Note: Fixed menu positions are positioned relative to the window and not the
> document. This means that the menu will not scroll with the anchor as the page
Expand All @@ -217,7 +217,7 @@ Cucumber."](images/menu/usage-fixed.webp)
</div>

<!-- Fixed menus do not require a common ancestor with the anchor. -->
<md-menu fixed id="usage-fixed" anchor="usage-fixed-anchor">
<md-menu positioning="fixed" id="usage-fixed" anchor="usage-fixed-anchor">
<md-menu-item headline="Apple"></md-menu-item>
<md-menu-item headline="Banana"></md-menu-item>
<md-menu-item headline="Cucumber"></md-menu-item>
Expand Down Expand Up @@ -353,7 +353,6 @@ a sharp 0px border radius.](images/menu/theming.webp)

## API


### MdMenu &lt;md-menu&gt;

#### Properties
Expand Down
13 changes: 9 additions & 4 deletions menu/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,16 @@ const collection =
]
}),
}),
new Knob('open', {
defaultValue: false,
ui: boolInput(),
new Knob('positioning', {
defaultValue: 'absolute' as const,
ui: selectDropdown<'absolute'|'fixed'>({
options: [
{label: 'absolute', value: 'absolute'},
{label: 'fixed', value: 'fixed'},
]
})
}),
new Knob('fixed', {
new Knob('open', {
defaultValue: false,
ui: boolInput(),
}),
Expand Down
14 changes: 7 additions & 7 deletions menu/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export interface StoryKnobs {
anchorCorner: Corner|undefined;
menuCorner: Corner|undefined;
defaultFocus: FocusState|undefined;
positioning: 'absolute'|'fixed'|undefined;
open: boolean;
fixed: boolean;
quick: boolean;
hasOverflow: boolean;
stayOpenOnOutsideClick: boolean;
Expand Down Expand Up @@ -120,7 +120,7 @@ const standard: MaterialStoryInit<StoryKnobs> = {
.menuCorner="${knobs.menuCorner!}"
.xOffset=${knobs.xOffset}
.yOffset=${knobs.yOffset}
.fixed=${knobs.fixed}
.positioning=${knobs.positioning!}
.defaultFocus=${knobs.defaultFocus!}
.skipRestoreFocus=${knobs.skipRestoreFocus}
.typeaheadDelay=${knobs.typeaheadDelay}
Expand Down Expand Up @@ -188,7 +188,7 @@ const linkable: MaterialStoryInit<StoryKnobs> = {
.menuCorner="${knobs.menuCorner!}"
.xOffset=${knobs.xOffset}
.yOffset=${knobs.yOffset}
.fixed=${knobs.fixed}
.positioning=${knobs.positioning!}
.defaultFocus=${knobs.defaultFocus!}
.skipRestoreFocus=${knobs.skipRestoreFocus}
.typeaheadDelay=${knobs.typeaheadDelay}
Expand Down Expand Up @@ -250,7 +250,7 @@ const submenu: MaterialStoryInit<StoryKnobs> = {
.ariaLabel=${knobs.ariaLabel}
.xOffset=${knobs.xOffset}
.yOffset=${knobs.yOffset}
.fixed=${knobs.fixed}
.positioning=${knobs.positioning!}
.defaultFocus=${knobs.defaultFocus!}
.typeaheadDelay=${knobs.typeaheadDelay}>
${layer2}
Expand Down Expand Up @@ -296,7 +296,7 @@ const submenu: MaterialStoryInit<StoryKnobs> = {
.ariaLabel=${knobs.ariaLabel}
.xOffset=${knobs.xOffset}
.yOffset=${knobs.yOffset}
.fixed=${knobs.fixed}
.positioning=${knobs.positioning!}
.defaultFocus=${knobs.defaultFocus!}
.typeaheadDelay=${knobs.typeaheadDelay}>
${layer1}
Expand Down Expand Up @@ -327,7 +327,7 @@ const submenu: MaterialStoryInit<StoryKnobs> = {
.menuCorner="${knobs.menuCorner!}"
.xOffset=${knobs.xOffset}
.yOffset=${knobs.yOffset}
.fixed=${knobs.fixed}
.positioning=${knobs.positioning!}
.defaultFocus=${knobs.defaultFocus!}
.skipRestoreFocus=${knobs.skipRestoreFocus}
.typeaheadDelay=${knobs.typeaheadDelay}
Expand Down Expand Up @@ -377,7 +377,7 @@ const menuWithoutButton: MaterialStoryInit<StoryKnobs> = {
.menuCorner="${knobs.menuCorner!}"
.xOffset=${knobs.xOffset}
.yOffset=${knobs.yOffset}
.fixed=${knobs.fixed}
.positioning=${knobs.positioning!}
.defaultFocus=${knobs.defaultFocus!}
.skipRestoreFocus=${knobs.skipRestoreFocus}
.typeaheadDelay=${knobs.typeaheadDelay}
Expand Down
43 changes: 19 additions & 24 deletions menu/internal/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {createAnimationSignal, EASING} from '../../internal/motion/animation.js'

import {ListController, NavigableKeys} from './list-controller.js';
import {getActiveItem, getFirstActivatableItem, getLastActivatableItem} from './list-navigation-helpers.js';
import {ActivateTypeaheadEvent, DeactivateTypeaheadEvent, isClosableKey, isElementInSubtree, MenuItem} from './shared.js';
import {ActivateTypeaheadEvent, DeactivateTypeaheadEvent, FocusState, isClosableKey, isElementInSubtree, MenuItem} from './shared.js';
import {Corner, SurfacePositionController, SurfacePositionTarget} from './surfacePositionController.js';
import {TypeaheadController} from './typeaheadController.js';

Expand All @@ -41,22 +41,6 @@ const menuNavKeys = new Set<string>([
...submenuNavKeys,
]);

/**
* Element to focus on when menu is first opened.
*/
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
export const FocusState = {
NONE: 'none',
LIST_ROOT: 'list-root',
FIRST_ITEM: 'first-item',
LAST_ITEM: 'last-item'
} as const;

/**
* Element to focus on when menu is first opened.
*/
export type FocusState = typeof FocusState[keyof typeof FocusState];

/**
* Gets the currently focused element on the page.
*
Expand Down Expand Up @@ -101,15 +85,26 @@ export abstract class Menu extends LitElement {
*/
@property() anchor = '';
/**
* Makes the element use `position:fixed` instead of `position:absolute`. In
* most cases, the menu should position itself above most other
* `position:absolute` or `position:fixed` elements when placed inside of
* them. e.g. using a menu inside of an `md-dialog`.
* Whether the positioning algorithim should calculate relative to the parent
* of the anchor element (absolute) or relative to the window (fixed).
*
* Examples for `position = 'fixed'`:
*
* - If there is no `position:relative` in the given parent tree and the
* surface is `position:absolute`
* - If the surface is `position:fixed`
* - If the surface is in the "top layer"
* - The anchor and the surface do not share a common `position:relative`
* ancestor
*
* When using positioning = fixed, in most cases, the menu should position
* itself above most other `position:absolute` or `position:fixed` elements
* when placed inside of them. e.g. using a menu inside of an `md-dialog`.
*
* __NOTE__: Fixed menus will not scroll with the page and will be fixed to
* the window instead.
*/
@property({type: Boolean}) fixed = false;
@property() positioning: 'absolute'|'fixed' = 'absolute';
/**
* Skips the opening and closing animations.
*/
Expand Down Expand Up @@ -325,7 +320,7 @@ export abstract class Menu extends LitElement {
surfaceCorner: this.menuCorner,
surfaceEl: this.surfaceEl,
anchorEl: this.anchorElement,
isTopLayer: this.fixed,
positioning: this.positioning,
isOpen: this.open,
xOffset: this.xOffset,
yOffset: this.yOffset,
Expand Down Expand Up @@ -403,7 +398,7 @@ export abstract class Menu extends LitElement {
private getSurfaceClasses() {
return {
open: this.open,
fixed: this.fixed,
fixed: this.positioning === 'fixed',
'has-overflow': this.hasOverflow,
};
}
Expand Down
16 changes: 16 additions & 0 deletions menu/internal/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,19 @@ export function isElementInSubtree(
const isContained = composedPath.length > 0;
return isContained;
}

/**
* Element to focus on when menu is first opened.
*/
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
export const FocusState = {
NONE: 'none',
LIST_ROOT: 'list-root',
FIRST_ITEM: 'first-item',
LAST_ITEM: 'last-item'
} as const;

/**
* Element to focus on when menu is first opened.
*/
export type FocusState = typeof FocusState[keyof typeof FocusState];
36 changes: 19 additions & 17 deletions menu/internal/surfacePositionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ export interface SurfacePositionControllerProperties {
*/
anchorEl: SurfacePositionTarget|null;
/**
* Whether or not the calculation should be relative to the top layer rather
* than relative to the parent of the anchor.
* Whether the positioning algorithim should calculate relative to the parent
* of the anchor element (absolute) or relative to the window (fixed).
*
* Examples for `isTopLayer:true`:
* Examples for `position = 'fixed'`:
*
* - If there is no `position:relative` in the given parent tree and the
* surface is `position:absolute`
Expand All @@ -65,7 +65,7 @@ export interface SurfacePositionControllerProperties {
* - The anchor and the surface do not share a common `position:relative`
* ancestor
*/
isTopLayer: boolean;
positioning: 'absolute'|'fixed';
/**
* Whether or not the surface should be "open" and visible
*/
Expand Down Expand Up @@ -153,7 +153,7 @@ export class SurfacePositionController implements ReactiveController {
anchorEl,
anchorCorner: anchorCornerRaw,
surfaceCorner: surfaceCornerRaw,
isTopLayer,
positioning,
xOffset,
yOffset,
repositionStrategy,
Expand Down Expand Up @@ -231,7 +231,7 @@ export class SurfacePositionController implements ReactiveController {
anchorBlock,
surfaceBlock,
yOffset,
isTopLayer
positioning
});

// If the surface should be out of bounds in the block direction, flip the
Expand All @@ -246,7 +246,7 @@ export class SurfacePositionController implements ReactiveController {
anchorBlock: flippedAnchorBlock,
surfaceBlock: flippedSurfaceBlock,
yOffset,
isTopLayer
positioning
});

// In the case that the flipped verion would require less out of bounds
Expand All @@ -267,7 +267,7 @@ export class SurfacePositionController implements ReactiveController {
anchorInline,
surfaceInline,
xOffset,
isTopLayer,
positioning,
isLTR,
});

Expand All @@ -283,7 +283,7 @@ export class SurfacePositionController implements ReactiveController {
anchorInline: flippedAnchorInline,
surfaceInline: flippedSurfaceInline,
xOffset,
isTopLayer,
positioning,
isLTR,
});

Expand Down Expand Up @@ -340,19 +340,19 @@ export class SurfacePositionController implements ReactiveController {
anchorBlock: 'start'|'end',
surfaceBlock: 'start'|'end',
yOffset: number,
isTopLayer: boolean,
positioning: 'absolute'|'fixed',
}) {
const {
surfaceRect,
anchorRect,
anchorBlock,
surfaceBlock,
yOffset,
isTopLayer: isTopLayerBool,
positioning,
} = config;
// We use number booleans to multiply values rather than `if` / ternary
// statements because it _heavily_ cuts down on nesting and readability
const isTopLayer = isTopLayerBool ? 1 : 0;
const relativeToWindow = positioning === 'fixed' ? 1 : 0;
const isSurfaceBlockStart = surfaceBlock === 'start' ? 1 : 0;
const isSurfaceBlockEnd = surfaceBlock === 'end' ? 1 : 0;
const isOneBlockEnd = anchorBlock !== surfaceBlock ? 1 : 0;
Expand All @@ -371,7 +371,8 @@ export class SurfacePositionController implements ReactiveController {


// The block logical value of the surface
const blockInset = isTopLayer * blockTopLayerOffset + blockAnchorOffset;
const blockInset =
relativeToWindow * blockTopLayerOffset + blockAnchorOffset;

const surfaceBlockProperty =
surfaceBlock === 'start' ? 'inset-block-start' : 'inset-block-end';
Expand All @@ -390,7 +391,7 @@ export class SurfacePositionController implements ReactiveController {
anchorRect: DOMRect,
surfaceRect: DOMRect,
xOffset: number,
isTopLayer: boolean,
positioning: 'absolute'|'fixed',
}) {
const {
isLTR: isLTRBool,
Expand All @@ -399,11 +400,11 @@ export class SurfacePositionController implements ReactiveController {
anchorRect,
surfaceRect,
xOffset,
isTopLayer: isTopLayerBool,
positioning,
} = config;
// We use number booleans to multiply values rather than `if` / ternary
// statements because it _heavily_ cuts down on nesting and readability
const isTopLayer = isTopLayerBool ? 1 : 0;
const relativeToWindow = positioning === 'fixed' ? 1 : 0;
const isLTR = isLTRBool ? 1 : 0;
const isRTL = isLTRBool ? 0 : 1;
const isSurfaceInlineStart = surfaceInline === 'start' ? 1 : 0;
Expand Down Expand Up @@ -432,7 +433,8 @@ export class SurfacePositionController implements ReactiveController {


// The inline logical value of the surface
const inlineInset = isTopLayer * inlineTopLayerOffset + inlineAnchorOffset;
const inlineInset =
relativeToWindow * inlineTopLayerOffset + inlineAnchorOffset;

const surfaceInlineProperty =
surfaceInline === 'start' ? 'inset-inline-start' : 'inset-inline-end';
Expand Down
4 changes: 2 additions & 2 deletions menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {Menu} from './internal/menu.js';
import {styles} from './internal/menu-styles.css.js';

export {ListItem} from '../list/internal/listitem/list-item.js';
export {Corner, FocusState} from './internal/menu.js';
export {CloseMenuEvent, MenuItem} from './internal/shared.js';
export {Corner} from './internal/menu.js';
export {CloseMenuEvent, FocusState, MenuItem} from './internal/shared.js';

declare global {
interface HTMLElementTagNameMap {
Expand Down
5 changes: 3 additions & 2 deletions menu/sub-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ declare global {
* `deselect-items` events.
*
* This menu item will open a sub-menu that is slotted in the `submenu` slot.
* Additionally, the containing menu must either have `has-overflow` or `fixed`
* set to `true` in order to display the containing menu properly.
* Additionally, the containing menu must either have `has-overflow` or
* `positioning=fixed` set to `true` in order to display the containing menu
* properly.
*
* @example
* ```html
Expand Down
Loading

0 comments on commit 63b0142

Please sign in to comment.