diff --git a/docs/docs/components/page.md b/docs/docs/components/page.md
index c38e0e598..9c1e1798e 100644
--- a/docs/docs/components/page.md
+++ b/docs/docs/components/page.md
@@ -794,11 +794,29 @@ You can override the default display and flex properties for each slot with your
#### Responsive Navigation
When you use the `navigation` slot, your slotted content automatically collapses into a drawer on smaller screens.
-The breakpoint at which this occurs is `768px` by default, but you can change it using the `mobile-breakpoint` attribute.
+The breakpoint at which this occurs is `768px` by default, but you can change it using the `mobile-breakpoint` attribute,
+which takes either a number or a [CSS length](https://developer.mozilla.org/en-US/docs/Web/CSS/length).
```html
...
```
+```html {.example viewport}
+
+ Nav
+
+
+
+```
By default, a "hamburger" button appears in the `header` slot to toggle the navigation menu on smaller screens.
You can customize what this looks like by slotting your own button in the `toggle-navigation` slot,
diff --git a/src/components/page/page.mobile.styles.ts b/src/components/page/page.mobile.styles.ts
index 2fa3d5ebf..6338be789 100644
--- a/src/components/page/page.mobile.styles.ts
+++ b/src/components/page/page.mobile.styles.ts
@@ -1,7 +1,5 @@
-export default (breakpoint: number) => `
- @media screen and (
- max-width: ${(Number.isSafeInteger(breakpoint) ? breakpoint.toString() : '768') + 'px'}
- ) {
+export default (breakpoint: string = '768px') => `
+ @media screen and (width < ${breakpoint}) {
[part~='navigation'] {
display: none;
}
diff --git a/src/components/page/page.ts b/src/components/page/page.ts
index 7e5bc8ccd..52377d838 100644
--- a/src/components/page/page.ts
+++ b/src/components/page/page.ts
@@ -2,10 +2,12 @@ import '../drawer/drawer.js';
import { customElement, property, query } from 'lit/decorators.js';
import { html, isServer } from 'lit';
import { live } from 'lit/directives/live.js';
+import { toLength, toPx } from '../../internal/css-values.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import mobileStyles from './page.mobile.styles.js';
import styles from './page.css';
import WebAwesomeElement from '../../internal/webawesome-element.js';
+
import type { PropertyValues } from 'lit';
import type WaDrawer from '../drawer/drawer.js';
@@ -138,9 +140,11 @@ export default class WaPage extends WebAwesomeElement {
@property({ attribute: 'nav-open', reflect: true, type: Boolean }) navOpen = false;
/**
- * At what "px" to hide the "menu" slot and collapse into a hamburger button
+ * At what page width to hide the "menu" slot and collapse into a hamburger button.
+ * Accepts both numbers (interpreted as px) and CSS lengths (e.g. `50em`), which are resolved based on the root element.
*/
- @property({ attribute: 'mobile-breakpoint', type: Number }) mobileBreakpoint = 768;
+ @property({ attribute: 'mobile-breakpoint', type: String })
+ mobileBreakpoint = '768px';
/**
* Where to place the navigation when in the mobile viewport.
@@ -148,7 +152,9 @@ export default class WaPage extends WebAwesomeElement {
@property({ attribute: 'navigation-placement', reflect: true }) navigationPlacement: 'start' | 'end' = 'start';
/**
- * Determines whether or not to hide the default hamburger button. This will automatically flip to "true" if you add an element with `data-toggle-nav` anywhere in the element light DOM. Generally this will be set for you and you don't need to do anything, unless you're using SSR, in which case you should set this manually for initial page loads.
+ * Determines whether or not to hide the default hamburger button.
+ * This will automatically flip to "true" if you add an element with `data-toggle-nav` anywhere in the element light DOM.
+ * Generally this will be set for you and you don't need to do anything, unless you're using SSR, in which case you should set this manually for initial page loads.
*/
@property({ attribute: 'disable-navigation-toggle', reflect: true, type: Boolean }) disableNavigationToggle: boolean =
false;
@@ -161,15 +167,13 @@ export default class WaPage extends WebAwesomeElement {
const oldView = this.view;
- if (pageWidth >= this.mobileBreakpoint) {
+ if (pageWidth >= toPx(this.mobileBreakpoint)) {
this.view = 'desktop';
} else {
this.view = 'mobile';
}
this.requestUpdate('view', oldView);
-
- this.style.setProperty(`--page-width`, `${pageWidth}px`);
}
}
});
@@ -262,7 +266,7 @@ export default class WaPage extends WebAwesomeElement {
${unsafeHTML(`
`)}
diff --git a/src/internal/css-values.ts b/src/internal/css-values.ts
new file mode 100644
index 000000000..f0c6a2dad
--- /dev/null
+++ b/src/internal/css-values.ts
@@ -0,0 +1,63 @@
+import { getComputedStyle } from './computedStyle.js';
+
+const definedProperties = new Set();
+const initialValues: Record = {
+ length: '0px',
+ time: '0s',
+ angle: '0deg',
+ color: 'transparent'
+};
+
+interface ResolveOptions {
+ on?: HTMLElement | SVGElement;
+ as?: string;
+ initialValue?: string;
+}
+
+export function resolve(
+ value: string,
+ { on = document.documentElement, as = 'length', initialValue = initialValues[as] }: ResolveOptions = {}
+) {
+ const resolver = `--wa-${as}-resolver`;
+
+ if (!window.CSS || !CSS.registerProperty) {
+ return value;
+ }
+
+ if (!definedProperties.has(resolver)) {
+ CSS.registerProperty({
+ name: resolver,
+ syntax: `<${as}>`,
+ inherits: false,
+ initialValue
+ });
+
+ definedProperties.add(resolver);
+ }
+
+ const previousValue = on.style.getPropertyValue(resolver);
+ on.style.setProperty(resolver, value);
+ const ret = getComputedStyle(on)?.getPropertyValue(resolver);
+ on.style.setProperty(resolver, previousValue);
+
+ return ret ?? value;
+}
+
+export function toPx(value: string | number, element: HTMLElement | SVGElement = document.documentElement): number {
+ if (!Number.isNaN(Number(value))) {
+ // Number of string containing a pure number
+ return Number(value);
+ }
+
+ const resolved = resolve(value as string, { on: element });
+
+ if (resolved?.endsWith('px')) {
+ return parseFloat(resolved);
+ }
+
+ return Number(resolved);
+}
+
+export function toLength(px: number | string): string {
+ return Number.isNaN(Number(px)) ? `${px}px` : (px as string);
+}