@kerfjs/ui 5.0.0-beta.110 → 5.0.0-beta.112

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 (211) hide show
  1. package/README.md +3 -0
  2. package/ai/component-catalog.json +161 -8
  3. package/ai/component-composition.json +119 -2
  4. package/ai/components/README.md +2 -0
  5. package/ai/components/backdrop-image.md +1 -0
  6. package/ai/components/button.md +5 -2
  7. package/ai/components/content-item.md +1 -0
  8. package/ai/components/editor-frame.md +80 -0
  9. package/ai/components/summary-row.md +5 -2
  10. package/ai/components/surface-scaffold.md +3 -0
  11. package/ai/components/value-preview.md +75 -0
  12. package/ai/public-api-signatures-v1.md +207 -50
  13. package/ai/skill.md +17 -0
  14. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  15. package/dist/backdrop-image.d.ts +11 -0
  16. package/dist/backdrop-image.js +1 -1
  17. package/dist/browser/content-item.js +1 -0
  18. package/dist/browser/editor-frame.js +3 -0
  19. package/dist/browser/value-preview.js +3 -0
  20. package/dist/button.d.ts +4 -2
  21. package/dist/button.js +1 -1
  22. package/dist/catalog-resources.d.ts +2 -1
  23. package/dist/catalog.d.ts +2 -1
  24. package/dist/catalog.js +6 -4
  25. package/dist/catalog.js.map +1 -1
  26. package/dist/{chunk-JCCN3U6X.js → chunk-2J7XKAZX.js} +3 -2
  27. package/dist/chunk-2J7XKAZX.js.map +1 -0
  28. package/dist/chunk-3KFMLOTH.js +201 -0
  29. package/dist/chunk-3KFMLOTH.js.map +1 -0
  30. package/dist/{chunk-ATGAJVZ6.js → chunk-3OP66VXU.js} +64 -4
  31. package/dist/chunk-3OP66VXU.js.map +1 -0
  32. package/dist/{chunk-ZNROKDSZ.js → chunk-3V4OCDTL.js} +2 -8
  33. package/dist/chunk-3V4OCDTL.js.map +1 -0
  34. package/dist/{chunk-3GLYIIFR.js → chunk-3WWX2O4G.js} +3 -2
  35. package/dist/chunk-3WWX2O4G.js.map +1 -0
  36. package/dist/{chunk-SCDWURBO.js → chunk-5CLMV5YU.js} +21 -3
  37. package/dist/chunk-5CLMV5YU.js.map +1 -0
  38. package/dist/{chunk-P7EJ4CAD.js → chunk-ETFP7HAR.js} +2 -2
  39. package/dist/{chunk-P7EJ4CAD.js.map → chunk-ETFP7HAR.js.map} +1 -1
  40. package/dist/chunk-F653O57X.js +41 -0
  41. package/dist/chunk-F653O57X.js.map +1 -0
  42. package/dist/{chunk-SOFPUW3O.js → chunk-IIQ2XIMU.js} +4 -3
  43. package/dist/chunk-IIQ2XIMU.js.map +1 -0
  44. package/dist/chunk-IRC5N7FA.js +16 -0
  45. package/dist/chunk-IRC5N7FA.js.map +1 -0
  46. package/dist/{chunk-SQ5DH66U.js → chunk-ITENVZ6Q.js} +2 -15
  47. package/dist/chunk-ITENVZ6Q.js.map +1 -0
  48. package/dist/{chunk-4UB5Z3ZZ.js → chunk-K67XBCHG.js} +8 -4
  49. package/dist/chunk-K67XBCHG.js.map +1 -0
  50. package/dist/chunk-OK7FUK7U.js +9 -0
  51. package/dist/chunk-OK7FUK7U.js.map +1 -0
  52. package/dist/{chunk-WSEJ2FDE.js → chunk-OUCHU7JB.js} +2 -2
  53. package/dist/{chunk-WSEJ2FDE.js.map → chunk-OUCHU7JB.js.map} +1 -1
  54. package/dist/{chunk-KPINJHIC.js → chunk-PS24PVR6.js} +3 -1
  55. package/dist/chunk-PS24PVR6.js.map +1 -0
  56. package/dist/{chunk-6CDILXF4.js → chunk-S3DCRI2M.js} +31 -5
  57. package/dist/chunk-S3DCRI2M.js.map +1 -0
  58. package/dist/chunk-V6N3YLTM.js +56 -0
  59. package/dist/chunk-V6N3YLTM.js.map +1 -0
  60. package/dist/{chunk-HQZG3OFL.js → chunk-XRZSK2WC.js} +3 -2
  61. package/dist/chunk-XRZSK2WC.js.map +1 -0
  62. package/dist/{chunk-CPSGUGAY.js → chunk-YPAQFBMN.js} +4 -4
  63. package/dist/{chunk-CPSGUGAY.js.map → chunk-YPAQFBMN.js.map} +1 -1
  64. package/dist/collapsible-panel.d.ts +1 -0
  65. package/dist/collapsible-panel.js +3 -2
  66. package/dist/collapsible-panel.js.map +1 -1
  67. package/dist/content-item.d.ts +46 -2
  68. package/dist/content-item.js +4 -1
  69. package/dist/editor-frame.d.ts +41 -0
  70. package/dist/editor-frame.js +3 -0
  71. package/dist/editor-frame.js.map +1 -0
  72. package/dist/index.d.ts +5 -2
  73. package/dist/index.js +27 -23
  74. package/dist/list-action-row.js +2 -1
  75. package/dist/list-detail-dialog.d.ts +2 -1
  76. package/dist/list-detail-dialog.js +5 -4
  77. package/dist/list-detail-dialog.js.map +1 -1
  78. package/dist/list-item.js +2 -1
  79. package/dist/{nav-stack-CU_0dKtI.d.ts → nav-stack-CrZF95VO.d.ts} +2 -1
  80. package/dist/nav-stack.d.ts +2 -1
  81. package/dist/nav-stack.js +2 -1
  82. package/dist/pane.d.ts +3 -23
  83. package/dist/pane.js +2 -1
  84. package/dist/split-view.d.ts +2 -1
  85. package/dist/split-view.js +2 -1
  86. package/dist/styles/backdrop-image.css +24 -1
  87. package/dist/styles/components/actions/popup-menu/popup-menu.css +1 -1
  88. package/dist/styles/components/collections/summary-row/summary-row.css +7 -3
  89. package/dist/styles/components/data-display/value-preview/value-preview.css +18 -0
  90. package/dist/styles/components/surfaces/backdrop-image/backdrop-image.css +24 -1
  91. package/dist/styles/components/surfaces/content-item/content-item.css +52 -0
  92. package/dist/styles/components/surfaces/editor-frame/editor-frame.css +121 -0
  93. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +25 -1
  94. package/dist/styles/content-item.css +3 -168
  95. package/dist/styles/editor-frame.css +121 -0
  96. package/dist/styles/foundation.css +9 -4
  97. package/dist/styles/popup-menu.css +1 -1
  98. package/dist/styles/styles.css +2 -0
  99. package/dist/styles/summary-row.css +7 -3
  100. package/dist/styles/surface-scaffold.css +25 -1
  101. package/dist/styles/value-preview.css +18 -0
  102. package/dist/styles/webawesome.css +2 -2
  103. package/dist/summary-row.d.ts +20 -11
  104. package/dist/summary-row.js +2 -1
  105. package/dist/surface-scaffold-register.js +1 -1
  106. package/dist/surface-scaffold.d.ts +30 -2
  107. package/dist/surface-scaffold.js +2 -2
  108. package/dist/surface-tint-DQgYCaP9.d.ts +25 -0
  109. package/dist/tab-navigator.d.ts +2 -1
  110. package/dist/tab-navigator.js +2 -1
  111. package/dist/tab-navigator.js.map +1 -1
  112. package/dist/toolbar.d.ts +1 -0
  113. package/dist/value-preview.d.ts +21 -0
  114. package/dist/value-preview.js +3 -0
  115. package/dist/value-preview.js.map +1 -0
  116. package/dist/wire-catalog.d.ts +2 -1
  117. package/dist/wire-catalog.js +1 -1
  118. package/dist/wire-content-items.js +88 -0
  119. package/dist/wire-content-items.js.map +1 -1
  120. package/dist/workbench-dialog.d.ts +2 -1
  121. package/dist/workbench-dialog.js +4 -3
  122. package/dist/workbench-dialog.js.map +1 -1
  123. package/dist/workbench.d.ts +2 -1
  124. package/dist/workbench.js +2 -1
  125. package/docs/accessibility.md +6 -0
  126. package/docs/backdrop-image.md +4 -0
  127. package/docs/button.md +15 -0
  128. package/docs/component-contract.md +34 -4
  129. package/docs/component-selection.md +16 -1
  130. package/docs/design/templates/button/emphasis-dark.svg +1 -1
  131. package/docs/design/templates/button-dark.svg +1 -1
  132. package/docs/design/templates/checkbox/error-dark.svg +1 -1
  133. package/docs/design/templates/checkbox/states.svg +1 -1
  134. package/docs/design/templates/checkbox-dark.svg +1 -1
  135. package/docs/design/templates/checkbox-group/horizontal-dark.svg +1 -1
  136. package/docs/design/templates/checkbox-group/vertical.svg +1 -1
  137. package/docs/design/templates/checkbox-group-dark.svg +1 -1
  138. package/docs/design/templates/checkbox-group.svg +1 -1
  139. package/docs/design/templates/checkbox.svg +1 -1
  140. package/docs/design/templates/choice-chips/color-palette-dark.svg +1 -1
  141. package/docs/design/templates/choice-chips-dark.svg +1 -1
  142. package/docs/design/templates/color-field/error-dark.svg +1 -1
  143. package/docs/design/templates/color-field/hint.svg +1 -1
  144. package/docs/design/templates/color-field-dark.svg +1 -1
  145. package/docs/design/templates/color-field.svg +1 -1
  146. package/docs/design/templates/color-picker/gray.svg +1 -1
  147. package/docs/design/templates/color-picker/hsb.svg +1 -1
  148. package/docs/design/templates/color-picker/rgb.svg +1 -1
  149. package/docs/design/templates/color-picker.svg +1 -1
  150. package/docs/design/templates/color-swatches/palette-dark.svg +1 -1
  151. package/docs/design/templates/color-swatches-dark.svg +1 -1
  152. package/docs/design/templates/disclosure/embedded.svg +1 -1
  153. package/docs/design/templates/disclosure/framed-sunken.svg +1 -1
  154. package/docs/design/templates/disclosure/plain.svg +1 -1
  155. package/docs/design/templates/disclosure.svg +1 -1
  156. package/docs/design/templates/message-composer/chat.svg +1 -1
  157. package/docs/design/templates/message-composer.svg +1 -1
  158. package/docs/design/templates/progress-bar/determinate.svg +1 -1
  159. package/docs/design/templates/progress-bar/tones.svg +1 -1
  160. package/docs/design/templates/progress-bar.svg +1 -1
  161. package/docs/design/templates/radio-group/horizontal-dark.svg +1 -1
  162. package/docs/design/templates/radio-group/vertical.svg +1 -1
  163. package/docs/design/templates/radio-group-dark.svg +1 -1
  164. package/docs/design/templates/radio-group.svg +1 -1
  165. package/docs/design/templates/search-field/empty.svg +1 -1
  166. package/docs/design/templates/search-field/tokens.svg +1 -1
  167. package/docs/design/templates/search-field.svg +1 -1
  168. package/docs/design/templates/select/form.svg +1 -1
  169. package/docs/design/templates/select.svg +1 -1
  170. package/docs/design/templates/slider/compact.svg +1 -1
  171. package/docs/design/templates/slider/readout.svg +1 -1
  172. package/docs/design/templates/slider/ticks.svg +1 -1
  173. package/docs/design/templates/slider.svg +1 -1
  174. package/docs/design/templates/switch/states.svg +1 -1
  175. package/docs/design/templates/switch.svg +1 -1
  176. package/docs/design/templates/tag-field/error-dark.svg +1 -1
  177. package/docs/design/templates/tag-field/error.svg +1 -1
  178. package/docs/design/templates/tag-field/tags.svg +1 -1
  179. package/docs/design/templates/tag-field-dark.svg +1 -1
  180. package/docs/design/templates/tag-field.svg +1 -1
  181. package/docs/design/templates/text-field/error-dark.svg +1 -1
  182. package/docs/design/templates/text-field/labeled.svg +1 -1
  183. package/docs/design/templates/text-field/multiline.svg +1 -1
  184. package/docs/design/templates/text-field/search.svg +1 -1
  185. package/docs/design/templates/text-field-dark.svg +1 -1
  186. package/docs/design/templates/text-field.svg +1 -1
  187. package/docs/design/templates/toolbar-control-group/icon-tile-danger-dark.svg +1 -1
  188. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  189. package/docs/design/templates.md +3 -1
  190. package/docs/editor-frame.md +47 -0
  191. package/docs/investigations/content-item-media-scroll-performance.md +26 -0
  192. package/docs/layout.md +34 -0
  193. package/docs/manual-test-plan.md +27 -1
  194. package/docs/recipes.md +9 -0
  195. package/docs/surface-scaffold.md +50 -1
  196. package/docs/value-preview.md +16 -0
  197. package/llms.txt +10 -1
  198. package/package.json +13 -1
  199. package/dist/chunk-3GLYIIFR.js.map +0 -1
  200. package/dist/chunk-4UB5Z3ZZ.js.map +0 -1
  201. package/dist/chunk-5USKCWGL.js +0 -113
  202. package/dist/chunk-5USKCWGL.js.map +0 -1
  203. package/dist/chunk-6CDILXF4.js.map +0 -1
  204. package/dist/chunk-ATGAJVZ6.js.map +0 -1
  205. package/dist/chunk-HQZG3OFL.js.map +0 -1
  206. package/dist/chunk-JCCN3U6X.js.map +0 -1
  207. package/dist/chunk-KPINJHIC.js.map +0 -1
  208. package/dist/chunk-SCDWURBO.js.map +0 -1
  209. package/dist/chunk-SOFPUW3O.js.map +0 -1
  210. package/dist/chunk-SQ5DH66U.js.map +0 -1
  211. package/dist/chunk-ZNROKDSZ.js.map +0 -1
@@ -223,7 +223,7 @@
223
223
  --kui-color-text: var(--wa-color-text-normal, light-dark(#1d1d1f, #f5f5f7));
224
224
  --kui-color-text-quiet: var(
225
225
  --wa-color-text-quiet,
226
- light-dark(#6e6e73, #aeaeb2)
226
+ light-dark(#646469, #aeaeb2)
227
227
  );
228
228
  --kui-color-text-link: var(
229
229
  --wa-color-text-link,
@@ -525,7 +525,7 @@
525
525
  );
526
526
  --kui-color-danger-on-quiet: var(
527
527
  --wa-color-danger-on-quiet,
528
- light-dark(#c20b20, #ff696c)
528
+ light-dark(#c20b20, #ff8588)
529
529
  );
530
530
  --kui-color-danger-on-normal: var(
531
531
  --wa-color-danger-on-normal,
@@ -538,8 +538,8 @@
538
538
  /* Danger text placed ON a danger fill (e.g. the danger StateBanner) and the
539
539
  danger Badge's solid fill. The light value matches danger-on-quiet, which
540
540
  already clears WCAG AA (5.7:1) over danger-fill-quiet. The dark value is
541
- lighter than danger-on-quiet (#ff696c), which reached only 5.7:1 over the
542
- dark fill-quiet (4.0:1 as dimmed banner detail) and left the solid badge's
541
+ lighter than danger-on-quiet (#ff8588); the former #ff696c reached only
542
+ 5.7:1 over the dark fill-quiet (4.0:1 as dimmed banner detail) and left the solid badge's
543
543
  dark text and placeholder skeleton the weakest of any tone on a mid-red
544
544
  fill; #ffa0a2 matches the other tones' pale dark on-fill values. */
545
545
  --kui-color-danger-on-fill: light-dark(#c20b20, #ffa0a2);
@@ -661,6 +661,11 @@
661
661
  --wa-color-brand-on-quiet: oklch(
662
662
  from var(--_kui-tint-brand-source) min(l, 0.45) c h
663
663
  );
664
+ /* The global on-fill blue is too light over saturated light tints when
665
+ the app does not supply an accent. Keep compact info badges at AA. */
666
+ --kui-color-brand-on-fill: oklch(
667
+ from var(--_kui-tint-brand-source) min(l, 0.3) c h
668
+ );
664
669
  }
665
670
 
666
671
  [data-kui-tint="dark"] {
@@ -402,7 +402,7 @@
402
402
  > .kui-popup-menu {
403
403
  margin: -2px;
404
404
  }
405
- .kui-toolbar-control-group:is([data-single="true"], [data-content="mixed"])
405
+ .kui-toolbar-control-group[data-content="mixed"]
406
406
  > .kui-popup-menu
407
407
  > .kui-popup-menu__trigger {
408
408
  min-width: max-content;
@@ -56,3 +56,5 @@
56
56
  @import "./summary-row.css";
57
57
  @import "./empty-state.css";
58
58
  @import "./value-table.css";
59
+ @import "./value-preview.css";
60
+ @import "./editor-frame.css";
@@ -1,4 +1,4 @@
1
- /* A selectable summary: a full-width option on the base surface, a container
1
+ /* A summary surface: a full-width option or static group on the base surface, a container
2
2
  for its own compaction, with a hairline divider below each list row. */
3
3
  .kui-summary-row {
4
4
  --_kui-summary-row-rail: transparent;
@@ -18,7 +18,7 @@
18
18
  border-radius: 0;
19
19
  color: var(--kui-color-text);
20
20
  background: var(--kui-color-surface);
21
- cursor: pointer;
21
+ cursor: default;
22
22
  outline: none;
23
23
  container-type: inline-size;
24
24
  user-select: none;
@@ -47,7 +47,11 @@
47
47
  contain-intrinsic-size: auto 4.5rem;
48
48
  }
49
49
 
50
- .kui-summary-row:hover {
50
+ .kui-summary-row[role="option"] {
51
+ cursor: pointer;
52
+ }
53
+
54
+ .kui-summary-row[role="option"]:hover {
51
55
  border-color: var(--kui-color-brand-border-normal);
52
56
  }
53
57
 
@@ -52,6 +52,22 @@
52
52
  light-dark(rgb(29 29 31 / 0.34), rgb(0 0 0 / 0.6))
53
53
  );
54
54
  }
55
+ .kui-dialog-surface[data-stack-active="false"] {
56
+ pointer-events: none;
57
+ transform: translateY(var(--_kui-dialog-surface-stack-offset, 0px));
58
+ }
59
+ .kui-dialog-surface[data-stack-position] {
60
+ --_kui-dialog-surface-gutter: 1.5rem;
61
+ }
62
+ .kui-dialog-surface[data-stack-active="false"]::backdrop {
63
+ background: transparent;
64
+ }
65
+ @media screen and (max-width: 48rem) {
66
+ .kui-dialog-surface[data-stack-position] {
67
+ --_kui-dialog-surface-gutter: 1rem;
68
+ transform: none;
69
+ }
70
+ }
55
71
  /* Room for phones whose viewport height ignores the browser chrome. */
56
72
  @media screen and (max-width: 26.25rem) {
57
73
  .kui-dialog-surface {
@@ -173,15 +189,23 @@ h2.kui-dialog-surface__title {
173
189
  background: transparent;
174
190
  cursor: pointer;
175
191
  }
192
+ .kui-dialog-surface__close[data-emphasis="quiet"] {
193
+ color: var(--kui-color-text-quiet);
194
+ }
176
195
  .kui-dialog-surface__close:hover {
177
196
  background-color: var(--kui-color-neutral-fill-normal);
178
197
  }
198
+ .kui-dialog-surface__close[data-emphasis="quiet"]:is(:hover, :focus-visible) {
199
+ color: var(--kui-color-neutral-on-quiet);
200
+ }
179
201
  .kui-dialog-surface__close:focus-visible {
180
202
  outline: var(--kui-focus-ring);
181
203
  outline-offset: var(--kui-focus-ring-offset, 1px);
182
204
  }
183
205
  @media (forced-colors: active) {
184
- .kui-dialog-surface__close {
206
+ .kui-dialog-surface__close,
207
+ .kui-dialog-surface__close[data-emphasis="quiet"]:is(:hover, :focus-visible),
208
+ .kui-dialog-surface__close[data-emphasis="quiet"] {
185
209
  color: ButtonText;
186
210
  }
187
211
  }
@@ -0,0 +1,18 @@
1
+ .kui-value-preview {
2
+ display: block;
3
+ box-sizing: border-box;
4
+ min-width: 0;
5
+ min-height: var(--_kui-value-preview-min-height, 3rem);
6
+ max-height: var(--_kui-value-preview-max-height, 6rem);
7
+ overflow: auto;
8
+ color: var(--kui-color-text);
9
+ font-size: var(--kui-font-xs);
10
+ line-height: 1.4;
11
+ white-space: pre-wrap;
12
+ overflow-wrap: anywhere;
13
+ }
14
+
15
+ .kui-value-preview:focus-visible {
16
+ outline: var(--kui-focus-ring);
17
+ outline-offset: -2px;
18
+ }
@@ -38,7 +38,7 @@
38
38
  );
39
39
 
40
40
  --wa-color-text-normal: light-dark(#1d1d1f, #f5f5f7);
41
- --wa-color-text-quiet: light-dark(#6e6e73, #aeaeb2);
41
+ --wa-color-text-quiet: light-dark(#646469, #aeaeb2);
42
42
  --wa-color-text-link: var(--wa-color-brand-on-quiet);
43
43
 
44
44
  --wa-color-overlay-modal: light-dark(
@@ -203,7 +203,7 @@
203
203
  var(--wa-color-danger-fill-loud) 78%,
204
204
  var(--wa-color-neutral-on-normal)
205
205
  );
206
- --wa-color-danger-on-quiet: light-dark(#c20b20, #ff696c);
206
+ --wa-color-danger-on-quiet: light-dark(#c20b20, #ff8588);
207
207
  --wa-color-danger-on-normal: light-dark(#8e0717, #ff8a8c);
208
208
  --wa-color-danger-on-loud: light-dark(#fff, #111113);
209
209
 
@@ -14,7 +14,7 @@ type SummaryRowRootAttributes = Readonly<Record<`data-${string}`, string | undef
14
14
  'data-selected'?: never;
15
15
  'data-dimmed'?: never;
16
16
  }>;
17
- interface SummaryRowProps {
17
+ interface SummaryRowBaseProps {
18
18
  /** The identity line: an identifier, inline status glyphs, and the title. */
19
19
  title: KerfUiContent;
20
20
  /** Optional leading glyph, such as a category icon. */
@@ -29,7 +29,7 @@ interface SummaryRowProps {
29
29
  titleLines?: number;
30
30
  /** A leading state rail in a semantic tone (Up Next, blocked, …). */
31
31
  rail?: SemanticTone;
32
- /** Selected state, exposed as `aria-selected`. */
32
+ /** Selected visual state; also exposed as `aria-selected` for an option. */
33
33
  selected?: boolean;
34
34
  /** Dim a finished or cut item; hover and selection restore it. */
35
35
  dimmed?: boolean;
@@ -37,20 +37,29 @@ interface SummaryRowProps {
37
37
  listEdge?: SummaryRowListEdge;
38
38
  /** Skip rendering while off screen in long lists (`content-visibility`). */
39
39
  deferRendering?: boolean;
40
- /** Accessible name when the visible content is not a good one. */
41
- ariaLabel?: string;
42
40
  id?: string;
43
41
  className?: string;
44
- /** Safe `data-*` identity (an item id or delegated action) on the option. */
42
+ /** Safe `data-*` identity (an item id or delegated action) on the root. */
45
43
  rootAttributes?: SummaryRowRootAttributes;
46
44
  }
45
+ type SummaryRowProps = SummaryRowBaseProps & ({
46
+ /** Selectable option, the default; place it in a labeled listbox. */
47
+ semanticRole?: 'option';
48
+ /** Native HTML drag source on this option root, in row or card form. */
49
+ draggable?: boolean;
50
+ /** Accessible name when the visible content is not a good one. */
51
+ ariaLabel?: string;
52
+ } | {
53
+ /** Static labeled group; nested actions own focus and activation. */
54
+ semanticRole: 'group';
55
+ draggable?: never;
56
+ ariaLabel: string;
57
+ });
47
58
  /**
48
- * A selectable, keyboard-focusable summary of one item, as a list row or a
49
- * board card: an optional leading glyph and state rail, a clamped identity line
50
- * with trailing quiet metadata, and a wrapping metadata line. It is a
51
- * `role="option"`; place rows in a labeled `role="listbox"` and own selection
52
- * and keyboard movement in the application.
59
+ * A list row or board card with a clamped identity line, metadata, and state
60
+ * rail. The default is a focusable `role="option"` for a labeled listbox;
61
+ * `semanticRole="group"` gives a static labeled row for nested actions.
53
62
  */
54
- declare function SummaryRow({ title, leading, trailingMeta, metadata, presentation, titleLines, rail, selected, dimmed, listEdge, deferRendering, ariaLabel, id, className, rootAttributes, }: SummaryRowProps): SafeHtml;
63
+ declare function SummaryRow({ title, leading, trailingMeta, metadata, presentation, titleLines, rail, selected, dimmed, listEdge, deferRendering, semanticRole, draggable, ariaLabel, id, className, rootAttributes, }: SummaryRowProps): SafeHtml;
55
64
 
56
65
  export { SummaryRow, type SummaryRowListEdge, type SummaryRowPresentation, type SummaryRowProps };
@@ -1,4 +1,5 @@
1
- export { SummaryRow } from './chunk-4UB5Z3ZZ.js';
1
+ export { SummaryRow } from './chunk-K67XBCHG.js';
2
+
2
3
 
3
4
 
4
5
  //# sourceMappingURL=summary-row.js.map
@@ -1,4 +1,4 @@
1
- import { installDialogSurfaces } from './chunk-ATGAJVZ6.js';
1
+ import { installDialogSurfaces } from './chunk-3OP66VXU.js';
2
2
 
3
3
 
4
4
  // src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
@@ -22,6 +22,15 @@ declare function openDialog(dialog: HTMLDialogElement): void;
22
22
  * `close` event fires once it has closed.
23
23
  */
24
24
  declare function closeDialog(dialog: HTMLDialogElement): void;
25
+ /**
26
+ * Reconcile one controlled stack after rendering its DialogSurface layers.
27
+ * Opening back-to-front establishes native top-layer order; unchanged layers
28
+ * keep their DOM nodes and focus. The application owns `stack.open` and handles
29
+ * `kui-request-close` to update that state before calling this again.
30
+ */
31
+ declare function syncDialogStack(root: ParentNode): void;
32
+ /** Follow a controlled stack's render updates until the returned stop runs. */
33
+ declare function wireDialogStack(root: ParentNode): () => void;
25
34
 
26
35
  type DialogSurfaceSize = 'small' | 'medium' | 'large';
27
36
  /**
@@ -62,6 +71,8 @@ type DialogSurfaceFooterPresentation = 'docked' | 'floating';
62
71
  * material, so content scrolls visibly beneath the title bar.
63
72
  */
64
73
  type DialogSurfaceHeaderPresentation = 'docked' | 'floating';
74
+ /** The built-in close control's visual weight; `normal` keeps the default. */
75
+ type DialogSurfaceCloseEmphasis = 'normal' | 'quiet';
65
76
  /**
66
77
  * Application `data-*` identity on the DialogSurface root, such as
67
78
  * `data-key`; the surface's own configuration attributes stay protected.
@@ -79,6 +90,19 @@ type DialogSurfaceName = {
79
90
  labelledBy: string;
80
91
  };
81
92
  type DialogSurfaceProps = DialogSurfaceName & DialogSurfaceOptions;
93
+ /** Controlled state of one native modal layer inside a `wireDialogStack` root. */
94
+ interface DialogSurfaceStack {
95
+ /** Whether this layer belongs in the native top layer. */
96
+ open: boolean;
97
+ /** The one frontmost open layer; it owns modal focus and accessibility. */
98
+ active: boolean;
99
+ /** One-based order from the back to the front. */
100
+ position: number;
101
+ /** Number of layers in the stack, including closed layers still rendered. */
102
+ count: number;
103
+ /** Optional vertical offset for an inactive layer; omitted on phones. */
104
+ offset?: CssLength;
105
+ }
82
106
  interface DialogSurfaceOptions {
83
107
  /** The dialog's id, for `openDialog(document.getElementById(id))`. */
84
108
  id?: string;
@@ -98,6 +122,8 @@ interface DialogSurfaceOptions {
98
122
  alert?: boolean;
99
123
  /** The body content. */
100
124
  children: KerfUiContent;
125
+ /** Controlled modal stack state; call `wireDialogStack` on the containing root. */
126
+ stack?: DialogSurfaceStack;
101
127
  /**
102
128
  * Header title content in place of the `label` text, such as an xl
103
129
  * `ToolbarText` with a description. The dialog keeps its `label` (or
@@ -117,6 +143,8 @@ interface DialogSurfaceOptions {
117
143
  lightDismiss?: boolean;
118
144
  /** The close button's accessible name (default `Close`). */
119
145
  closeLabel?: string;
146
+ /** Soften the built-in close control without changing its 40px target. */
147
+ closeEmphasis?: DialogSurfaceCloseEmphasis;
120
148
  size?: DialogSurfaceSize;
121
149
  presentation?: DialogSurfacePresentation;
122
150
  /** Modal preferred width. Takes precedence over size, within the viewport cap. */
@@ -210,7 +238,7 @@ interface DialogSurfaceOptions {
210
238
  * floating header and footer measurement, the bottom sheet's keyboard
211
239
  * clearance and grabber, and re-render resilience while open.
212
240
  */
213
- declare function DialogSurface({ id, label, labelledBy, describedBy, alert, heading, headerActions, footer, withoutHeader, lightDismiss, closeLabel, children, size, presentation, preferredWidth, bodyInset, footerInset, viewportGutter, maxHeight, height, minHeight, resizable, enterMotion, exitMotion, bodyLayout, headerPresentation, footerPresentation, footerOffset, rootAttributes, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
241
+ declare function DialogSurface({ id, label, labelledBy, describedBy, alert, heading, headerActions, footer, withoutHeader, lightDismiss, closeLabel, closeEmphasis, children, stack, size, presentation, preferredWidth, bodyInset, footerInset, viewportGutter, maxHeight, height, minHeight, resizable, enterMotion, exitMotion, bodyLayout, headerPresentation, footerPresentation, footerOffset, rootAttributes, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
214
242
  /** Where a PopoverDialogSurface appears: the top layer, or in flow. */
215
243
  type PopoverDialogSurfacePresentation = 'overlay' | 'embedded';
216
244
  /** Application `data-*` metadata on the surface root. */
@@ -277,4 +305,4 @@ interface PopupSurfaceProps {
277
305
  /** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
278
306
  declare function PopupSurface({ children, inset, className, slot, }: PopupSurfaceProps): kerfjs.SafeHtml;
279
307
 
280
- export { type DialogCloseSource, type DialogRequestCloseDetail, DialogSurface, type DialogSurfaceBodyLayout, type DialogSurfaceFooterPresentation, type DialogSurfaceHeaderPresentation, type DialogSurfaceHeight, type DialogSurfaceMaxHeight, type DialogSurfaceName, type DialogSurfaceOptions, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceRootAttributes, type DialogSurfaceSheetMotion, type DialogSurfaceSize, PopoverDialogSurface, type PopoverDialogSurfaceDataAttributes, type PopoverDialogSurfacePresentation, type PopoverDialogSurfaceProps, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset, closeDialog, openDialog };
308
+ export { type DialogCloseSource, type DialogRequestCloseDetail, DialogSurface, type DialogSurfaceBodyLayout, type DialogSurfaceCloseEmphasis, type DialogSurfaceFooterPresentation, type DialogSurfaceHeaderPresentation, type DialogSurfaceHeight, type DialogSurfaceMaxHeight, type DialogSurfaceName, type DialogSurfaceOptions, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceRootAttributes, type DialogSurfaceSheetMotion, type DialogSurfaceSize, type DialogSurfaceStack, PopoverDialogSurface, type PopoverDialogSurfaceDataAttributes, type PopoverDialogSurfacePresentation, type PopoverDialogSurfaceProps, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset, closeDialog, openDialog, syncDialogStack, wireDialogStack };
@@ -1,5 +1,5 @@
1
- export { DialogSurface, PopoverDialogSurface, PopupSurface } from './chunk-6CDILXF4.js';
2
- export { closeDialog, openDialog } from './chunk-ATGAJVZ6.js';
1
+ export { DialogSurface, PopoverDialogSurface, PopupSurface } from './chunk-S3DCRI2M.js';
2
+ export { closeDialog, openDialog, syncDialogStack, wireDialogStack } from './chunk-3OP66VXU.js';
3
3
 
4
4
 
5
5
 
@@ -0,0 +1,25 @@
1
+ import { CssColor } from './css-values.js';
2
+
3
+ /** The color scheme an app declares for a {@link SurfaceTint}'s color. */
4
+ type SurfaceTintScheme = 'light' | 'dark';
5
+ /**
6
+ * A scoped tint for a layout surface: the app's color (usually sampled from
7
+ * artwork it already knows) and the scheme that color belongs to. Kerf does no
8
+ * color detection; `scheme` says whether the color reads as a light or a dark
9
+ * surface. Inside the scope the surface, raised surface, text, quiet text, and
10
+ * everything built on them (quiet fills, separators' backdrop, the chrome
11
+ * material) re-derive from the color, with lightness clamped so text keeps
12
+ * WCAG AA over any color.
13
+ */
14
+ interface SurfaceTint {
15
+ color: CssColor;
16
+ scheme: SurfaceTintScheme;
17
+ /**
18
+ * An optional accent (usually the artwork's most vivid color). The scope's
19
+ * brand ramp re-derives from it: loud fills and their text, brand text and
20
+ * links, text on brand fills, and the focus ring, each clamped to keep AA.
21
+ */
22
+ accent?: CssColor;
23
+ }
24
+
25
+ export type { SurfaceTint as S, SurfaceTintScheme as a };
@@ -1,7 +1,8 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
- import { PaneAppearance, SurfaceTint, ScrollEdge } from './pane.js';
3
+ import { PaneAppearance, ScrollEdge } from './pane.js';
4
4
  import { CssColor } from './css-values.js';
5
+ import { S as SurfaceTint } from './surface-tint-DQgYCaP9.js';
5
6
  import './scroll-clearance-BikHljTc.js';
6
7
 
7
8
  interface TabNavigatorTabBase<Id extends string> {
@@ -1,5 +1,6 @@
1
- import { surfaceTintAttributes, joinStyles, scrollEdgeAttributes } from './chunk-SQ5DH66U.js';
1
+ import { scrollEdgeAttributes } from './chunk-IRC5N7FA.js';
2
2
  import { ensureSunkenTexture } from './chunk-IKS72C6P.js';
3
+ import { surfaceTintAttributes, joinStyles } from './chunk-ITENVZ6Q.js';
3
4
  import { Badge } from './chunk-RMWNO6BA.js';
4
5
 
5
6
  import { jsxs, jsx } from 'kerfjs/jsx-runtime';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/navigation/tab-navigator/tab-navigator.tsx"],"names":[],"mappings":"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","file":"tab-navigator.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport {\n type ScrollEdge,\n scrollEdgeAttributes,\n} from '../../../shared/layout/scroll-edge.js';\nimport type { CssColor } from '../../../shared/styles/css-values.js';\nimport { ensureSunkenTexture } from '../../../shared/styles/sunken-texture.js';\nimport {\n joinStyles,\n type SurfaceTint,\n surfaceTintAttributes,\n} from '../../../shared/styles/surface-tint.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabNavigatorTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n /**\n * Paint this scene in an app-chosen color (a `@kerfjs/ui/css-values` color\n * builder). It replaces the appearance's fill. Forced colors paint `Canvas`.\n */\n background?: CssColor;\n /**\n * A scoped tint (`{ color, scheme }`) for this scene: its surfaces and every\n * descendant re-derive their surface and text tokens from the app's color\n * in the declared scheme, with lightness clamped to keep text at WCAG AA.\n */\n tint?: SurfaceTint;\n /** Add 8px to every side of a plain scene's scrolling content. */\n deepInset?: boolean;\n /** Opt into keyboard (`0`) or programmatic (`-1`) focus on this scene. */\n tabIndex?: 0 | -1;\n /** Keep the standard focus outline visible while this scene is active. */\n outlined?: boolean;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabNavigatorTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabNavigatorTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabNavigatorTab<Id extends string = string> =\n TabNavigatorTabBase<Id> &\n (TabNavigatorTabCountBadge | TabNavigatorTabDotBadge);\n\n/**\n * How a {@link TabNavigator}'s tab bar relates to the active scene: `docked`\n * below it, or `overlay` laid over its bottom edge on the translucent chrome\n * material, so the scene's content scrolls visibly beneath the bar.\n * `clear` lays it over\n * the same edge fully transparent, for chrome over imagery: legibility comes\n * from glass control groups or a scrim, and with `scrollEdge=\"material\"` the\n * chrome material fades in once content scrolls beneath\n * it, until the scene scrolls to its end.\n */\nexport type TabNavigatorBarPresentation = 'docked' | 'overlay' | 'clear';\n\nexport interface TabNavigatorProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabNavigatorTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The bar's top separator. `auto` (default) draws it against a sunken active\n * work surface and otherwise follows `chromeDividers`; `hidden` suppresses\n * the line, including a scroll divider.\n */\n separator?: 'auto' | 'hidden';\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` suppresses that scroll divider. A sunken active work\n * surface still draws the separator unless `separator=\"hidden\"`. The line\n * is the bar's own 1px top border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n /**\n * `docked` (default) gives the tab bar its own band below the scenes.\n * `overlay` lays it over the active scene's bottom edge on the translucent\n * chrome material (opaque under reduced transparency, increased contrast,\n * or without `backdrop-filter`; `Canvas` in forced colors), so content\n * scrolls visibly beneath it, as under an iOS tab bar. `wireTabNavigator`\n * measures the live bar (bottom safe area included) and the scenes hand\n * its height to their content as the block-end `--kui-edge-inset-*`\n * context, so a plain scene, a sole `Pane`, or a `NavStack` (and its own\n * bottom toolbar, which then sits above the bar) rests its last item clear\n * of it. Until then it stays docked. Unselected tab labels lift halfway\n * toward the normal text color to keep AA over the content beneath.\n */\n barPresentation?: TabNavigatorBarPresentation;\n /**\n * With `barPresentation=\"clear\"`, `material` fades the chrome material in\n * behind the bar while the active scene continues beneath it (default\n * `none`).\n */\n scrollEdge?: ScrollEdge;\n /**\n * The scroll distance, in CSS pixels, over which the scroll edge fades in\n * (default 60).\n */\n scrollEdgeDistance?: number;\n /**\n * Paint the navigator, and its docked tab bar, in an app-chosen color (a\n * `@kerfjs/ui/css-values` color builder) instead of the surface. A tab's own\n * `background` paints its scene. Forced colors paint `Canvas`.\n */\n background?: CssColor;\n /**\n * A scoped tint (`{ color, scheme }`) for the navigator, its bar, and every scene: its surfaces and every\n * descendant re-derive their surface and text tokens from the app's color\n * in the declared scheme, with lightness clamped to keep text at WCAG AA.\n */\n tint?: SurfaceTint;\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab navigator: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-navigator`'s `wireTabNavigator`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabNavigator<Id extends string>({\n id,\n label,\n tabs,\n active,\n separator = 'auto',\n chromeDividers = 'scroll',\n barPresentation = 'docked',\n scrollEdge = 'none',\n scrollEdgeDistance,\n background,\n tint,\n className = '',\n slot,\n}: TabNavigatorProps<Id>) {\n const tinted = surfaceTintAttributes(tint);\n if (\n tabs.some(\n (tab) => tab.appearance === 'sunken' && tab.background === undefined,\n )\n )\n ensureSunkenTexture();\n return (\n <section\n class={`kui-tab-scaffold ${tinted.className} ${className}`\n .replace(/\\s+/g, ' ')\n .trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-separator={separator}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n data-bar-presentation={\n barPresentation !== 'docked' ? 'overlay' : undefined\n }\n data-bar-material={barPresentation === 'clear' ? 'clear' : undefined}\n {...scrollEdgeAttributes(\n scrollEdge,\n scrollEdgeDistance,\n barPresentation === 'clear',\n )}\n data-background={background === undefined ? undefined : ''}\n data-kui-tint={tinted['data-kui-tint']}\n data-kui-tint-accent={tinted['data-kui-tint-accent']}\n style={joinStyles(\n background === undefined\n ? undefined\n : `--_kui-tab-scaffold-background:${background}`,\n tinted.style,\n )}\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class={`kui-tab-scaffold__scene ${surfaceTintAttributes(tab.tint).className}`.trim()}\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={\n tab.appearance === 'sunken' && tab.background === undefined\n ? 'sunken'\n : undefined\n }\n data-background={tab.background === undefined ? undefined : ''}\n data-kui-tint={surfaceTintAttributes(tab.tint)['data-kui-tint']}\n data-kui-tint-accent={\n surfaceTintAttributes(tab.tint)['data-kui-tint-accent']\n }\n style={joinStyles(\n tab.background === undefined\n ? undefined\n : `--_kui-tab-scaffold-scene-background:${tab.background}`,\n surfaceTintAttributes(tab.tint).style,\n )}\n data-deep-inset={tab.deepInset ? 'true' : undefined}\n data-outlined={\n tab.id === active && tab.outlined ? 'true' : undefined\n }\n role={tab.tabIndex === undefined ? undefined : 'tabpanel'}\n aria-label={tab.tabIndex === undefined ? undefined : tab.label}\n aria-hidden={String(tab.id !== active)}\n tabindex={\n tab.id === active\n ? tab.tabIndex\n : tab.tabIndex === undefined\n ? undefined\n : -1\n }\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
1
+ {"version":3,"sources":["../src/components/navigation/tab-navigator/tab-navigator.tsx"],"names":[],"mappings":"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","file":"tab-navigator.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport {\n type ScrollEdge,\n scrollEdgeAttributes,\n} from '../../../shared/layout/scroll-edge.js';\nimport type { CssColor } from '../../../shared/styles/css-values.js';\nimport { ensureSunkenTexture } from '../../../shared/styles/sunken-texture.js';\nimport {\n joinStyles,\n type SurfaceTint,\n surfaceTintAttributes,\n} from '../../../shared/styles/surface-tint.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabNavigatorTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n /**\n * Paint this scene in an app-chosen color (a `@kerfjs/ui/css-values` color\n * builder). It replaces the appearance's fill. Forced colors paint `Canvas`.\n */\n background?: CssColor;\n /**\n * A scoped tint (`{ color, scheme }`) for this scene: its surfaces and every\n * descendant re-derive their surface and text tokens from the app's color\n * in the declared scheme, with lightness clamped to keep text at WCAG AA.\n */\n tint?: SurfaceTint;\n /** Add 8px to every side of a plain scene's scrolling content. */\n deepInset?: boolean;\n /** Opt into keyboard (`0`) or programmatic (`-1`) focus on this scene. */\n tabIndex?: 0 | -1;\n /** Keep the standard focus outline visible while this scene is active. */\n outlined?: boolean;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabNavigatorTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabNavigatorTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabNavigatorTab<Id extends string = string> =\n TabNavigatorTabBase<Id> &\n (TabNavigatorTabCountBadge | TabNavigatorTabDotBadge);\n\n/**\n * How a {@link TabNavigator}'s tab bar relates to the active scene: `docked`\n * below it, or `overlay` laid over its bottom edge on the translucent chrome\n * material, so the scene's content scrolls visibly beneath the bar.\n * `clear` lays it over\n * the same edge fully transparent, for chrome over imagery: legibility comes\n * from glass control groups or a scrim, and with `scrollEdge=\"material\"` the\n * chrome material fades in once content scrolls beneath\n * it, until the scene scrolls to its end.\n */\nexport type TabNavigatorBarPresentation = 'docked' | 'overlay' | 'clear';\n\nexport interface TabNavigatorProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabNavigatorTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The bar's top separator. `auto` (default) draws it against a sunken active\n * work surface and otherwise follows `chromeDividers`; `hidden` suppresses\n * the line, including a scroll divider.\n */\n separator?: 'auto' | 'hidden';\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` suppresses that scroll divider. A sunken active work\n * surface still draws the separator unless `separator=\"hidden\"`. The line\n * is the bar's own 1px top border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n /**\n * `docked` (default) gives the tab bar its own band below the scenes.\n * `overlay` lays it over the active scene's bottom edge on the translucent\n * chrome material (opaque under reduced transparency, increased contrast,\n * or without `backdrop-filter`; `Canvas` in forced colors), so content\n * scrolls visibly beneath it, as under an iOS tab bar. `wireTabNavigator`\n * measures the live bar (bottom safe area included) and the scenes hand\n * its height to their content as the block-end `--kui-edge-inset-*`\n * context, so a plain scene, a sole `Pane`, or a `NavStack` (and its own\n * bottom toolbar, which then sits above the bar) rests its last item clear\n * of it. Until then it stays docked. Unselected tab labels lift halfway\n * toward the normal text color to keep AA over the content beneath.\n */\n barPresentation?: TabNavigatorBarPresentation;\n /**\n * With `barPresentation=\"clear\"`, `material` fades the chrome material in\n * behind the bar while the active scene continues beneath it (default\n * `none`).\n */\n scrollEdge?: ScrollEdge;\n /**\n * The scroll distance, in CSS pixels, over which the scroll edge fades in\n * (default 60).\n */\n scrollEdgeDistance?: number;\n /**\n * Paint the navigator, and its docked tab bar, in an app-chosen color (a\n * `@kerfjs/ui/css-values` color builder) instead of the surface. A tab's own\n * `background` paints its scene. Forced colors paint `Canvas`.\n */\n background?: CssColor;\n /**\n * A scoped tint (`{ color, scheme }`) for the navigator, its bar, and every scene: its surfaces and every\n * descendant re-derive their surface and text tokens from the app's color\n * in the declared scheme, with lightness clamped to keep text at WCAG AA.\n */\n tint?: SurfaceTint;\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab navigator: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-navigator`'s `wireTabNavigator`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabNavigator<Id extends string>({\n id,\n label,\n tabs,\n active,\n separator = 'auto',\n chromeDividers = 'scroll',\n barPresentation = 'docked',\n scrollEdge = 'none',\n scrollEdgeDistance,\n background,\n tint,\n className = '',\n slot,\n}: TabNavigatorProps<Id>) {\n const tinted = surfaceTintAttributes(tint);\n if (\n tabs.some(\n (tab) => tab.appearance === 'sunken' && tab.background === undefined,\n )\n )\n ensureSunkenTexture();\n return (\n <section\n class={`kui-tab-scaffold ${tinted.className} ${className}`\n .replace(/\\s+/g, ' ')\n .trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-separator={separator}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n data-bar-presentation={\n barPresentation !== 'docked' ? 'overlay' : undefined\n }\n data-bar-material={barPresentation === 'clear' ? 'clear' : undefined}\n {...scrollEdgeAttributes(\n scrollEdge,\n scrollEdgeDistance,\n barPresentation === 'clear',\n )}\n data-background={background === undefined ? undefined : ''}\n data-kui-tint={tinted['data-kui-tint']}\n data-kui-tint-accent={tinted['data-kui-tint-accent']}\n style={joinStyles(\n background === undefined\n ? undefined\n : `--_kui-tab-scaffold-background:${background}`,\n tinted.style,\n )}\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class={`kui-tab-scaffold__scene ${surfaceTintAttributes(tab.tint).className}`.trim()}\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={\n tab.appearance === 'sunken' && tab.background === undefined\n ? 'sunken'\n : undefined\n }\n data-background={tab.background === undefined ? undefined : ''}\n data-kui-tint={surfaceTintAttributes(tab.tint)['data-kui-tint']}\n data-kui-tint-accent={\n surfaceTintAttributes(tab.tint)['data-kui-tint-accent']\n }\n style={joinStyles(\n tab.background === undefined\n ? undefined\n : `--_kui-tab-scaffold-scene-background:${tab.background}`,\n surfaceTintAttributes(tab.tint).style,\n )}\n data-deep-inset={tab.deepInset ? 'true' : undefined}\n data-outlined={\n tab.id === active && tab.outlined ? 'true' : undefined\n }\n role={tab.tabIndex === undefined ? undefined : 'tabpanel'}\n aria-label={tab.tabIndex === undefined ? undefined : tab.label}\n aria-hidden={String(tab.id !== active)}\n tabindex={\n tab.id === active\n ? tab.tabIndex\n : tab.tabIndex === undefined\n ? undefined\n : -1\n }\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
package/dist/toolbar.d.ts CHANGED
@@ -4,6 +4,7 @@ import { S as Sides } from './sides-BPSWde0A.js';
4
4
  import { PaneSeparatorSide } from './pane.js';
5
5
  import './scroll-clearance-BikHljTc.js';
6
6
  import './css-values.js';
7
+ import './surface-tint-DQgYCaP9.js';
7
8
 
8
9
  type ToolbarPosition = 'header' | 'footer';
9
10
  /** Control-band density: `default` (60px with 44px groups) or `compact`. */
@@ -0,0 +1,21 @@
1
+ import * as kerfjs from 'kerfjs';
2
+ import { CssLength } from './css-values.js';
3
+
4
+ interface ValuePreviewProps {
5
+ /** Accessible name for the keyboard-scrollable value. */
6
+ label: string;
7
+ /** Literal plain-text value, including newlines and long unbroken text. */
8
+ value: string;
9
+ /** Minimum height; defaults to 48px. */
10
+ minHeight?: CssLength;
11
+ /** Maximum height before the value scrolls; defaults to 96px. */
12
+ maxHeight?: CssLength;
13
+ id?: string;
14
+ className?: string;
15
+ /** Native named-slot assignment when composed inside a web component. */
16
+ slot?: string;
17
+ }
18
+ /** Read-only, bounded multiline text for a containing surface such as SunkenPanel. */
19
+ declare function ValuePreview({ label, value, minHeight, maxHeight, id, className, slot, }: ValuePreviewProps): kerfjs.SafeHtml;
20
+
21
+ export { ValuePreview, type ValuePreviewProps };
@@ -0,0 +1,3 @@
1
+ export { ValuePreview } from './chunk-F653O57X.js';
2
+
3
+ //# sourceMappingURL=value-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"value-preview.js"}
@@ -6,8 +6,9 @@ import './sides-BPSWde0A.js';
6
6
  import './pane.js';
7
7
  import './scroll-clearance-BikHljTc.js';
8
8
  import './css-values.js';
9
+ import './surface-tint-DQgYCaP9.js';
9
10
  import './workbench.js';
10
- import './nav-stack-CU_0dKtI.js';
11
+ import './nav-stack-CrZF95VO.js';
11
12
  import './list.js';
12
13
  import './extension-attributes-mu8MFUl0.js';
13
14
  import './flex-alignment-4ms8ZbV8.js';
@@ -1,6 +1,6 @@
1
1
  import { wireWorkbench } from './chunk-WPNIKZMJ.js';
2
-
3
2
  import { wireScrollDividers } from './chunk-OZ6X76G5.js';
3
+
4
4
  import { loadUxReviewProject } from './chunk-6C6DLP3W.js';
5
5
 
6
6
  import { readSearchField } from './chunk-VEME3D7K.js';
@@ -1,3 +1,89 @@
1
+ // src/components/surfaces/content-item/wiring/wire-content-item-media.ts
2
+ var CARD = '[data-component="content-item"][data-media-state]';
3
+ var LEAD = ".kui-content-item__media .kui-backdrop-image__media";
4
+ var BLURRED = ".kui-content-item__media .kui-backdrop-image__blurred";
5
+ function wireContentItemMedia(root) {
6
+ const tint = (card, enabled) => {
7
+ const media = card.querySelector(".kui-content-item__media");
8
+ const scheme = media?.dataset.tintScheme;
9
+ if (!scheme) return;
10
+ if (enabled) {
11
+ if (card.dataset.kuiTint !== scheme)
12
+ card.setAttribute("data-kui-tint", scheme);
13
+ if (!card.classList.contains(`wa-${scheme}`))
14
+ card.classList.add(`wa-${scheme}`);
15
+ if (media?.hasAttribute("data-tint-accent") && !card.hasAttribute("data-kui-tint-accent")) {
16
+ card.setAttribute("data-kui-tint-accent", "");
17
+ }
18
+ } else {
19
+ if (card.hasAttribute("data-kui-tint"))
20
+ card.removeAttribute("data-kui-tint");
21
+ if (card.hasAttribute("data-kui-tint-accent"))
22
+ card.removeAttribute("data-kui-tint-accent");
23
+ if (card.classList.contains("wa-light") || card.classList.contains("wa-dark"))
24
+ card.classList.remove("wa-light", "wa-dark");
25
+ }
26
+ };
27
+ const sync = (card) => {
28
+ const lead = card.querySelector(LEAD);
29
+ if (!lead) return;
30
+ const state = !lead.complete ? "pending" : lead.naturalWidth > 0 ? "ready" : "error";
31
+ if (card.dataset.mediaState !== state) card.dataset.mediaState = state;
32
+ tint(card, state !== "error");
33
+ const blurred = card.querySelector(BLURRED);
34
+ if (blurred?.complete && blurred.naturalWidth === 0)
35
+ blurred.dataset.imageError = "true";
36
+ else blurred?.removeAttribute("data-image-error");
37
+ };
38
+ const all = () => {
39
+ if (root.matches(CARD)) sync(root);
40
+ root.querySelectorAll(CARD).forEach(sync);
41
+ };
42
+ const onImage = (event) => {
43
+ const target = event.target;
44
+ if (!(target instanceof Element) || !target.matches(`${LEAD}, ${BLURRED}`))
45
+ return;
46
+ const card = target.closest(CARD);
47
+ if (card && root.contains(card)) sync(card);
48
+ };
49
+ const observer = new MutationObserver((records) => {
50
+ if (records.some((record) => record.type === "childList")) {
51
+ all();
52
+ return;
53
+ }
54
+ const cards = /* @__PURE__ */ new Set();
55
+ for (const record of records) {
56
+ const target = record.target;
57
+ if (target instanceof Element) {
58
+ const card = target.closest(CARD);
59
+ if (card && root.contains(card)) cards.add(card);
60
+ }
61
+ }
62
+ cards.forEach(sync);
63
+ });
64
+ observer.observe(root, {
65
+ childList: true,
66
+ subtree: true,
67
+ attributes: true,
68
+ attributeFilter: [
69
+ "src",
70
+ "srcset",
71
+ "data-media-state",
72
+ "data-kui-tint",
73
+ "data-kui-tint-accent",
74
+ "class"
75
+ ]
76
+ });
77
+ root.addEventListener("load", onImage, true);
78
+ root.addEventListener("error", onImage, true);
79
+ all();
80
+ return () => {
81
+ observer.disconnect();
82
+ root.removeEventListener("load", onImage, true);
83
+ root.removeEventListener("error", onImage, true);
84
+ };
85
+ }
86
+
1
87
  // src/components/surfaces/content-item/wiring/wire-content-items.ts
2
88
  var ITEM_SELECTOR = '[data-component="content-item"][data-interactive="true"]';
3
89
  var NESTED_CONTROL_SELECTOR = 'button, a[href], input, select, textarea, [contenteditable="true"], [role="button"]';
@@ -68,6 +154,7 @@ function activate(item, modifiers) {
68
154
  );
69
155
  }
70
156
  function wireContentItems(root) {
157
+ const stopMedia = wireContentItemMedia(root);
71
158
  const activeRows = /* @__PURE__ */ new WeakMap();
72
159
  const rove = (grid) => {
73
160
  const rows = gridRows(grid);
@@ -194,6 +281,7 @@ function wireContentItems(root) {
194
281
  root.addEventListener("focusout", focusout);
195
282
  root.addEventListener("focusin", focusin);
196
283
  return () => {
284
+ stopMedia();
197
285
  clearSpace();
198
286
  observer?.disconnect();
199
287
  root.removeEventListener("focusin", focusin);