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

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
@@ -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;
@@ -0,0 +1,241 @@
1
+ /* An image block at the top of a scrolling Pane or NavStack view. The root
2
+ paints fadeTo, so the image fades into it and the content below sits on the
3
+ same color. It bleeds to the Pane's resolved inline insets (the safe area)
4
+ and hands them back to its overlay and content, so their own items keep
5
+ the ordinary text axis. */
6
+ .kui-backdrop-image {
7
+ --_kui-backdrop-image-height: 20rem;
8
+ --_kui-backdrop-image-fade-to: transparent;
9
+ --_kui-backdrop-image-parallax: 0;
10
+ --_kui-backdrop-image-position: center;
11
+ --_kui-backdrop-image-fit: cover;
12
+ --_kui-backdrop-image-inset-start: var(
13
+ --kui-pane-content-inset-inline-start,
14
+ 0px
15
+ );
16
+ --_kui-backdrop-image-inset-end: var(
17
+ --kui-pane-content-inset-inline-end,
18
+ 0px
19
+ );
20
+ --_kui-backdrop-image-bleed-block-start: 0px;
21
+ display: flow-root;
22
+ position: relative;
23
+ min-width: 0;
24
+ margin-inline: calc(-1 * var(--_kui-backdrop-image-inset-start))
25
+ calc(-1 * var(--_kui-backdrop-image-inset-end));
26
+ background-color: var(--_kui-backdrop-image-fade-to);
27
+ isolation: isolate;
28
+ }
29
+
30
+ /* `underChrome`: the block runs up beneath the scroller's block-start inset
31
+ (overlay or clear chrome clearance plus the top safe area, as the Pane
32
+ publishes it) and the hero grows by the same amount, so the visible image
33
+ keeps its height below the chrome and the overlay stays where it was. */
34
+ .kui-backdrop-image[data-under-chrome="true"] {
35
+ --_kui-backdrop-image-bleed-block-start: var(
36
+ --kui-pane-content-inset-block-start,
37
+ 0px
38
+ );
39
+ margin-block-start: calc(-1 * var(--_kui-backdrop-image-bleed-block-start));
40
+ }
41
+
42
+ /* The view timeline's inset defaults to the scroller's scroll padding, which
43
+ is the chrome clearance this image now runs beneath; measured from there,
44
+ the parallax would start part-way through at rest and shift the image
45
+ down. Measure from the scroller's real edge instead. */
46
+ .kui-backdrop-image[data-under-chrome="true"] .kui-backdrop-image__hero {
47
+ view-timeline-inset: 0px;
48
+ }
49
+
50
+ /* The hero is the scroll-timeline subject: its own block of scroll drives
51
+ the parallax, whatever sits above it and however tall it is. */
52
+ .kui-backdrop-image__hero {
53
+ display: grid;
54
+ position: relative;
55
+ min-height: calc(
56
+ var(--_kui-backdrop-image-height) +
57
+ var(--_kui-backdrop-image-bleed-block-start)
58
+ );
59
+ grid-template: minmax(0, 1fr) / minmax(0, 1fr);
60
+ view-timeline: --kui-backdrop-image block;
61
+ }
62
+
63
+ .kui-backdrop-image__backdrop {
64
+ position: absolute;
65
+ inset: 0;
66
+ overflow: clip;
67
+ pointer-events: none;
68
+ }
69
+
70
+ /* The fade is a mask on the backdrop, not a painted gradient, so the image
71
+ dissolves into whatever fadeTo is without a visible band. */
72
+ .kui-backdrop-image[data-fade="true"] .kui-backdrop-image__backdrop {
73
+ mask-image: linear-gradient(to bottom, #000 50%, transparent);
74
+ }
75
+
76
+ /* The moving layer: everything that lags the scroll. */
77
+ .kui-backdrop-image__layers {
78
+ position: absolute;
79
+ inset: 0;
80
+ }
81
+
82
+ .kui-backdrop-image__media,
83
+ .kui-backdrop-image__blurred {
84
+ display: block;
85
+ width: 100%;
86
+ height: 100%;
87
+ object-fit: var(--_kui-backdrop-image-fit);
88
+ object-position: var(--_kui-backdrop-image-position);
89
+ background-position: var(--_kui-backdrop-image-position);
90
+ }
91
+
92
+ /* Progressive blur: a copy over the sharp image, blurred once and hidden
93
+ until the scroll timeline fades it in. A filtered copy overhangs the block
94
+ by its radius on every side, so the blur's transparent fringe falls outside
95
+ the clip instead of letting the sharp edge show through. The dim layer
96
+ darkens toward its peak opacity with it. */
97
+ .kui-backdrop-image__blurred {
98
+ position: absolute;
99
+ inset: 0;
100
+ opacity: 0;
101
+ }
102
+
103
+ .kui-backdrop-image[data-blur="filter"] .kui-backdrop-image__blurred {
104
+ inset: calc(-1 * var(--_kui-backdrop-image-blur)) auto auto
105
+ calc(-1 * var(--_kui-backdrop-image-blur));
106
+ width: calc(100% + 2 * var(--_kui-backdrop-image-blur));
107
+ height: calc(100% + 2 * var(--_kui-backdrop-image-blur));
108
+ filter: blur(var(--_kui-backdrop-image-blur));
109
+ }
110
+
111
+ .kui-backdrop-image__dim {
112
+ position: absolute;
113
+ inset: 0;
114
+ background: rgb(0 0 0 / var(--_kui-backdrop-image-dim));
115
+ opacity: 0;
116
+ }
117
+
118
+ .kui-backdrop-image__scrim {
119
+ position: absolute;
120
+ inset: 0;
121
+ }
122
+
123
+ .kui-backdrop-image[data-scrim="bottom"] .kui-backdrop-image__scrim {
124
+ background: linear-gradient(to bottom, rgb(0 0 0 / 0) 35%, rgb(0 0 0 / 0.5));
125
+ }
126
+
127
+ .kui-backdrop-image[data-scrim="full"] .kui-backdrop-image__scrim {
128
+ background: rgb(0 0 0 / 0.35);
129
+ }
130
+
131
+ .kui-backdrop-image__overlay {
132
+ position: relative;
133
+ min-width: 0;
134
+ align-self: end;
135
+ padding-inline: var(--_kui-backdrop-image-inset-start)
136
+ var(--_kui-backdrop-image-inset-end);
137
+ }
138
+
139
+ /* A scrim is dark in both color schemes, so the content laid over it takes
140
+ the dark scheme: default-tone titles and controls resolve their light
141
+ foregrounds whatever the page's scheme. */
142
+ .kui-backdrop-image:is([data-scrim="bottom"], [data-scrim="full"])
143
+ .kui-backdrop-image__overlay {
144
+ color-scheme: dark;
145
+ color: var(--kui-color-text);
146
+ }
147
+
148
+ .kui-backdrop-image__content {
149
+ min-width: 0;
150
+ padding-inline: var(--_kui-backdrop-image-inset-start)
151
+ var(--_kui-backdrop-image-inset-end);
152
+ }
153
+
154
+ /* Parallax: as the hero scrolls out past the scroller's start edge, the
155
+ layer slides down by the lag factor of that distance, so the image moves
156
+ slower than the content. The blurred copy and dim layer fade in over the
157
+ same range. Only transform and opacity animate, on a scroll-driven
158
+ timeline the compositor runs; the blur filter is never animated, and there
159
+ is no scroll listener and no background-attachment: fixed. Without
160
+ scroll-driven animations (Firefox today) or with reduced motion the image
161
+ stays static and sharp. */
162
+ @supports (animation-timeline: view()) {
163
+ @media (prefers-reduced-motion: no-preference) {
164
+ .kui-backdrop-image[data-parallax="scroll"] .kui-backdrop-image__layers {
165
+ animation: kui-backdrop-image-parallax linear both;
166
+ animation-timeline: --kui-backdrop-image;
167
+ animation-range: exit-crossing 0% exit-crossing 100%;
168
+ }
169
+
170
+ /* The blur reaches full strength halfway through the exit, while most
171
+ of the image is still in view; ramping over the whole exit left it
172
+ barely visible before the image had scrolled away. */
173
+ .kui-backdrop-image[data-blur]
174
+ :is(.kui-backdrop-image__blurred, .kui-backdrop-image__dim) {
175
+ animation: kui-backdrop-image-reveal linear both;
176
+ animation-timeline: --kui-backdrop-image;
177
+ animation-range: exit-crossing 0% exit-crossing 50%;
178
+ }
179
+ }
180
+ }
181
+
182
+ @keyframes kui-backdrop-image-parallax {
183
+ from {
184
+ transform: none;
185
+ }
186
+ to {
187
+ transform: translateY(calc(var(--_kui-backdrop-image-parallax) * 100%));
188
+ }
189
+ }
190
+
191
+ @keyframes kui-backdrop-image-reveal {
192
+ from {
193
+ opacity: 0;
194
+ }
195
+ to {
196
+ opacity: 1;
197
+ }
198
+ }
199
+
200
+ /* The document-wide static-effects switch and slow-update displays (e-ink,
201
+ print) get the same static, sharp image as reduced motion. */
202
+ :root[data-kui-effects="static"]
203
+ .kui-backdrop-image[data-parallax="scroll"]
204
+ .kui-backdrop-image__layers,
205
+ :root[data-kui-effects="static"]
206
+ .kui-backdrop-image[data-blur]
207
+ :is(.kui-backdrop-image__blurred, .kui-backdrop-image__dim) {
208
+ animation: none;
209
+ }
210
+
211
+ @media (update: slow) {
212
+ .kui-backdrop-image[data-parallax="scroll"] .kui-backdrop-image__layers,
213
+ .kui-backdrop-image[data-blur]
214
+ :is(.kui-backdrop-image__blurred, .kui-backdrop-image__dim) {
215
+ animation: none;
216
+ }
217
+ }
218
+
219
+ /* Reduced transparency or increased contrast: no dissolve. The image ends on
220
+ a hard edge over solid fadeTo, and overlay content sits on a solid fadeTo
221
+ band instead of the image, so text chosen for the fade color stays
222
+ legible. */
223
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
224
+ .kui-backdrop-image[data-fade="true"] .kui-backdrop-image__backdrop {
225
+ mask-image: none;
226
+ }
227
+
228
+ .kui-backdrop-image[data-fade="true"] .kui-backdrop-image__overlay {
229
+ background-color: var(--_kui-backdrop-image-fade-to);
230
+ }
231
+
232
+ /* Over that solid band the overlay keeps the page's scheme. */
233
+ .kui-backdrop-image[data-fade="true"]:is(
234
+ [data-scrim="bottom"],
235
+ [data-scrim="full"]
236
+ )
237
+ .kui-backdrop-image__overlay {
238
+ color-scheme: inherit;
239
+ color: inherit;
240
+ }
241
+ }
@@ -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
  }