@kerfjs/ui 5.0.0-beta.100 → 5.0.0-beta.102

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.
Files changed (148) hide show
  1. package/README.md +3 -1
  2. package/ai/component-catalog.json +214 -18
  3. package/ai/component-composition.json +216 -8
  4. package/ai/components/README.md +2 -0
  5. package/ai/components/backdrop-image.md +84 -0
  6. package/ai/components/nav-stack.md +6 -1
  7. package/ai/components/pane.md +11 -1
  8. package/ai/components/recipe-media-detail.md +57 -0
  9. package/ai/components/surface-scaffold.md +13 -6
  10. package/ai/components/tab-navigator.md +6 -1
  11. package/ai/components/toolbar-control-group.md +1 -0
  12. package/ai/public-api-signatures-v1.md +356 -32
  13. package/ai/skill.md +53 -5
  14. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  15. package/dist/backdrop-image.d.ts +132 -0
  16. package/dist/backdrop-image.js +5 -0
  17. package/dist/backdrop-image.js.map +1 -0
  18. package/dist/browser/backdrop-image.js +3 -0
  19. package/dist/catalog-resources.d.ts +2 -2
  20. package/dist/catalog.d.ts +2 -2
  21. package/dist/catalog.js +10 -9
  22. package/dist/catalog.js.map +1 -1
  23. package/dist/{chunk-QIG4IGUP.js → chunk-2PXZBFEC.js} +3 -1
  24. package/dist/chunk-2PXZBFEC.js.map +1 -0
  25. package/dist/{chunk-T4WJ5VGP.js → chunk-3SKAEPPZ.js} +2 -2
  26. package/dist/{chunk-T4WJ5VGP.js.map → chunk-3SKAEPPZ.js.map} +1 -1
  27. package/dist/chunk-5NQF35VO.js +67 -0
  28. package/dist/chunk-5NQF35VO.js.map +1 -0
  29. package/dist/{chunk-CMETHPKR.js → chunk-6E2H2E5A.js} +29 -8
  30. package/dist/chunk-6E2H2E5A.js.map +1 -0
  31. package/dist/{chunk-PIVY32AE.js → chunk-BYFTHA4I.js} +16 -3
  32. package/dist/chunk-BYFTHA4I.js.map +1 -0
  33. package/dist/{chunk-HGV7J4T6.js → chunk-EKHDLBRP.js} +1 -1
  34. package/dist/chunk-EKHDLBRP.js.map +1 -0
  35. package/dist/{chunk-QROYYGWU.js → chunk-FNZQOUZ5.js} +2 -2
  36. package/dist/{chunk-QROYYGWU.js.map → chunk-FNZQOUZ5.js.map} +1 -1
  37. package/dist/{chunk-EOO6PYGF.js → chunk-IXLUZHGT.js} +4 -4
  38. package/dist/{chunk-EOO6PYGF.js.map → chunk-IXLUZHGT.js.map} +1 -1
  39. package/dist/{chunk-UMKV7YVQ.js → chunk-LPHU44S3.js} +5 -1
  40. package/dist/chunk-LPHU44S3.js.map +1 -0
  41. package/dist/{chunk-INI4WDWC.js → chunk-M5KZ26VV.js} +27 -4
  42. package/dist/chunk-M5KZ26VV.js.map +1 -0
  43. package/dist/{chunk-TLMMFQWM.js → chunk-NS762OVM.js} +3 -3
  44. package/dist/{chunk-TLMMFQWM.js.map → chunk-NS762OVM.js.map} +1 -1
  45. package/dist/{chunk-CXZXHZTR.js → chunk-OZ6X76G5.js} +34 -1
  46. package/dist/chunk-OZ6X76G5.js.map +1 -0
  47. package/dist/{chunk-YOA5W5ZH.js → chunk-PKL6QJKK.js} +31 -3
  48. package/dist/chunk-PKL6QJKK.js.map +1 -0
  49. package/dist/chunk-QTSMHH5J.js +85 -0
  50. package/dist/chunk-QTSMHH5J.js.map +1 -0
  51. package/dist/chunk-SCDWURBO.js +136 -0
  52. package/dist/chunk-SCDWURBO.js.map +1 -0
  53. package/dist/{chunk-RHD7RKGI.js → chunk-SIW64V65.js} +3 -3
  54. package/dist/{chunk-RHD7RKGI.js.map → chunk-SIW64V65.js.map} +1 -1
  55. package/dist/chunk-SQ5DH66U.js +37 -0
  56. package/dist/chunk-SQ5DH66U.js.map +1 -0
  57. package/dist/{chunk-PYBBYSZ3.js → chunk-YBWQR2OK.js} +5 -5
  58. package/dist/{chunk-PYBBYSZ3.js.map → chunk-YBWQR2OK.js.map} +1 -1
  59. package/dist/{chunk-3GLYIIFR.js → chunk-ZYU5WRC3.js} +2 -2
  60. package/dist/{chunk-3GLYIIFR.js.map → chunk-ZYU5WRC3.js.map} +1 -1
  61. package/dist/collapsible-panel.js +3 -2
  62. package/dist/collapsible-panel.js.map +1 -1
  63. package/dist/index.d.ts +5 -3
  64. package/dist/index.js +24 -20
  65. package/dist/list-action-row.js +1 -1
  66. package/dist/list-header.js +1 -1
  67. package/dist/list-item.js +1 -1
  68. package/dist/{nav-stack-VXTumzKa.d.ts → nav-stack-CU_0dKtI.d.ts} +41 -5
  69. package/dist/nav-stack.d.ts +4 -4
  70. package/dist/nav-stack.js +2 -1
  71. package/dist/pane.d.ts +76 -4
  72. package/dist/pane.js +2 -1
  73. package/dist/select.js +1 -1
  74. package/dist/split-view.d.ts +4 -4
  75. package/dist/split-view.js +3 -2
  76. package/dist/split-view.js.map +1 -1
  77. package/dist/state-banner.js +1 -1
  78. package/dist/styles/backdrop-image.css +241 -0
  79. package/dist/styles/components/actions/toolbar/toolbar.css +9 -0
  80. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +128 -0
  81. package/dist/styles/components/layout/pane/pane.css +121 -1
  82. package/dist/styles/components/navigation/nav-stack/nav-stack.css +207 -6
  83. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +121 -3
  84. package/dist/styles/components/surfaces/backdrop-image/backdrop-image.css +241 -0
  85. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +166 -2
  86. package/dist/styles/foundation.css +165 -8
  87. package/dist/styles/nav-stack.css +207 -6
  88. package/dist/styles/overlay-sheet.css +211 -0
  89. package/dist/styles/pane.css +121 -1
  90. package/dist/styles/styles.css +1 -0
  91. package/dist/styles/surface-scaffold.css +166 -2
  92. package/dist/styles/tab-navigator.css +121 -3
  93. package/dist/styles/toolbar-control-group.css +128 -0
  94. package/dist/styles/toolbar.css +9 -0
  95. package/dist/styles/webawesome.css +10 -0
  96. package/dist/surface-scaffold-register.d.ts +3 -2
  97. package/dist/surface-scaffold-register.js +113 -10
  98. package/dist/surface-scaffold-register.js.map +1 -1
  99. package/dist/surface-scaffold.d.ts +38 -5
  100. package/dist/surface-scaffold.js +1 -1
  101. package/dist/tab-navigator.d.ts +43 -3
  102. package/dist/tab-navigator.js +35 -6
  103. package/dist/tab-navigator.js.map +1 -1
  104. package/dist/toolbar-control-group.d.ts +13 -2
  105. package/dist/toolbar-control-group.js +1 -1
  106. package/dist/toolbar-text.d.ts +6 -1
  107. package/dist/toolbar-text.js +1 -1
  108. package/dist/toolbar.d.ts +1 -0
  109. package/dist/wire-catalog.d.ts +2 -2
  110. package/dist/wire-catalog.js +1 -1
  111. package/dist/wire-nav-stack.js +29 -3
  112. package/dist/wire-nav-stack.js.map +1 -1
  113. package/dist/wire-overlay-sheets.d.ts +20 -0
  114. package/dist/wire-overlay-sheets.js +4 -0
  115. package/dist/wire-overlay-sheets.js.map +1 -0
  116. package/dist/wire-scroll-dividers.js +1 -1
  117. package/dist/wire-token-search-fields.js +1 -1
  118. package/dist/workbench.d.ts +2 -2
  119. package/dist/workbench.js +2 -1
  120. package/docs/accessibility.md +35 -0
  121. package/docs/app-layouts.md +13 -0
  122. package/docs/backdrop-image.md +128 -0
  123. package/docs/component-contract.md +2 -0
  124. package/docs/component-selection.md +11 -2
  125. package/docs/design/templates/toolbar-control-group/glass-dark.svg +1 -0
  126. package/docs/design/templates/toolbar-control-group/glass.svg +1 -0
  127. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  128. package/docs/design/templates/toolbar-control-group.svg +1 -1
  129. package/docs/design/templates.md +8 -0
  130. package/docs/layout.md +135 -5
  131. package/docs/manual-test-plan.md +29 -0
  132. package/docs/nav-stack.md +37 -1
  133. package/docs/recipes.md +24 -0
  134. package/docs/surface-scaffold.md +149 -3
  135. package/docs/tab-navigator.md +36 -0
  136. package/docs/ux-demo.md +11 -1
  137. package/llms.txt +42 -4
  138. package/package.json +13 -1
  139. package/ux-demo/recipes/loaders.ts +2 -0
  140. package/ux-demo/recipes/media-detail.tsx +272 -0
  141. package/dist/chunk-CMETHPKR.js.map +0 -1
  142. package/dist/chunk-CXZXHZTR.js.map +0 -1
  143. package/dist/chunk-HGV7J4T6.js.map +0 -1
  144. package/dist/chunk-INI4WDWC.js.map +0 -1
  145. package/dist/chunk-PIVY32AE.js.map +0 -1
  146. package/dist/chunk-QIG4IGUP.js.map +0 -1
  147. package/dist/chunk-UMKV7YVQ.js.map +0 -1
  148. package/dist/chunk-YOA5W5ZH.js.map +0 -1
@@ -61,6 +61,32 @@
61
61
  background: var(--kui-pane-chrome-background, var(--kui-color-surface));
62
62
  }
63
63
 
64
+ /* `background`: an app-chosen color paints the whole pane, header and footer
65
+ included, and replaces the appearance's fill, so a sunken pane's content
66
+ and auto chrome turn transparent over it. Forced colors paint the system
67
+ surface instead. */
68
+ .kui-pane[data-background] {
69
+ background: var(--_kui-pane-background);
70
+ }
71
+
72
+ .kui-pane[data-background][data-appearance="sunken"] > .kui-pane__content,
73
+ .kui-pane[data-background][data-appearance="sunken"][data-chrome-placement="auto"]
74
+ > :is(.kui-pane__header, .kui-pane__footer) {
75
+ background: transparent;
76
+ }
77
+
78
+ /* `tint`: the pane paints its re-derived surface, under a sunken pane's
79
+ lowered layer, unless an explicit `background` already paints it. */
80
+ .kui-pane[data-kui-tint]:not([data-background]) {
81
+ background: var(--kui-color-surface);
82
+ }
83
+
84
+ @media (forced-colors: active) {
85
+ .kui-pane[data-background] {
86
+ background: Canvas;
87
+ }
88
+ }
89
+
64
90
  /* Safe areas. The pane root never pads, so its background and separators paint
65
91
  through an unsafe area; its slots compensate on the edges the pane still
66
92
  touches. The inherited `--kui-edge-inset-*` context says how far each edge
@@ -158,6 +184,9 @@
158
184
  pane edge while keeping their own content aligned with ordinary items. */
159
185
  --kui-pane-content-inset-inline-start: var(--_kui-pane-safe-inline-start);
160
186
  --kui-pane-content-inset-inline-end: var(--_kui-pane-safe-inline-end);
187
+ /* The resolved block-start inset (safe area, deep inset, and the clearance
188
+ of overlay or clear chrome), so a first child can run up beneath it. */
189
+ --kui-pane-content-inset-block-start: var(--_kui-pane-content-block-start);
161
190
  --_kui-pane-deep-block-inset: 0px;
162
191
  --_kui-pane-clearance-block-start: 0px;
163
192
  --_kui-pane-clearance-block-end: 0px;
@@ -263,6 +292,97 @@
263
292
  }
264
293
  }
265
294
 
295
+ /* `headerPresentation="clear"`: an overlay header (the same measuring,
296
+ placement, and clearance as above) that is fully transparent, for chrome
297
+ laid over imagery. Legibility comes from glass control groups or a scrim
298
+ in the content, not from the header. With `scrollEdge="material"` a
299
+ pseudo-element carrying the chrome material fades in (opacity only, never
300
+ the filter) while `wireScrollDividers` reports content scrolled beneath
301
+ the header, and out at rest; the clear header draws no scroll divider. */
302
+ .kui-pane[data-header-material="clear"] > .kui-pane__header {
303
+ -webkit-backdrop-filter: none;
304
+ backdrop-filter: none;
305
+ background: transparent;
306
+ }
307
+
308
+ :where(.kui-pane[data-header-material="clear"][data-scroll-edge])
309
+ > .kui-pane__header {
310
+ position: relative;
311
+ isolation: isolate;
312
+ }
313
+
314
+ /* The scroll edge fades in as content scrolls beneath the bar: the wiring
315
+ publishes `--kui-scroll-edge-progress` (0 at rest, 1 once content has
316
+ scrolled `scrollEdgeDistance` past the edge) and only the layer's opacity
317
+ follows it; the filter never animates. Unwired, it stays clear. */
318
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
319
+ > .kui-pane__header::before {
320
+ position: absolute;
321
+ z-index: -1;
322
+ content: "";
323
+ inset: 0;
324
+ opacity: var(--kui-scroll-edge-progress, 0);
325
+ pointer-events: none;
326
+ }
327
+
328
+ .kui-pane[data-header-material="clear"][data-scroll-edge="material"]
329
+ > .kui-pane__header::before {
330
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
331
+ backdrop-filter: var(--kui-chrome-material-filter);
332
+ background: var(--kui-chrome-material);
333
+ }
334
+
335
+ .kui-pane[data-header-material="clear"][data-scroll-edge="surface"]
336
+ > .kui-pane__header::before {
337
+ background: var(--kui-color-surface);
338
+ }
339
+
340
+ /* Static effects and slow displays snap the edge on once content lies
341
+ beneath the bar instead of tracking the scroll. */
342
+ :root[data-kui-effects="static"]
343
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
344
+ > .kui-pane__header::before {
345
+ opacity: 0;
346
+ }
347
+
348
+ :root[data-kui-effects="static"]
349
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
350
+ > .kui-pane__header[data-scroll-divider*="b"]::before {
351
+ opacity: 1;
352
+ }
353
+
354
+ @media (update: slow) {
355
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
356
+ > .kui-pane__header::before {
357
+ opacity: 0;
358
+ }
359
+
360
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
361
+ > .kui-pane__header[data-scroll-divider*="b"]::before {
362
+ opacity: 1;
363
+ }
364
+ }
365
+
366
+ /* Clear chrome turns opaque where transparency would hurt, like the
367
+ material itself. */
368
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
369
+ .kui-pane[data-header-material="clear"] > .kui-pane__header {
370
+ background: var(--kui-chrome-material);
371
+ }
372
+ }
373
+
374
+ @media (forced-colors: active) {
375
+ .kui-pane[data-header-material="clear"] > .kui-pane__header {
376
+ background: Canvas;
377
+ border-block-end: 1px solid CanvasText;
378
+ }
379
+
380
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
381
+ > .kui-pane__header::before {
382
+ content: none;
383
+ }
384
+ }
385
+
266
386
  /* Add one outer gutter without changing the inherited item-margin token.
267
387
  Nested Row, List, and Grid therefore keep their own default 8px insets. */
268
388
  .kui-pane[data-deep-inset="true"] > .kui-pane__content {
@@ -313,7 +433,7 @@
313
433
  box-shadow: inset 0 1px var(--_kui-pane-chrome-divider);
314
434
  }
315
435
 
316
- .kui-pane:not([data-chrome-dividers])
436
+ .kui-pane:not([data-chrome-dividers], [data-header-material="clear"])
317
437
  > :is(.kui-pane__header, .kui-pane__toolbar)[data-scroll-divider*="b"],
318
438
  .kui-pane:not([data-chrome-dividers])
319
439
  > .kui-pane__footer[data-scroll-divider*="t"],
@@ -33,6 +33,7 @@
33
33
  @import "./corner-status.css";
34
34
  @import "./column-track.css";
35
35
  @import "./content-swap.css";
36
+ @import "./backdrop-image.css";
36
37
  @import "./file-picker.css";
37
38
  @import "./combobox.css";
38
39
  @import "./edge-activity-indicator.css";
@@ -5,6 +5,21 @@
5
5
  var(--_kui-dialog-surface-gutter, 1rem)
6
6
  );
7
7
  --width: min(35rem, calc(100vw - 2rem));
8
+ /* Concentric with a pill ToolbarControlGroup at the body inset: 22px + 8px
9
+ compact, 22px + 16px comfortable. */
10
+ --_kui-dialog-surface-radius: calc(
11
+ var(--kui-layout-pill-radius, calc(1px + 1.3125rem)) +
12
+ var(--kui-wa-surface-inset, 0.5rem)
13
+ );
14
+ }
15
+ .kui-dialog-surface[data-body-inset="comfortable"] > wa-dialog {
16
+ --_kui-dialog-surface-radius: calc(
17
+ var(--kui-layout-pill-radius, calc(1px + 1.3125rem)) +
18
+ var(--kui-wa-container-inset, 1rem)
19
+ );
20
+ }
21
+ .kui-dialog-surface > wa-dialog::part(dialog) {
22
+ border-radius: var(--_kui-dialog-surface-radius);
8
23
  }
9
24
  .kui-dialog-surface[data-size="small"] > wa-dialog {
10
25
  --_kui-dialog-surface-width: 25rem;
@@ -281,7 +296,8 @@
281
296
  height: 100dvh;
282
297
  max-height: 100dvh;
283
298
  margin-inline-end: 0;
284
- border-radius: var(--kui-radius-l) 0 0 var(--kui-radius-l);
299
+ border-radius: var(--_kui-dialog-surface-radius) 0 0
300
+ var(--_kui-dialog-surface-radius);
285
301
  }
286
302
  .kui-dialog-surface[data-presentation="fullscreen"] > wa-dialog {
287
303
  --width: 100vw;
@@ -293,6 +309,149 @@
293
309
  max-height: 100dvh;
294
310
  border-radius: 0;
295
311
  }
312
+ /* A bottom sheet rests on the viewport's block-end edge: square bottom
313
+ corners, the dialog's own radius on top. Its panel runs to the edge, behind
314
+ the home indicator and any virtual keyboard, while a block-end padding
315
+ (the larger of the safe area, the keyboard inset the register measures
316
+ from `visualViewport`, and the VirtualKeyboard API's
317
+ `env(keyboard-inset-height)`) keeps its content above them. `height`,
318
+ `minHeight` and `maxHeight` measure that visible content height; the panel
319
+ is capped at the dynamic viewport minus the top safe area and one gutter. */
320
+ .kui-dialog-surface[data-presentation="bottom-sheet"]
321
+ > wa-dialog::part(dialog) {
322
+ --_kui-dialog-surface-sheet-clearance: max(
323
+ var(--kui-safe-area-block-end, env(safe-area-inset-bottom, 0px)),
324
+ var(--_kui-dialog-surface-keyboard-inset, 0px),
325
+ env(keyboard-inset-height, 0px)
326
+ );
327
+ --_kui-dialog-surface-sheet-cap: max(
328
+ 0px,
329
+ min(
330
+ calc(
331
+ var(--_kui-dialog-surface-max-height, 100dvh) +
332
+ var(--_kui-dialog-surface-sheet-clearance)
333
+ ),
334
+ calc(
335
+ 100dvh -
336
+ var(--kui-safe-area-block-start, env(safe-area-inset-top, 0px)) -
337
+ var(--_kui-dialog-surface-resolved-gutter)
338
+ )
339
+ )
340
+ );
341
+ box-sizing: border-box;
342
+ width: min(
343
+ var(
344
+ --_kui-dialog-surface-preferred-width,
345
+ var(--_kui-dialog-surface-width)
346
+ ),
347
+ 100%
348
+ );
349
+ max-width: 100%;
350
+ /* Unset (content height) until the register publishes a resized height. */
351
+ height: calc(
352
+ var(--_kui-dialog-surface-sheet-height) +
353
+ var(--_kui-dialog-surface-sheet-clearance)
354
+ );
355
+ max-height: var(--_kui-dialog-surface-sheet-cap);
356
+ /* A modal dialog sits between both block insets, so an auto height would
357
+ stretch to the cap; anchored only at the bottom, a sheet with no
358
+ `height` (and no resized height) opens at its content height. */
359
+ inset-block-start: auto;
360
+ margin: auto auto 0;
361
+ padding-block-end: var(--_kui-dialog-surface-sheet-clearance);
362
+ border-radius: var(--_kui-dialog-surface-radius)
363
+ var(--_kui-dialog-surface-radius) 0 0;
364
+ }
365
+ .kui-dialog-surface[data-presentation="bottom-sheet"][data-height]
366
+ > wa-dialog::part(dialog) {
367
+ height: calc(
368
+ var(
369
+ --_kui-dialog-surface-sheet-height,
370
+ var(--_kui-dialog-surface-height, 100dvh)
371
+ ) +
372
+ var(--_kui-dialog-surface-sheet-clearance)
373
+ );
374
+ }
375
+ /* A resizable sheet keeps a band at its top edge for the grabber the
376
+ register injects: a `role="separator"` that resizes the visible height
377
+ between `minHeight` (default 160px) and the cap by drag or keyboard. */
378
+ .kui-dialog-surface[data-presentation="bottom-sheet"][data-resizable]
379
+ > wa-dialog::part(dialog) {
380
+ min-height: min(
381
+ calc(
382
+ var(--_kui-dialog-surface-min-height, 10rem) +
383
+ var(--_kui-dialog-surface-sheet-clearance)
384
+ ),
385
+ var(--_kui-dialog-surface-sheet-cap)
386
+ );
387
+ padding-block-start: 0.75rem;
388
+ }
389
+ .kui-dialog-surface[data-presentation="bottom-sheet"][data-resizable]
390
+ > wa-dialog::part(sheet-handle) {
391
+ position: absolute;
392
+ z-index: 2;
393
+ inset-block-start: 0;
394
+ inset-inline: 0;
395
+ width: 6rem;
396
+ height: 1.25rem;
397
+ margin-inline: auto;
398
+ border-radius: var(--kui-radius-s);
399
+ cursor: ns-resize;
400
+ touch-action: none;
401
+ }
402
+ .kui-dialog-surface[data-presentation="bottom-sheet"][data-resizable]
403
+ > wa-dialog::part(sheet-handle)::before {
404
+ position: absolute;
405
+ inset-block-start: 0.375rem;
406
+ inset-inline: 0;
407
+ width: 2.25rem;
408
+ height: 0.3125rem;
409
+ margin-inline: auto;
410
+ border-radius: 0.15625rem;
411
+ background: var(--kui-color-neutral-border-loud);
412
+ content: "";
413
+ }
414
+ .kui-dialog-surface[data-presentation="bottom-sheet"][data-resizable]
415
+ > wa-dialog::part(sheet-handle):focus-visible {
416
+ outline: var(--kui-focus-ring);
417
+ outline-offset: -3px;
418
+ }
419
+ @media (forced-colors: active) {
420
+ .kui-dialog-surface[data-presentation="bottom-sheet"][data-resizable]
421
+ > wa-dialog::part(sheet-handle)::before {
422
+ background: CanvasText;
423
+ }
424
+ }
425
+ /* The sheet slides up from, and back down to, the bottom edge. Web Awesome
426
+ runs its show, hide and pulse animations through one slot, so the register
427
+ marks the host only while a show (`enter`) or hide (`exit`) runs, and only
428
+ those keyframes change; the hide plays them in reverse. Reduced motion and
429
+ the static effects mode keep Web Awesome's own animation. */
430
+ @keyframes kui-dialog-surface-sheet-slide {
431
+ from {
432
+ translate: 0 100%;
433
+ }
434
+ to {
435
+ translate: 0 0;
436
+ }
437
+ }
438
+ @media (prefers-reduced-motion: no-preference) {
439
+ :root:not([data-kui-effects="static"])
440
+ .kui-dialog-surface[data-presentation="bottom-sheet"]:not(
441
+ [data-enter-motion="standard"]
442
+ )
443
+ > wa-dialog[data-kui-sheet-motion="enter"]::part(dialog),
444
+ :root:not([data-kui-effects="static"])
445
+ .kui-dialog-surface[data-presentation="bottom-sheet"]:not(
446
+ [data-exit-motion="standard"]
447
+ )
448
+ > wa-dialog[data-kui-sheet-motion="exit"]::part(dialog) {
449
+ animation-name: kui-dialog-surface-sheet-slide;
450
+ /* A full-height travel needs longer than the dialog's 150ms scale. */
451
+ animation-duration: var(--wa-transition-slow, 300ms);
452
+ animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
453
+ }
454
+ }
296
455
  /* PopoverDialogSurface: the dialog chrome and bounded geometry for a native
297
456
  popover="auto" surface (non-modal, light dismiss) or the same surface in
298
457
  flow. The UA popover rules supply the centered top-layer placement. */
@@ -325,7 +484,12 @@
325
484
  overflow: auto;
326
485
  color: var(--kui-color-text);
327
486
  border: 1px solid var(--kui-color-neutral-border-normal);
328
- border-radius: var(--kui-radius-l);
487
+ /* Matches the modal DialogSurface corner: a pill ToolbarControlGroup at the
488
+ 8px surface inset (22px + 8px). */
489
+ border-radius: calc(
490
+ var(--kui-layout-pill-radius, calc(1px + 1.3125rem)) +
491
+ var(--kui-wa-surface-inset, 0.5rem)
492
+ );
329
493
  background: var(--kui-color-surface-raised);
330
494
  box-shadow: var(--kui-shadow-l);
331
495
  }
@@ -5,7 +5,7 @@
5
5
  height: 100%;
6
6
  overflow: hidden;
7
7
  flex-direction: column;
8
- background: var(--kui-color-surface);
8
+ background: var(--_kui-tab-scaffold-background, var(--kui-color-surface));
9
9
  /* Safe areas: the scenes and the bar paint through unsafe areas; the bar
10
10
  compensates for the bottom and inline edges, and the scenes for the top
11
11
  and inline edges (see pane.css for the `--kui-edge-inset-*` context). */
@@ -55,6 +55,31 @@
55
55
  background: var(--kui-sunken-panel-background, var(--kui-sunken-surface));
56
56
  }
57
57
 
58
+ /* `background`: the navigator's color paints the root and a docked bar (the
59
+ overlay bar keeps the chrome material); a tab's own color paints its scene
60
+ and replaces its appearance's fill. A nested navigator never inherits the
61
+ color. Forced colors paint the system surface. */
62
+ .kui-tab-scaffold:not([data-background]) {
63
+ --_kui-tab-scaffold-background: initial;
64
+ }
65
+
66
+ .kui-tab-scaffold__scene[data-kui-tint]:not([data-background]) {
67
+ background: var(--kui-color-surface);
68
+ }
69
+
70
+ .kui-tab-scaffold__scene[data-background] {
71
+ background: var(--_kui-tab-scaffold-scene-background);
72
+ }
73
+
74
+ @media (forced-colors: active) {
75
+ .kui-tab-scaffold[data-background],
76
+ .kui-tab-scaffold[data-background]:not([data-bar-presentation="overlay"])
77
+ > .kui-tab-scaffold__bar,
78
+ .kui-tab-scaffold__scene[data-background] {
79
+ background: Canvas;
80
+ }
81
+ }
82
+
58
83
  /* A scene is its region's scroll owner, so its top inset is padding inside
59
84
  the scroller. A scene whose only child is a Pane or layout (usually a
60
85
  NavStack) lets that child own the insets instead. */
@@ -138,7 +163,7 @@
138
163
  scroll divider. Changing its color never moves the bar or a tab. */
139
164
  border-block-start: 1px solid
140
165
  var(--_kui-tab-scaffold-bar-divider, transparent);
141
- background: var(--kui-color-surface);
166
+ background: var(--_kui-tab-scaffold-background, var(--kui-color-surface));
142
167
  }
143
168
 
144
169
  .kui-tab-scaffold[data-separator="auto"]:has(
@@ -161,7 +186,10 @@
161
186
  > [data-component="pane"][data-appearance="sunken"]
162
187
  )
163
188
  > .kui-tab-scaffold__bar,
164
- .kui-tab-scaffold[data-separator="auto"]:not([data-chrome-dividers])
189
+ .kui-tab-scaffold[data-separator="auto"]:not(
190
+ [data-chrome-dividers],
191
+ [data-bar-material="clear"]
192
+ )
165
193
  > .kui-tab-scaffold__bar[data-scroll-divider*="t"],
166
194
  .kui-tab-scaffold[data-separator="auto"][data-chrome-dividers="always"]
167
195
  > .kui-tab-scaffold__bar {
@@ -213,6 +241,96 @@
213
241
  }
214
242
  }
215
243
 
244
+ /* `barPresentation="clear"`: an overlay bar (the same measuring and edge
245
+ inset as above) that is fully transparent, for a bar over imagery. With
246
+ `scrollEdge="material"` a pseudo-element carrying the chrome material
247
+ fades in (opacity only) while the active scene continues beneath the bar,
248
+ and out once it is scrolled to its end. A clear bar draws no scroll
249
+ divider. */
250
+ .kui-tab-scaffold[data-bar-material="clear"] > .kui-tab-scaffold__bar {
251
+ -webkit-backdrop-filter: none;
252
+ backdrop-filter: none;
253
+ background: transparent;
254
+ }
255
+
256
+ :where(.kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge])
257
+ > .kui-tab-scaffold__bar {
258
+ position: relative;
259
+ isolation: isolate;
260
+ }
261
+
262
+ /* The scroll edge fades in as content scrolls beneath the bar: the wiring
263
+ publishes `--kui-scroll-edge-progress` (0 at rest, 1 once content lies
264
+ `scrollEdgeDistance` beyond the bar) and only the layer's opacity follows
265
+ it; the filter never animates. Unwired, it stays clear. */
266
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge]
267
+ > .kui-tab-scaffold__bar::before {
268
+ position: absolute;
269
+ z-index: -1;
270
+ content: "";
271
+ inset: 0;
272
+ opacity: var(--kui-scroll-edge-progress, 0);
273
+ pointer-events: none;
274
+ }
275
+
276
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge="material"]
277
+ > .kui-tab-scaffold__bar::before {
278
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
279
+ backdrop-filter: var(--kui-chrome-material-filter);
280
+ background: var(--kui-chrome-material);
281
+ }
282
+
283
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge="surface"]
284
+ > .kui-tab-scaffold__bar::before {
285
+ background: var(--kui-color-surface);
286
+ }
287
+
288
+ /* Static effects and slow displays snap the edge on while content lies
289
+ beneath the bar instead of tracking the scroll. */
290
+ :root[data-kui-effects="static"]
291
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge]
292
+ > .kui-tab-scaffold__bar::before {
293
+ opacity: 0;
294
+ }
295
+
296
+ :root[data-kui-effects="static"]
297
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge]
298
+ > .kui-tab-scaffold__bar[data-scroll-divider*="t"]::before {
299
+ opacity: 1;
300
+ }
301
+
302
+ @media (update: slow) {
303
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge]
304
+ > .kui-tab-scaffold__bar::before {
305
+ opacity: 0;
306
+ }
307
+
308
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge]
309
+ > .kui-tab-scaffold__bar[data-scroll-divider*="t"]::before {
310
+ opacity: 1;
311
+ }
312
+ }
313
+
314
+ /* Clear chrome turns opaque where transparency would hurt, like the
315
+ material itself. */
316
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
317
+ .kui-tab-scaffold[data-bar-material="clear"] > .kui-tab-scaffold__bar {
318
+ background: var(--kui-chrome-material);
319
+ }
320
+ }
321
+
322
+ @media (forced-colors: active) {
323
+ .kui-tab-scaffold[data-bar-material="clear"] > .kui-tab-scaffold__bar {
324
+ background: Canvas;
325
+ border-block-start-color: CanvasText;
326
+ }
327
+
328
+ .kui-tab-scaffold[data-bar-material="clear"][data-scroll-edge]
329
+ > .kui-tab-scaffold__bar::before {
330
+ content: none;
331
+ }
332
+ }
333
+
216
334
  .kui-tab-scaffold__tab {
217
335
  display: flex;
218
336
  flex: 1 1 0;
@@ -420,6 +420,134 @@
420
420
  --kui-toolbar-control-color: var(--kui-color-text);
421
421
  }
422
422
 
423
+ /* `material="glass"`: the group floats over imagery or a clear toolbar on the
424
+ translucent chrome material (foundation.css), blurred and saturated, the
425
+ per-control glass pills of overlay chrome. The fill and filter come from the
426
+ shared tokens, so the group inherits every opaque fallback they carry
427
+ (reduced transparency, increased contrast, static effects, no
428
+ backdrop-filter). The group's own 1px border becomes the specular edge, a
429
+ translucent white highlight, instead of a gray border plus a highlight; a
430
+ soft two-layer shadow lifts it off the content beneath.
431
+
432
+ Controls take the normal text color: over its own 80% surface material the
433
+ quiet control color falls below AA over the worst-case backdrop, while the
434
+ text color stays well above it (about 10:1 light over black, 8:1 dark over
435
+ white). Hover mixes a text-color tint into the material itself, so a lone
436
+ control's whole-group hover keeps a fill rather than replacing it with a
437
+ bare tint. A raised or outline selection paints an opaque surface pill, so
438
+ the brand selected color keeps AA over any content. A borderless group has
439
+ no surface and ignores the material. */
440
+ .kui-toolbar-control-group[data-material="glass"]:not(
441
+ [data-appearance="borderless"]
442
+ ) {
443
+ --kui-toolbar-control-border: light-dark(
444
+ rgb(255 255 255 / 0.72),
445
+ rgb(255 255 255 / 0.16)
446
+ );
447
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
448
+ backdrop-filter: var(--kui-chrome-material-filter);
449
+ box-shadow:
450
+ 0 0.0625rem 0.1875rem light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.32)),
451
+ 0 0.375rem 1.125rem light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.28));
452
+ }
453
+
454
+ .kui-toolbar-control-group[data-material="glass"]:not(
455
+ [data-appearance="borderless"],
456
+ [data-tone="dark"],
457
+ [data-scrim="true"]
458
+ ) {
459
+ --kui-toolbar-control-background: var(--kui-chrome-material);
460
+ --kui-toolbar-control-color: var(--kui-color-text);
461
+ --kui-toolbar-control-hover-background: color-mix(
462
+ in srgb,
463
+ var(--kui-color-text) 12%,
464
+ var(--kui-chrome-material)
465
+ );
466
+ }
467
+
468
+ /* A dark glass group keeps its loud translucent fill and on-loud colors; its
469
+ edge is a faint white rim in either scheme. */
470
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
471
+ [data-appearance="borderless"]
472
+ ) {
473
+ --kui-toolbar-control-border: rgb(255 255 255 / 0.18);
474
+ }
475
+
476
+ .kui-toolbar-control-group[data-material="glass"][data-selected-chrome="outline"] {
477
+ --kui-toolbar-control-selected-background: var(--kui-color-surface);
478
+ }
479
+
480
+ /* Wherever the chrome material turns opaque, the glass edge returns to the
481
+ group's normal border and a dark glass group to its opaque loud fill. The
482
+ default glass fill already follows the material tokens. */
483
+ @supports not (
484
+ (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
485
+ ) {
486
+ .kui-toolbar-control-group[data-material="glass"]:not(
487
+ [data-appearance="borderless"]
488
+ ) {
489
+ --kui-toolbar-control-border: var(--kui-color-neutral-border-normal);
490
+ }
491
+
492
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
493
+ [data-appearance="borderless"]
494
+ ) {
495
+ --kui-toolbar-control-background: var(--kui-color-neutral-fill-loud);
496
+ --kui-toolbar-control-border: var(
497
+ --kui-toolbar-control-dark-border,
498
+ #353536
499
+ );
500
+ }
501
+ }
502
+
503
+ @media (prefers-reduced-transparency: reduce),
504
+ (prefers-contrast: more),
505
+ (update: slow) {
506
+ .kui-toolbar-control-group[data-material="glass"]:not(
507
+ [data-appearance="borderless"]
508
+ ) {
509
+ --kui-toolbar-control-border: var(--kui-color-neutral-border-normal);
510
+ }
511
+
512
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
513
+ [data-appearance="borderless"]
514
+ ) {
515
+ --kui-toolbar-control-background: var(--kui-color-neutral-fill-loud);
516
+ --kui-toolbar-control-border: var(
517
+ --kui-toolbar-control-dark-border,
518
+ #353536
519
+ );
520
+ }
521
+ }
522
+
523
+ :root[data-kui-effects="static"]
524
+ .kui-toolbar-control-group[data-material="glass"]:not(
525
+ [data-appearance="borderless"]
526
+ ) {
527
+ --kui-toolbar-control-border: var(--kui-color-neutral-border-normal);
528
+ }
529
+
530
+ :root[data-kui-effects="static"]
531
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
532
+ [data-appearance="borderless"]
533
+ ) {
534
+ --kui-toolbar-control-background: var(--kui-color-neutral-fill-loud);
535
+ --kui-toolbar-control-border: var(--kui-toolbar-control-dark-border, #353536);
536
+ }
537
+
538
+ @media (forced-colors: active) {
539
+ .kui-toolbar-control-group[data-material="glass"]:not(
540
+ [data-appearance="borderless"]
541
+ ) {
542
+ -webkit-backdrop-filter: none;
543
+ backdrop-filter: none;
544
+ border-color: CanvasText;
545
+ color: CanvasText;
546
+ background-color: Canvas;
547
+ box-shadow: none;
548
+ }
549
+ }
550
+
423
551
  /* `selectedChrome="none"`: a pressed control paints like its unpressed
424
552
  neighbors, for an icon-state toggle whose glyph already shows the state.
425
553
  One button opts out alone with `data-selected-chrome="none"`. After the
@@ -217,6 +217,15 @@
217
217
  overflow-clip-margin: 4px;
218
218
  }
219
219
 
220
+ /* A glass control group floats on a soft shadow that reaches well past the
221
+ ring margin (about 24px below and 18px to the side). While the zone holds
222
+ one it stops clipping, so the shadow is never cut off flat: a margin would
223
+ not do, since WebKit has no `overflow-clip-margin`. A title beside it still
224
+ truncates itself with its own ellipsis. */
225
+ .kui-toolbar__leading:has(> .kui-toolbar-control-group[data-material="glass"]) {
226
+ overflow: visible;
227
+ }
228
+
220
229
  .kui-toolbar__center {
221
230
  /* The center content never compresses below its own width (a group cannot
222
231
  shrink or clip its controls). The track above takes this as its minimum, so
@@ -477,6 +477,16 @@
477
477
  padding: var(--kui-wa-surface-inset);
478
478
  }
479
479
 
480
+ /* The panel corner is concentric with a pill ToolbarControlGroup resting at
481
+ the surface inset (22px pill + 8px inset = 30px), so header and body
482
+ controls nest into the corner instead of crowding a tighter curve. */
483
+ wa-dialog::part(dialog) {
484
+ border-radius: calc(
485
+ var(--kui-layout-pill-radius, calc(1px + 1.3125rem)) +
486
+ var(--kui-wa-surface-inset, 0.5rem)
487
+ );
488
+ }
489
+
480
490
  wa-dialog::part(footer) {
481
491
  padding: var(--kui-wa-container-inset);
482
492
  display: flex;
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Importing this module registers wa-dialog, repairs wrapped native names, and
3
3
  * measures each DialogSurface header and footer for a floating header's or
4
- * footer's body clearance, and shows a floating header's divider while
5
- * content is scrolled beneath it.
4
+ * footer's body clearance, shows a floating header's divider while content
5
+ * is scrolled beneath it, and wires a bottom sheet's resize grabber,
6
+ * virtual-keyboard clearance, and slide motion.
6
7
  */
7
8
  declare const surfaceScaffoldElementsRegistered = true;
8
9