Skip to content

Commit 6df7161

Browse files
committed
Allow users to disable surface flipping in md-menu
Sometimes the desired effect is to have the menu extend to the available height and then start overflowing instead of flipping to the opposite corner. Cases like search bar, autocomplete or any kind of input with suggestions below the textfield content.
1 parent 7231e51 commit 6df7161

File tree

5 files changed

+47
-2
lines changed

5 files changed

+47
-2
lines changed

docs/components/menu.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ a sharp 0px border radius.](images/menu/theming.webp)
527527
| `typeaheadDelay` | `typeahead-delay` | `number` | `200` | The max time between the keystrokes of the typeahead menu behavior before it clears the typeahead buffer. |
528528
| `anchorCorner` | `anchor-corner` | `string` | `Corner.END_START` | The corner of the anchor which to align the menu in the standard logical property style of <block>-<inline> e.g. `'end-start'`.<br>NOTE: This value may not be respected by the menu positioning algorithm if the menu would render outisde the viewport. |
529529
| `menuCorner` | `menu-corner` | `string` | `Corner.START_START` | The corner of the menu which to align the anchor in the standard logical property style of <block>-<inline> e.g. `'start-start'`.<br>NOTE: This value may not be respected by the menu positioning algorithm if the menu would render outisde the viewport. |
530+
| `noHorizontalFlip` | `no-horizontal-flip` | `boolean` | `false` | Disable the `flip` behavior that usually happens on the horizontal axis when the surface would render outside the viewport. |
531+
| `noVerticalFlip` | `no-vertical-flip` | `boolean` | `false` | Disable the `flip` behavior that usually happens on the vertical axis when the surface would render outside the viewport. |
530532
| `stayOpenOnOutsideClick` | `stay-open-on-outside-click` | `boolean` | `false` | Keeps the user clicks outside the menu.<br>NOTE: clicking outside may still cause focusout to close the menu so see `stayOpenOnFocusout`. |
531533
| `stayOpenOnFocusout` | `stay-open-on-focusout` | `boolean` | `false` | Keeps the menu open when focus leaves the menu's composed subtree.<br>NOTE: Focusout behavior will stop propagation of the focusout event. Set this property to true to opt-out of menu's focusout handling altogether. |
532534
| `skipRestoreFocus` | `skip-restore-focus` | `boolean` | `false` | After closing, does not restore focus to the last focused element before the menu was opened. |

menu/demo/demo.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
105105
defaultValue: 0,
106106
ui: numberInput(),
107107
}),
108+
new Knob('noHorizontalFlip', {
109+
defaultValue: false,
110+
ui: boolInput(),
111+
}),
112+
new Knob('noVerticalFlip', {
113+
defaultValue: false,
114+
ui: boolInput(),
115+
}),
108116
new Knob('typeaheadDelay', {
109117
defaultValue: 200,
110118
ui: numberInput(),

menu/demo/stories.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export interface StoryKnobs {
3131
skipRestoreFocus: boolean;
3232
xOffset: number;
3333
yOffset: number;
34+
noVerticalFlip: boolean;
35+
noHorizontalFlip: boolean;
3436
typeaheadDelay: number;
3537
listTabIndex: number;
3638

@@ -122,6 +124,8 @@ const standard: MaterialStoryInit<StoryKnobs> = {
122124
.menuCorner="${knobs.menuCorner!}"
123125
.xOffset=${knobs.xOffset}
124126
.yOffset=${knobs.yOffset}
127+
.noVerticalFlip=${knobs.noVerticalFlip}
128+
.noHorizontalFlip=${knobs.noHorizontalFlip}
125129
.positioning=${knobs.positioning!}
126130
.defaultFocus=${knobs.defaultFocus!}
127131
.skipRestoreFocus=${knobs.skipRestoreFocus}
@@ -191,6 +195,8 @@ const linkable: MaterialStoryInit<StoryKnobs> = {
191195
.menuCorner="${knobs.menuCorner!}"
192196
.xOffset=${knobs.xOffset}
193197
.yOffset=${knobs.yOffset}
198+
.noVerticalFlip=${knobs.noVerticalFlip}
199+
.noHorizontalFlip=${knobs.noHorizontalFlip}
194200
.positioning=${knobs.positioning!}
195201
.defaultFocus=${knobs.defaultFocus!}
196202
.skipRestoreFocus=${knobs.skipRestoreFocus}
@@ -327,6 +333,8 @@ const submenu: MaterialStoryInit<StoryKnobs> = {
327333
.menuCorner="${knobs.menuCorner!}"
328334
.xOffset=${knobs.xOffset}
329335
.yOffset=${knobs.yOffset}
336+
.noVerticalFlip=${knobs.noVerticalFlip}
337+
.noHorizontalFlip=${knobs.noHorizontalFlip}
330338
.positioning=${knobs.positioning!}
331339
.defaultFocus=${knobs.defaultFocus!}
332340
.skipRestoreFocus=${knobs.skipRestoreFocus}
@@ -379,6 +387,8 @@ const menuWithoutButton: MaterialStoryInit<StoryKnobs> = {
379387
.menuCorner="${knobs.menuCorner!}"
380388
.xOffset=${knobs.xOffset}
381389
.yOffset=${knobs.yOffset}
390+
.noVerticalFlip=${knobs.noVerticalFlip}
391+
.noHorizontalFlip=${knobs.noHorizontalFlip}
382392
.positioning=${knobs.positioning!}
383393
.defaultFocus=${knobs.defaultFocus!}
384394
.skipRestoreFocus=${knobs.skipRestoreFocus}

menu/internal/controllers/surfacePositionController.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ export interface SurfacePositionTarget extends HTMLElement {
5151
* The configurable options for the surface position controller.
5252
*/
5353
export interface SurfacePositionControllerProperties {
54+
/**
55+
* Disable the `flip` behavior on the block axis of the surface's corner
56+
*/
57+
disableBlockFlip: boolean;
58+
/**
59+
* Disable the `flip` behavior on the inline axis of the surface's corner
60+
*/
61+
disableInlineFlip: boolean;
5462
/**
5563
* The corner of the anchor to align the surface's position.
5664
*/
@@ -172,6 +180,8 @@ export class SurfacePositionController implements ReactiveController {
172180
positioning,
173181
xOffset,
174182
yOffset,
183+
disableBlockFlip,
184+
disableInlineFlip,
175185
repositionStrategy,
176186
} = this.getProperties();
177187
const anchorCorner = anchorCornerRaw.toLowerCase().trim();
@@ -293,7 +303,7 @@ export class SurfacePositionController implements ReactiveController {
293303

294304
// If the surface should be out of bounds in the block direction, flip the
295305
// surface and anchor corner block values and recalculate
296-
if (blockOutOfBoundsCorrection) {
306+
if (blockOutOfBoundsCorrection && !disableBlockFlip) {
297307
const flippedSurfaceBlock = surfaceBlock === 'start' ? 'end' : 'start';
298308
const flippedAnchorBlock = anchorBlock === 'start' ? 'end' : 'start';
299309

@@ -319,6 +329,7 @@ export class SurfacePositionController implements ReactiveController {
319329
}
320330
}
321331

332+
322333
// Calculate the inline positioning properties
323334
let {inlineInset, inlineOutOfBoundsCorrection, surfaceInlineProperty} =
324335
this.calculateInline({
@@ -335,7 +346,7 @@ export class SurfacePositionController implements ReactiveController {
335346

336347
// If the surface should be out of bounds in the inline direction, flip the
337348
// surface and anchor corner inline values and recalculate
338-
if (inlineOutOfBoundsCorrection) {
349+
if (inlineOutOfBoundsCorrection && !disableInlineFlip) {
339350
const flippedSurfaceInline = surfaceInline === 'start' ? 'end' : 'start';
340351
const flippedAnchorInline = anchorInline === 'start' ? 'end' : 'start';
341352

menu/internal/menu.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ export abstract class Menu extends LitElement {
171171
* e.g. positive -> down, negative -> up
172172
*/
173173
@property({type: Number, attribute: 'y-offset'}) yOffset = 0;
174+
/**
175+
* Disable the `flip` behavior that usually happens on the horizontal
176+
* axis when the surface would render outside the viewport.
177+
*/
178+
@property({type: Boolean, attribute: 'no-horizontal-flip'}) noHorizontalFlip = false;
179+
/**
180+
* Disable the `flip` behavior that usually happens on the vertical
181+
* axis when the surface would render outside the viewport.
182+
*/
183+
@property({type: Boolean, attribute: 'no-vertical-flip'}) noVerticalFlip = false;
174184
/**
175185
* The max time between the keystrokes of the typeahead menu behavior before
176186
* it clears the typeahead buffer.
@@ -183,6 +193,7 @@ export abstract class Menu extends LitElement {
183193
*
184194
* NOTE: This value may not be respected by the menu positioning algorithm
185195
* if the menu would render outisde the viewport.
196+
* Use `no-horizontal-flip` or `no-vertical-flip` to force the usage of the value
186197
*/
187198
@property({attribute: 'anchor-corner'})
188199
anchorCorner: Corner = Corner.END_START;
@@ -192,6 +203,7 @@ export abstract class Menu extends LitElement {
192203
*
193204
* NOTE: This value may not be respected by the menu positioning algorithm
194205
* if the menu would render outisde the viewport.
206+
* Use `no-horizontal-flip` or `no-vertical-flip` to force the usage of the value
195207
*/
196208
@property({attribute: 'menu-corner'}) menuCorner: Corner = Corner.START_START;
197209
/**
@@ -376,6 +388,8 @@ export abstract class Menu extends LitElement {
376388
isOpen: this.open,
377389
xOffset: this.xOffset,
378390
yOffset: this.yOffset,
391+
disableBlockFlip: this.noVerticalFlip,
392+
disableInlineFlip: this.noHorizontalFlip,
379393
onOpen: this.onOpened,
380394
beforeClose: this.beforeClose,
381395
onClose: this.onClosed,

0 commit comments

Comments
 (0)