diff --git a/components/page/demo/page-component.js b/components/page/demo/page-component.js index 19527902733..a5a628093d8 100644 --- a/components/page/demo/page-component.js +++ b/components/page/demo/page-component.js @@ -27,13 +27,13 @@ import '../../selection/selection-action.js'; import '../../switch/switch-visibility.js'; import '../../switch/switch.js'; import '../../table/table-controls.js'; -import '../page.js'; import '../page-footer.js'; import '../page-main.js'; import '../page-side-nav.js'; import '../page-supporting.js'; import './page-header-full.js'; import { css, html, LitElement, nothing } from 'lit'; +import { _forceLegacyBrowserMode } from '../page.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { inputLabelStyles } from '../../inputs/input-label-styles.js'; import { pageHeaderImmersiveActionsDemo } from '../test/page-header-immersive-fixtures.js'; @@ -52,6 +52,7 @@ class PageDemo extends LitElement { header: { type: String, attribute: 'header' }, immersiveHeaderTitleType: { type: String, attribute: 'immersive-header-title-type' }, layout: { type: String, attribute: 'layout' }, + legacyBrowserMode: { type: String, attribute: 'legacy-browser-mode' }, widthType: { type: String, attribute: 'width-type' }, _mainDialogOpened: { state: true }, _mainToastOpened: { state: true }, @@ -86,6 +87,8 @@ class PageDemo extends LitElement { this.header = urlParams.get('header') || 'full'; this.immersiveHeaderTitleType = urlParams.get('immersiveHeaderTitleType') || 'title-subtitle'; this.layout = urlParams.get('layout') || 'main-only'; + this.legacyBrowserMode = urlParams.has('legacyBrowserMode'); + if (this.legacyBrowserMode) _forceLegacyBrowserMode(true); this.widthType = urlParams.get('widthType') || 'normal'; this._mainDialogOpened = false; this._mainToastOpened = false; @@ -124,6 +127,13 @@ class PageDemo extends LitElement { this.#updateUrlParam('layout', this.layout); } + #handleLegacyBrowserModeChange(e) { + this.legacyBrowserMode = e.target.on; + _forceLegacyBrowserMode(this.legacyBrowserMode); + this.#updateUrlParamBool('legacyBrowserMode', this.legacyBrowserMode); + this.shadowRoot.querySelector('d2l-page')?.requestUpdate(); + } + #handleMainDialogClose() { this._mainDialogOpened = false; this._mainToastOpened = true; @@ -251,6 +261,9 @@ class PageDemo extends LitElement { ${this.layout === 'side-nav' ? html`` : nothing} ${this.layout === 'supporting' ? html`` : nothing} + + + ${this.#renderDemoHeaderControls()} diff --git a/components/page/page-main.js b/components/page/page-main.js index ad82de676ea..614a5fcf535 100644 --- a/components/page/page-main.js +++ b/components/page/page-main.js @@ -11,7 +11,7 @@ class PageMain extends PagePanelMixin(LitElement) { static styles = [pagePanelStyles, css` .panel-header { - top: var(--d2l-page-header-height, 0); + top: var(--d2l-page-main-sticky-top, 0); } `]; diff --git a/components/page/page.js b/components/page/page.js index 6791db83bb1..4b9d13708c2 100644 --- a/components/page/page.js +++ b/components/page/page.js @@ -11,6 +11,22 @@ const DRAWER_MIN_HEIGHT = 200; // TO DO: Confirm const MAIN_MIN_WIDTH = 600; // TO DO: Confirm const PANEL_MIN_WIDTH = 320; +/* +* Legacy Browser Mode can be deleted once we have hard-blocked all browsers that do not support the Popover api, +* and dialog will be able to escape stacking contexts. It creates a "scrolling container" layout for pages with panels, +* to avoid the stacking context created by position: sticky. +*/ +let forceLegacyBrowserMode = false; +const isPopoverSupported = ('popover' in HTMLElement.prototype); +function isLegacyBrowserMode() { + return forceLegacyBrowserMode || !isPopoverSupported; +} +// Back door for the demo page and unit tests to force legacy browser mode, bypassing popover feature detection. +// DO NOT use in production. +export function _forceLegacyBrowserMode(on) { + forceLegacyBrowserMode = on; +} + class PanelStateController { constructor(host, panelConfigs) { this.#host = host; @@ -117,11 +133,12 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) { max-width: var(--d2l-page-content-max-width, 100%); padding-bottom: var(--d2l-page-footer-height, 0); /* Reserve space for fixed footer */ } - .content.has-panels { + .page.has-panels .content { min-height: calc(100vh - var(--d2l-page-header-height-measured, 0px)); } main { + --d2l-page-main-sticky-top: var(--d2l-page-header-height, 0); flex: 1; min-width: min(${MAIN_MIN_WIDTH}px, 100%); } @@ -160,6 +177,43 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) { margin-inline: var(--d2l-page-margin-inline, 0); max-width: var(--d2l-page-footer-max-width, 100%); } + + /* Legacy Browser Mode */ + .page.legacy-browser-mode.has-panels { + display: flex; + flex-direction: column; + height: 100vh; + } + .page.legacy-browser-mode.has-panels .header, + .page.legacy-browser-mode.header-sticky.has-panels .header { + flex: 0 0 auto; + position: static; + } + .page.legacy-browser-mode.has-panels .content { + flex: 1 1 auto; + min-height: 0; + overflow: hidden; + padding-bottom: 0; + width: 100%; + } + .page.legacy-browser-mode.has-panels main { + --d2l-page-main-sticky-top: 0; /* Header is static and content scrolls within main, so panel headers stick to the top of the scroll container */ + min-height: 0; + overflow: auto; + } + .page.legacy-browser-mode.has-panels .side-nav-panel, + .page.legacy-browser-mode.has-panels .supporting-panel, + .page.legacy-browser-mode.has-panels .divider { + max-height: none; + min-height: 0; + position: static; + } + .page.legacy-browser-mode.has-panels .footer { + flex: 0 0 auto; + } + .page.legacy-browser-mode.has-panels .fixed-footer { + position: static; + } `; constructor() { @@ -229,17 +283,15 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) { render() { const pageClasses = { 'page': true, - 'header-sticky': this._headerIsSticky - }; - const contentClasses = { - 'content': true, + 'header-sticky': this._headerIsSticky, + 'legacy-browser-mode': isLegacyBrowserMode(), 'has-panels': this._slotVisibility['side-nav'] || this._slotVisibility['supporting'] }; return html`
${this.#renderHeader()} -
+
${this.#renderSideNavPanel()}
${this.#renderSupportingPanel()}