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()}