@timmo001/groundwork-ui 0.9.26 → 0.9.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1485,7 +1485,7 @@
1485
1485
  "declarations": [
1486
1486
  {
1487
1487
  "kind": "class",
1488
- "description": "A site or app header pinned to the top of the page, with a brand, nav\nlinks and actions, full width with its content kept to the content width.\nIt's transparent at first, then gets a blurred surface and a bottom border\nonce the page scrolls, or once the element\n`reveal-after` names scrolls out of view, such as a hero. With\n`reveal-after` the brand stays hidden, taking no room, until then, so a\nlarge logo in the hero hands over to the small one in the bar. The nav\nslides over as it opens.\n\n`overlay` lays it over the top of the page rather than taking room, for a\npage that opens with a hero. Below `menu-below`, 600px wide by default,\nthe nav moves into a menu opened from a button, which closes on Esc, a\nclick outside or a link. Raise it when the nav and actions need more room.",
1488
+ "description": "A site or app header pinned to the top of the page, with a brand, nav\nlinks and actions, full width with its content kept to the content width.\nIt's transparent at first, then gets a blurred surface and a bottom border\nonce the page scrolls, or once the element\n`reveal-after` names scrolls out of view, such as a hero. With\n`reveal-after` the brand stays hidden, taking no room, until then, so a\nlarge logo in the hero hands over to the small one in the bar. The nav\nslides over as it opens.\n\n`overlay` lays it over the top of the page rather than taking room, for a\npage that opens with a hero. Below `menu-below`, 600px wide by default,\nthe nav moves into a menu opened from a button, which closes on Esc, a\nclick outside or a link. Raise it when the nav and actions need more room.\nWith no nav links there's no menu button, so a bar of only a brand and\nactions keeps its layout at every width.",
1489
1489
  "name": "GwAppBar",
1490
1490
  "cssParts": [
1491
1491
  {
@@ -1540,7 +1540,7 @@
1540
1540
  "text": "readonly CSSStyleSheet[]"
1541
1541
  },
1542
1542
  "static": true,
1543
- "default": "[ base, css` :host { position: sticky; inset-block-start: 0; z-index: 50; display: block; } :host([overlay]) { position: fixed; inset-inline: 0; } .bar { position: relative; display: flex; gap: var(--gw-space-3); align-items: center; min-height: 3.5rem; padding: var(--gw-space-1) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); border-block-end: max(var(--gw-border-width), 1px) solid transparent; transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing), backdrop-filter var(--gw-motion-normal) var(--gw-motion-easing); } :host([floating]) .bar, :host([menu-open]) .bar { background: color-mix( in oklab, var(--gw-color-surface-default) 82%, transparent ); border-color: var(--gw-color-border-quiet); backdrop-filter: blur(12px) saturate(1.4); } /* A one-column grid, so a hidden brand can fold its width to nothing and the nav slides over as it opens, rather than leaving a gap */ .brand { display: grid; flex: none; grid-template-columns: 1fr; align-items: center; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .brand > slot { display: flex; align-items: center; min-width: 0; overflow: clip; overflow-clip-margin: calc( var(--gw-focus-width) + var(--gw-focus-offset) ); } :host([reveal-after]:not([floating])) .brand { grid-template-columns: 0fr; margin-inline-end: calc(var(--gw-space-3) * -1); pointer-events: none; visibility: hidden; opacity: 0; translate: 0 -0.25rem; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-slow) allow-discrete; } /* The nav keeps its room; the actions take the rest, which a theme picker fills or gives up by moving its controls into its popover */ .nav { display: flex; flex: none; gap: var(--gw-space-1); align-items: center; min-width: 0; } .actions { display: flex; flex: 1 1 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } .menu { display: none; place-items: center; width: 2.5rem; height: 2.5rem; padding: 0; color: var(--gw-color-text-strong); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } .menu:hover { background: var(--gw-color-fill-neutral-hovered); } /* Below menu-below, which sets collapsed, the nav folds into a menu */ :host([collapsed]) .menu { display: inline-grid; } :host([collapsed]) .nav { position: absolute; inset-block-start: 100%; inset-inline: 0; flex-direction: column; gap: var(--gw-space-0-5); align-items: stretch; padding: var(--gw-space-2) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); visibility: hidden; background: var(--gw-color-surface-default); border-block-end: max(var(--gw-border-width), 1px) solid var(--gw-color-border-quiet); box-shadow: var(--gw-shadow-m); opacity: 0; translate: 0 -0.5rem; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-normal) allow-discrete; } :host([collapsed][menu-open]) .nav { visibility: visible; opacity: 1; translate: 0 0; } `, ]",
1543
+ "default": "[ base, css` :host { position: sticky; inset-block-start: 0; z-index: 50; display: block; } :host([overlay]) { position: fixed; inset-inline: 0; } .bar { position: relative; display: flex; gap: var(--gw-space-3); align-items: center; min-height: 3.5rem; padding: var(--gw-space-1) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); border-block-end: max(var(--gw-border-width), 1px) solid transparent; transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing), backdrop-filter var(--gw-motion-normal) var(--gw-motion-easing); } :host([floating]) .bar, :host([menu-open]) .bar { background: color-mix( in oklab, var(--gw-color-surface-default) 82%, transparent ); border-color: var(--gw-color-border-quiet); backdrop-filter: blur(12px) saturate(1.4); } /* A one-column grid, so a hidden brand can fold its width to nothing and the nav slides over as it opens, rather than leaving a gap */ .brand { display: grid; flex: none; grid-template-columns: 1fr; align-items: center; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .brand > slot { display: flex; align-items: center; min-width: 0; overflow: clip; overflow-clip-margin: calc( var(--gw-focus-width) + var(--gw-focus-offset) ); } :host([reveal-after]:not([floating])) .brand { grid-template-columns: 0fr; margin-inline-end: calc(var(--gw-space-3) * -1); pointer-events: none; visibility: hidden; opacity: 0; translate: 0 -0.25rem; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-slow) allow-discrete; } /* The nav keeps its room; the actions take the rest, which a theme picker fills or gives up by moving its controls into its popover */ .nav { display: flex; flex: none; gap: var(--gw-space-1); align-items: center; min-width: 0; } .actions { display: flex; flex: 1 1 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } .menu { display: none; place-items: center; width: 2.5rem; height: 2.5rem; padding: 0; color: var(--gw-color-text-strong); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } .menu:hover { background: var(--gw-color-fill-neutral-hovered); } /* Below menu-below, which sets collapsed, the nav folds into a menu, unless there's no nav to fold */ :host([collapsed]) .bar:not(.no-nav) .menu { display: inline-grid; } .no-nav .nav { display: none; } :host([collapsed]) .nav { position: absolute; inset-block-start: 100%; inset-inline: 0; flex-direction: column; gap: var(--gw-space-0-5); align-items: stretch; padding: var(--gw-space-2) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); visibility: hidden; background: var(--gw-color-surface-default); border-block-end: max(var(--gw-border-width), 1px) solid var(--gw-color-border-quiet); box-shadow: var(--gw-shadow-m); opacity: 0; translate: 0 -0.5rem; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-normal) allow-discrete; } :host([collapsed][menu-open]) .nav { visibility: visible; opacity: 1; translate: 0 0; } `, ]",
1544
1544
  "inheritedFrom": {
1545
1545
  "name": "GwElement",
1546
1546
  "module": "src/element.ts"
@@ -1630,6 +1630,12 @@
1630
1630
  "privacy": "private",
1631
1631
  "readonly": true
1632
1632
  },
1633
+ {
1634
+ "kind": "field",
1635
+ "name": "#navSlot",
1636
+ "privacy": "private",
1637
+ "readonly": true
1638
+ },
1633
1639
  {
1634
1640
  "kind": "field",
1635
1641
  "name": "#nav",
@@ -1639,6 +1645,12 @@
1639
1645
  },
1640
1646
  "readonly": true
1641
1647
  },
1648
+ {
1649
+ "kind": "field",
1650
+ "name": "#bar",
1651
+ "privacy": "private",
1652
+ "readonly": true
1653
+ },
1642
1654
  {
1643
1655
  "kind": "field",
1644
1656
  "name": "#observer",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.9.26",
3
+ "version": "0.9.27",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/gw-app-bar.ts CHANGED
@@ -27,6 +27,8 @@ const closeIcon =
27
27
  * page that opens with a hero. Below `menu-below`, 600px wide by default,
28
28
  * the nav moves into a menu opened from a button, which closes on Esc, a
29
29
  * click outside or a link. Raise it when the nav and actions need more room.
30
+ * With no nav links there's no menu button, so a bar of only a brand and
31
+ * actions keeps its layout at every width.
30
32
  *
31
33
  * @element gw-app-bar
32
34
  * @summary A header pinned to the top, floating once the page scrolls.
@@ -178,11 +180,16 @@ export class GwAppBar extends GwElement {
178
180
  background: var(--gw-color-fill-neutral-hovered);
179
181
  }
180
182
 
181
- /* Below menu-below, which sets collapsed, the nav folds into a menu */
182
- :host([collapsed]) .menu {
183
+ /* Below menu-below, which sets collapsed, the nav folds into a menu,
184
+ unless there's no nav to fold */
185
+ :host([collapsed]) .bar:not(.no-nav) .menu {
183
186
  display: inline-grid;
184
187
  }
185
188
 
189
+ .no-nav .nav {
190
+ display: none;
191
+ }
192
+
186
193
  :host([collapsed]) .nav {
187
194
  position: absolute;
188
195
  inset-block-start: 100%;
@@ -239,12 +246,16 @@ export class GwAppBar extends GwElement {
239
246
  this.#menuIcon,
240
247
  );
241
248
 
249
+ readonly #navSlot = h("slot");
250
+
242
251
  readonly #nav: HTMLDivElement = h(
243
252
  "div",
244
253
  { class: "nav", part: "nav" },
245
- h("slot"),
254
+ this.#navSlot,
246
255
  );
247
256
 
257
+ readonly #bar = h("div", { class: "bar no-nav", part: "bar" });
258
+
248
259
  #observer: IntersectionObserver | undefined;
249
260
 
250
261
  #frame = 0;
@@ -263,8 +274,10 @@ export class GwAppBar extends GwElement {
263
274
  /** Follows the viewport across `menuBelow`, setting `collapsed`. */
264
275
  #watchWidth() {
265
276
  this.#narrow?.removeEventListener("change", this.#onNarrow);
277
+ const below = Number(this.menuBelow);
278
+
266
279
  this.#narrow = window.matchMedia(
267
- `(max-width: ${(Number(this.menuBelow) || 600) - 0.02}px)`,
280
+ `(max-width: ${(Number.isFinite(below) ? below : 600) - 0.02}px)`,
268
281
  );
269
282
  this.#narrow.addEventListener("change", this.#onNarrow);
270
283
  this.#onNarrow();
@@ -370,25 +383,32 @@ export class GwAppBar extends GwElement {
370
383
  }
371
384
  });
372
385
 
373
- root.append(
386
+ // Without nav links there's nothing to fold, so no menu button
387
+ this.#navSlot.addEventListener("slotchange", () => {
388
+ this.#bar.classList.toggle(
389
+ "no-nav",
390
+ this.#navSlot
391
+ .assignedNodes()
392
+ .every(
393
+ (node) =>
394
+ node.nodeType === Node.TEXT_NODE &&
395
+ node.textContent?.trim() === "",
396
+ ),
397
+ );
398
+ });
399
+
400
+ this.#bar.append(
401
+ // The menu button leads, so a revealed brand opens beside it
402
+ this.#menu,
403
+ h("div", { class: "brand", part: "brand" }, h("slot", { name: "brand" })),
404
+ this.#nav,
374
405
  h(
375
406
  "div",
376
- { class: "bar", part: "bar" },
377
- // The menu button leads, so a revealed brand opens beside it
378
- this.#menu,
379
- h(
380
- "div",
381
- { class: "brand", part: "brand" },
382
- h("slot", { name: "brand" }),
383
- ),
384
- this.#nav,
385
- h(
386
- "div",
387
- { class: "actions", part: "actions" },
388
- h("slot", { name: "actions" }),
389
- ),
407
+ { class: "actions", part: "actions" },
408
+ h("slot", { name: "actions" }),
390
409
  ),
391
410
  );
411
+ root.append(this.#bar);
392
412
  }
393
413
 
394
414
  protected override update(changed: ReadonlySet<string>) {