diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 4b91e34a8a..5fa8d5395b 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2852,6 +2852,7 @@ export namespace Components { * If `true`, the split pane will be hidden. */ "disabled": boolean; + "isVisible": () => Promise; /** * When the split-pane should be shown. Can be a CSS media query expression, or a shortcut expression. Can also be a boolean expression. */ diff --git a/core/src/components/menu/menu.scss b/core/src/components/menu/menu.scss index 54cc316d6a..86b522b50c 100644 --- a/core/src/components/menu/menu.scss +++ b/core/src/components/menu/menu.scss @@ -179,10 +179,51 @@ ion-backdrop { // Menu Split Pane // -------------------------------------------------- +/** + * The split pane styles for menu are defined + * in the menu stylesheets instead in the split pane + * stylesheets with ::slotted to allow for menus + * to be wrapped in custom components. + * If we used ::slotted to target the menu + * then menus wrapped in components would never + * receive these styles because they are not + * children of the split pane. + */ + +/** + * Do not pass CSS Variables down on larger + * screens as we want them to affect the outer + * `ion-menu` rather than the inner content + */ :host(.menu-pane-visible) { - width: var(--width); - min-width: var(--min-width); - max-width: var(--max-width); + flex: 0 1 auto; + + width: var(--side-width, var(--width)); + min-width: var(--side-min-width, var(--min-width)); + max-width: var(--side-max-width, var(--max-width)); +} + +:host(.menu-pane-visible.split-pane-side) { + @include position(0, 0, 0, 0); + + position: relative; + + box-shadow: none; + z-index: 0; +} + +:host(.menu-pane-visible.split-pane-side.menu-enabled) { + display: flex; + + flex-shrink: 0; +} + +:host(.menu-pane-visible.split-pane-side) { + order: -1; +} + +:host(.menu-pane-visible.split-pane-side[side=end]) { + order: 1; } :host(.menu-pane-visible) .menu-inner { @@ -199,3 +240,18 @@ ion-backdrop { /* stylelint-disable-next-line declaration-no-important */ display: hidden !important; } + +:host(.menu-pane-visible.split-pane-side) { + @include border(0, var(--border), 0, 0); + + min-width: var(--side-min-width); + max-width: var(--side-max-width); +} + +:host(.menu-pane-visible.split-pane-side[side=end]) { + @include border(0, 0, 0, var(--border)); + + min-width: var(--side-min-width); + max-width: var(--side-max-width); +} + diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 45a03fad95..a559ce3963 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -6,6 +6,7 @@ import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers'; import { menuController } from '@utils/menu-controller'; import { getPresentedOverlay } from '@utils/overlays'; +import { hostContext } from '@utils/theme'; import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; @@ -77,6 +78,14 @@ export class Menu implements ComponentInterface, MenuI { @Element() el!: HTMLIonMenuElement; + /** + * If true, then the menu should be + * visible within a split pane. + * If false, then the menu is hidden. + * However, the menu-button/menu-toggle + * components can be used to open the + * menu. + */ @State() isPaneVisible = false; @State() isEndSide = false; @@ -225,8 +234,8 @@ export class Menu implements ComponentInterface, MenuI { // register this menu with the app's menu controller menuController._register(this); - this.menuChanged(); + this.menuChanged(); this.gesture = (await import('../../utils/gesture')).createGesture({ el: document, gestureName: 'menu-swipe', @@ -248,6 +257,21 @@ export class Menu implements ComponentInterface, MenuI { async componentDidLoad() { this.didLoad = true; + + /** + * A menu inside of a split pane is assumed + * to be a side pane. + * + * When the menu is loaded it needs to + * see if it should be considered visible inside + * of the split pane. If the split pane is + * hidden then the menu should be too. + */ + const splitPane = this.el.closest('ion-split-pane'); + if (splitPane !== null) { + this.isPaneVisible = await splitPane.isVisible(); + } + this.menuChanged(); this.updateState(); } @@ -288,23 +312,14 @@ export class Menu implements ComponentInterface, MenuI { } @Listen('ionSplitPaneVisible', { target: 'body' }) - onSplitPaneChanged(ev: CustomEvent) { - const { target } = ev; - const closestSplitPane = this.el.closest('ion-split-pane'); + onSplitPaneChanged(ev: CustomEvent<{ visible: boolean }>) { + const closestSplitPane = this.el.closest('ion-split-pane'); - /** - * Menu listens on the body for "ionSplitPaneVisible". - * However, this means the callback will run any time - * a SplitPane changes visibility. As a result, we only want - * Menu's visibility state to update if its parent SplitPane - * changes visibility. - */ - if (target !== closestSplitPane) { - return; + if (closestSplitPane !== null && closestSplitPane === ev.target) { + this.isPaneVisible = ev.detail.visible; + + this.updateState(); } - - this.isPaneVisible = ev.detail.isPane(this.el); - this.updateState(); } @Listen('click', { capture: true }) @@ -778,7 +793,7 @@ export class Menu implements ComponentInterface, MenuI { } render() { - const { type, disabled, isPaneVisible, inheritedAttributes, side } = this; + const { type, disabled, el, isPaneVisible, inheritedAttributes, side } = this; const mode = getIonMode(this); return ( @@ -791,6 +806,7 @@ export class Menu implements ComponentInterface, MenuI { 'menu-enabled': !disabled, [`menu-side-${side}`]: true, 'menu-pane-visible': isPaneVisible, + 'split-pane-side': hostContext('ion-split-pane', el), }} >