@kerfjs/ui 5.0.0-beta.55 → 5.0.0-beta.56

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 (235) hide show
  1. package/README.md +12 -10
  2. package/ai/compile-time-contracts-v1.json +2 -2
  3. package/ai/component-catalog-v2.json +149 -39
  4. package/ai/component-catalog.json +202 -51
  5. package/ai/public-api-signatures-v1.md +439 -97
  6. package/ai/skill.md +43 -40
  7. package/ai/webawesome-jsx-signatures-v1.md +8 -2
  8. package/dist/app-tab.js +1 -1
  9. package/dist/badge.d.ts +35 -7
  10. package/dist/badge.js +1 -1
  11. package/dist/browser/content-item.js +3 -0
  12. package/dist/browser/select.js +1 -0
  13. package/dist/catalog-resources.d.ts +12 -1
  14. package/dist/catalog.d.ts +14 -3
  15. package/dist/catalog.js +52 -43
  16. package/dist/catalog.js.map +1 -1
  17. package/dist/{chunk-WZ2GLEY4.js → chunk-2G2ZJQYK.js} +2 -14
  18. package/dist/chunk-2G2ZJQYK.js.map +1 -0
  19. package/dist/{chunk-D7GWXIAS.js → chunk-5I7QGQVT.js} +1 -1
  20. package/dist/chunk-5I7QGQVT.js.map +1 -0
  21. package/dist/{chunk-RNRUVPJ3.js → chunk-63ZWBGE3.js} +3 -2
  22. package/dist/chunk-63ZWBGE3.js.map +1 -0
  23. package/dist/{chunk-44IG7PFV.js → chunk-6CINWX2E.js} +3 -3
  24. package/dist/{chunk-44IG7PFV.js.map → chunk-6CINWX2E.js.map} +1 -1
  25. package/dist/{chunk-C2FAYFDE.js → chunk-AGXQQFKZ.js} +2 -2
  26. package/dist/{chunk-C2FAYFDE.js.map → chunk-AGXQQFKZ.js.map} +1 -1
  27. package/dist/{chunk-ATHXW6RP.js → chunk-AHEG27M7.js} +2 -2
  28. package/dist/{chunk-ATHXW6RP.js.map → chunk-AHEG27M7.js.map} +1 -1
  29. package/dist/{chunk-7GUQYHKA.js → chunk-AK67F6QN.js} +4 -1
  30. package/dist/chunk-AK67F6QN.js.map +1 -0
  31. package/dist/chunk-BFPBS5Y4.js +250 -0
  32. package/dist/chunk-BFPBS5Y4.js.map +1 -0
  33. package/dist/{chunk-5OLVUEND.js → chunk-DG6G5EPF.js} +4 -6
  34. package/dist/chunk-DG6G5EPF.js.map +1 -0
  35. package/dist/{chunk-MTM43SJO.js → chunk-G33XC2NB.js} +1 -1
  36. package/dist/chunk-G33XC2NB.js.map +1 -0
  37. package/dist/{chunk-TZ5IHWB2.js → chunk-K6UNVKDK.js} +3 -3
  38. package/dist/{chunk-TZ5IHWB2.js.map → chunk-K6UNVKDK.js.map} +1 -1
  39. package/dist/{chunk-HMQ2ZDGG.js → chunk-KROD7KZK.js} +3 -2
  40. package/dist/chunk-KROD7KZK.js.map +1 -0
  41. package/dist/{chunk-5NDQABWK.js → chunk-M7QV3IHH.js} +3 -1
  42. package/dist/chunk-M7QV3IHH.js.map +1 -0
  43. package/dist/{chunk-RNZAKSME.js → chunk-MA6Z6EIM.js} +3 -3
  44. package/dist/{chunk-RNZAKSME.js.map → chunk-MA6Z6EIM.js.map} +1 -1
  45. package/dist/chunk-MA73TA6D.js +42 -0
  46. package/dist/chunk-MA73TA6D.js.map +1 -0
  47. package/dist/{chunk-ZMBU522F.js → chunk-MEY33VWB.js} +2 -2
  48. package/dist/{chunk-ZMBU522F.js.map → chunk-MEY33VWB.js.map} +1 -1
  49. package/dist/{chunk-T22EM4ET.js → chunk-MOBRFZH3.js} +81 -31
  50. package/dist/chunk-MOBRFZH3.js.map +1 -0
  51. package/dist/chunk-MP2GDBQZ.js +50 -0
  52. package/dist/chunk-MP2GDBQZ.js.map +1 -0
  53. package/dist/{chunk-3NBL3OUQ.js → chunk-NAPE7WUS.js} +2 -2
  54. package/dist/chunk-NAPE7WUS.js.map +1 -0
  55. package/dist/{chunk-BSKEH3UC.js → chunk-NBYTIVHS.js} +2 -2
  56. package/dist/{chunk-BSKEH3UC.js.map → chunk-NBYTIVHS.js.map} +1 -1
  57. package/dist/chunk-O3EXIBEZ.js +114 -0
  58. package/dist/chunk-O3EXIBEZ.js.map +1 -0
  59. package/dist/{chunk-R754MHXX.js → chunk-PK4N5QTO.js} +7 -6
  60. package/dist/chunk-PK4N5QTO.js.map +1 -0
  61. package/dist/{chunk-EAX2GSS4.js → chunk-QQOTQTDD.js} +51 -20
  62. package/dist/chunk-QQOTQTDD.js.map +1 -0
  63. package/dist/{chunk-LZXQVUGN.js → chunk-T4UZTNZ4.js} +4 -4
  64. package/dist/{chunk-LZXQVUGN.js.map → chunk-T4UZTNZ4.js.map} +1 -1
  65. package/dist/{chunk-N7FHFNT6.js → chunk-V65EOWNF.js} +33 -5
  66. package/dist/chunk-V65EOWNF.js.map +1 -0
  67. package/dist/{chunk-GUIHMIT2.js → chunk-VNPQDCQZ.js} +3 -3
  68. package/dist/{chunk-GUIHMIT2.js.map → chunk-VNPQDCQZ.js.map} +1 -1
  69. package/dist/{chunk-5I22LMCV.js → chunk-VZOFZPSO.js} +2 -2
  70. package/dist/chunk-VZOFZPSO.js.map +1 -0
  71. package/dist/{chunk-4BIMLITI.js → chunk-WLG6SSNT.js} +5 -3
  72. package/dist/chunk-WLG6SSNT.js.map +1 -0
  73. package/dist/{chunk-NE5JAOQS.js → chunk-WOUF7YXM.js} +3 -3
  74. package/dist/{chunk-NE5JAOQS.js.map → chunk-WOUF7YXM.js.map} +1 -1
  75. package/dist/collapsible-panel.d.ts +14 -3
  76. package/dist/collapsible-panel.js +13 -16
  77. package/dist/collapsible-panel.js.map +1 -1
  78. package/dist/content-item.d.ts +46 -0
  79. package/dist/content-item.js +4 -0
  80. package/dist/content-item.js.map +1 -0
  81. package/dist/css-values.d.ts +5 -11
  82. package/dist/css-values.js +1 -1
  83. package/dist/disclosure-arrow.d.ts +4 -1
  84. package/dist/disclosure-arrow.js +1 -1
  85. package/dist/floating-toolbar.d.ts +17 -6
  86. package/dist/floating-toolbar.js +1 -1
  87. package/dist/grid.js +1 -1
  88. package/dist/help-tags-register.d.ts +13 -0
  89. package/dist/help-tags-register.js +9 -0
  90. package/dist/help-tags-register.js.map +1 -0
  91. package/dist/index.d.ts +6 -5
  92. package/dist/index.js +27 -26
  93. package/dist/list-action-row.js +1 -1
  94. package/dist/list-header.d.ts +14 -1
  95. package/dist/list-header.js +1 -1
  96. package/dist/list-inset-control.d.ts +1 -1
  97. package/dist/list-inset-control.js +1 -1
  98. package/dist/list-inset-text.d.ts +3 -10
  99. package/dist/list-inset-text.js +1 -1
  100. package/dist/list-item.js +1 -1
  101. package/dist/list.d.ts +10 -3
  102. package/dist/list.js +1 -1
  103. package/dist/nav-stack.d.ts +38 -5
  104. package/dist/nav-stack.js +4 -1
  105. package/dist/pane.d.ts +20 -2
  106. package/dist/pane.js +1 -1
  107. package/dist/{panel-toolbar-BFQxFITu.d.ts → panel-toolbar-C8sF-YZC.d.ts} +12 -1
  108. package/dist/popup-menu-register.js +3 -2
  109. package/dist/popup-menu-register.js.map +1 -1
  110. package/dist/popup-menu.js +1 -1
  111. package/dist/row.d.ts +1 -1
  112. package/dist/row.js +1 -1
  113. package/dist/select-register.js +70 -3
  114. package/dist/select-register.js.map +1 -1
  115. package/dist/select.d.ts +46 -8
  116. package/dist/select.js +2 -1
  117. package/dist/{divider-sides-BzB6rphT.d.ts → sides-BPSWde0A.d.ts} +1 -3
  118. package/dist/skeleton.js +1 -1
  119. package/dist/spacer.js +1 -1
  120. package/dist/split-view.d.ts +31 -3
  121. package/dist/split-view.js +33 -5
  122. package/dist/split-view.js.map +1 -1
  123. package/dist/state-banner.js +1 -1
  124. package/dist/styles/badge.css +13 -0
  125. package/dist/styles/catalog/components/catalog-example.css +1 -1
  126. package/dist/styles/catalog/components/catalog-stage.css +1 -1
  127. package/dist/styles/collapsible-panel.css +192 -39
  128. package/dist/styles/content-item.css +30 -0
  129. package/dist/styles/floating-toolbar.css +5 -0
  130. package/dist/styles/foundation.css +28 -19
  131. package/dist/styles/layout.css +1 -21
  132. package/dist/styles/list-action-row.css +13 -1
  133. package/dist/styles/list-inset-text.css +1 -2
  134. package/dist/styles/list-item.css +14 -1
  135. package/dist/styles/list.css +1 -1
  136. package/dist/styles/nav-stack.css +29 -64
  137. package/dist/styles/pane.css +33 -1
  138. package/dist/styles/popup-menu.css +78 -0
  139. package/dist/styles/resizable-region.css +53 -10
  140. package/dist/styles/select.css +39 -12
  141. package/dist/styles/split-view.css +21 -25
  142. package/dist/styles/tab-scaffold.css +36 -14
  143. package/dist/styles/text.css +1 -1
  144. package/dist/styles/token-search-field.css +4 -4
  145. package/dist/styles/toolbar-control-group.css +31 -12
  146. package/dist/styles/toolbar.css +14 -5
  147. package/dist/styles/value-table.css +19 -2
  148. package/dist/styles/webawesome.css +3 -1
  149. package/dist/styles/workbench.css +134 -31
  150. package/dist/tab-scaffold.d.ts +35 -1
  151. package/dist/tab-scaffold.js +50 -16
  152. package/dist/tab-scaffold.js.map +1 -1
  153. package/dist/token-search-field.d.ts +4 -2
  154. package/dist/token-search-field.js +1 -1
  155. package/dist/toolbar-text.js +1 -1
  156. package/dist/toolbar.d.ts +12 -4
  157. package/dist/toolbar.js +1 -1
  158. package/dist/{types-D6vzj-VT.d.ts → types-DXAGogOS.d.ts} +39 -6
  159. package/dist/value-table.js +1 -1
  160. package/dist/webawesome.d.ts +7 -1
  161. package/dist/wire-catalog.d.ts +12 -1
  162. package/dist/wire-catalog.js +10 -2
  163. package/dist/wire-catalog.js.map +1 -1
  164. package/dist/wire-token-search-fields.js +1 -1
  165. package/dist/workbench.d.ts +59 -22
  166. package/dist/workbench.js +1 -1
  167. package/docs/accessibility.md +116 -7
  168. package/docs/app-layouts.md +9 -7
  169. package/docs/catalog.md +17 -3
  170. package/docs/collapsible-panel.md +26 -7
  171. package/docs/component-contract.md +36 -10
  172. package/docs/component-selection.md +41 -40
  173. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  174. package/docs/design/templates/empty-state/with-action.svg +1 -1
  175. package/docs/design/templates/empty-state-dark.svg +1 -1
  176. package/docs/design/templates/empty-state.svg +1 -1
  177. package/docs/design/templates/list/compact-dark.svg +1 -1
  178. package/docs/design/templates/list/compact.svg +1 -1
  179. package/docs/design/templates/list/stack-dark.svg +1 -1
  180. package/docs/design/templates/list/stack.svg +1 -1
  181. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  182. package/docs/design/templates/list-action-row/default.svg +1 -1
  183. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  184. package/docs/design/templates/list-action-row/selected.svg +1 -1
  185. package/docs/design/templates/list-action-row-dark.svg +1 -1
  186. package/docs/design/templates/list-action-row.svg +1 -1
  187. package/docs/design/templates/list-dark.svg +1 -1
  188. package/docs/design/templates/list-item/default-dark.svg +1 -1
  189. package/docs/design/templates/list-item/default.svg +1 -1
  190. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  191. package/docs/design/templates/list-item/selected.svg +1 -1
  192. package/docs/design/templates/list-item/trailing-dark.svg +1 -1
  193. package/docs/design/templates/list-item/trailing.svg +1 -1
  194. package/docs/design/templates/list-item-dark.svg +1 -1
  195. package/docs/design/templates/list-item.svg +1 -1
  196. package/docs/design/templates/list.svg +1 -1
  197. package/docs/design/templates/value-table/details-dark.svg +1 -1
  198. package/docs/design/templates/value-table/details.svg +1 -1
  199. package/docs/design/templates/value-table-dark.svg +1 -1
  200. package/docs/design/templates/value-table.svg +1 -1
  201. package/docs/design-philosophy.md +1 -1
  202. package/docs/examples/command-palette-adapter.tsx +3 -2
  203. package/docs/layout.md +69 -29
  204. package/docs/nav-stack.md +45 -1
  205. package/docs/recipes.md +4 -3
  206. package/docs/split-view.md +46 -2
  207. package/docs/tab-scaffold.md +50 -1
  208. package/docs/type-contracts.md +17 -17
  209. package/docs/ux-demo.md +2 -2
  210. package/docs/webawesome-theme.md +1 -2
  211. package/docs/workbench.md +45 -9
  212. package/llms.txt +32 -4
  213. package/package.json +13 -2
  214. package/ux-demo/recipes/collapsible-sidebar.tsx +3 -2
  215. package/ux-demo/recipes/navigation-sidebar.tsx +3 -2
  216. package/ux-demo/recipes/navigation-stack.tsx +3 -2
  217. package/dist/chunk-3NBL3OUQ.js.map +0 -1
  218. package/dist/chunk-4BIMLITI.js.map +0 -1
  219. package/dist/chunk-5I22LMCV.js.map +0 -1
  220. package/dist/chunk-5NDQABWK.js.map +0 -1
  221. package/dist/chunk-5OLVUEND.js.map +0 -1
  222. package/dist/chunk-7GUQYHKA.js.map +0 -1
  223. package/dist/chunk-D7GWXIAS.js.map +0 -1
  224. package/dist/chunk-DQA5FMOX.js +0 -85
  225. package/dist/chunk-DQA5FMOX.js.map +0 -1
  226. package/dist/chunk-EAX2GSS4.js.map +0 -1
  227. package/dist/chunk-HMQ2ZDGG.js.map +0 -1
  228. package/dist/chunk-MTM43SJO.js.map +0 -1
  229. package/dist/chunk-N7FHFNT6.js.map +0 -1
  230. package/dist/chunk-R754MHXX.js.map +0 -1
  231. package/dist/chunk-RNRUVPJ3.js.map +0 -1
  232. package/dist/chunk-T22EM4ET.js.map +0 -1
  233. package/dist/chunk-WZ2GLEY4.js.map +0 -1
  234. package/dist/chunk-XD7DWYXG.js +0 -34
  235. package/dist/chunk-XD7DWYXG.js.map +0 -1
@@ -317,7 +317,12 @@
317
317
  padding-inline: 0;
318
318
  }
319
319
 
320
- .kui-toolbar-control-group[data-menu-inset="standard"] wa-dropdown::part(menu) {
320
+ /* `standard` keeps each menu's own standard inset: a raw wa-dropdown takes the
321
+ 8px surface inset, while a PopupMenu keeps the Select listbox inset it
322
+ shares (popup-menu.css). The explicit compact and list-zero choices apply to
323
+ every nested menu. */
324
+ .kui-toolbar-control-group[data-menu-inset="standard"]
325
+ wa-dropdown:not([data-component="popup-menu"])::part(menu) {
321
326
  padding: var(--kui-wa-surface-inset, 0.5rem);
322
327
  }
323
328
  .kui-toolbar-control-group[data-menu-inset="compact"] wa-dropdown::part(menu) {
@@ -478,35 +483,49 @@
478
483
 
479
484
  /* With per-control rings, a composed Select paints its own ring even when it
480
485
  was told the group owns it: nothing else would show focus. It matches the
481
- sibling controls' ring and offset. */
486
+ sibling controls' ring and offset. While its listbox is open the popup shows
487
+ focus instead, so the trigger ring stays off, as a PopupMenu trigger's does. */
482
488
  .kui-toolbar-control-group[data-focus-ring="control"]
483
- > wa-select:focus-within::part(combobox) {
489
+ > wa-select:not([open]):focus-within::part(combobox) {
484
490
  outline: var(--kui-focus-ring);
485
491
  outline-offset: 1px;
486
492
  }
487
493
 
488
- .kui-toolbar-control-group[data-focus-ring="outline"]:focus-within {
494
+ .kui-toolbar-control-group[data-focus-ring="outline"]:focus-within:not(
495
+ :where(:has(wa-select[open], wa-dropdown[open]))
496
+ ) {
489
497
  outline: var(--kui-focus-ring);
490
498
  outline-offset: 1px;
491
499
  }
492
500
 
493
- .kui-toolbar-control-group[data-focus-ring="halo"]:focus-within {
501
+ .kui-toolbar-control-group[data-focus-ring="halo"]:focus-within:not(
502
+ :where(:has(wa-select[open], wa-dropdown[open]))
503
+ ) {
494
504
  border-color: var(--kui-color-text-link);
495
505
  box-shadow: 0 0 0 3px
496
506
  color-mix(in srgb, var(--kui-color-text-link) 22%, transparent);
497
507
  }
498
508
 
499
- /* Chromium and WebKit do not propagate :focus-within from a focused
500
- light-DOM child (an open wa-select's active wa-option) through the host's
501
- popup shadow tree, so the group ring vanished while its Select listbox was
502
- open. A focused light-DOM descendant still owns the group ring. Kept as
503
- separate rules so an engine without :has() keeps the :focus-within ring. */
504
- .kui-toolbar-control-group[data-focus-ring="outline"]:has(:focus) {
509
+ /* A group-owned ring (outline or halo) is off while a Select listbox or a
510
+ PopupMenu inside the group is open: the open popup shows focus, as the
511
+ per-control Select and PopupMenu triggers already do, and the ring returns
512
+ when the popup closes with focus still in the group. The exclusion sits in
513
+ a forgiving :where(), so an engine without :has() keeps the ring.
514
+
515
+ Chromium and WebKit do not propagate :focus-within from a focused
516
+ light-DOM child through a host's popup shadow tree, so a focused light-DOM
517
+ descendant also owns the group ring. Kept as separate rules so an engine
518
+ without :has() keeps the :focus-within ring. */
519
+ .kui-toolbar-control-group[data-focus-ring="outline"]:has(:focus):not(
520
+ :where(:has(wa-select[open], wa-dropdown[open]))
521
+ ) {
505
522
  outline: var(--kui-focus-ring);
506
523
  outline-offset: 1px;
507
524
  }
508
525
 
509
- .kui-toolbar-control-group[data-focus-ring="halo"]:has(:focus) {
526
+ .kui-toolbar-control-group[data-focus-ring="halo"]:has(:focus):not(
527
+ :where(:has(wa-select[open], wa-dropdown[open]))
528
+ ) {
510
529
  border-color: var(--kui-color-text-link);
511
530
  box-shadow: 0 0 0 3px
512
531
  color-mix(in srgb, var(--kui-color-text-link) 22%, transparent);
@@ -5,7 +5,11 @@
5
5
  box-sizing: border-box;
6
6
  color: var(--kui-color-text);
7
7
  background: transparent;
8
- grid-template-columns: auto minmax(0, 1fr) auto;
8
+ /* The center track's minimum is `auto`, not 0, so it resolves to the center
9
+ zone's own min-width (its content width, below): when the zones do not all
10
+ fit, the auto leading track is what shrinks and its title truncates, and
11
+ the center group never overflows its track onto the leading identity. */
12
+ grid-template-columns: auto 1fr auto;
9
13
  align-items: start;
10
14
  gap: var(--kui-toolbar-gap, 0.5rem);
11
15
  container: kui-toolbar / inline-size;
@@ -103,28 +107,28 @@
103
107
  .kui-toolbar[divider-sides*="t"] {
104
108
  --_kui-toolbar-divider-top: var(
105
109
  --kui-toolbar-divider-color,
106
- var(--kui-color-border)
110
+ var(--kui-color-neutral-border-normal)
107
111
  );
108
112
  }
109
113
 
110
114
  .kui-toolbar[divider-sides*="r"] {
111
115
  --_kui-toolbar-divider-right: var(
112
116
  --kui-toolbar-divider-color,
113
- var(--kui-color-border)
117
+ var(--kui-color-neutral-border-normal)
114
118
  );
115
119
  }
116
120
 
117
121
  .kui-toolbar[divider-sides*="b"] {
118
122
  --_kui-toolbar-divider-bottom: var(
119
123
  --kui-toolbar-divider-color,
120
- var(--kui-color-border)
124
+ var(--kui-color-neutral-border-normal)
121
125
  );
122
126
  }
123
127
 
124
128
  .kui-toolbar[divider-sides*="l"] {
125
129
  --_kui-toolbar-divider-left: var(
126
130
  --kui-toolbar-divider-color,
127
- var(--kui-color-border)
131
+ var(--kui-color-neutral-border-normal)
128
132
  );
129
133
  }
130
134
 
@@ -190,6 +194,11 @@
190
194
  }
191
195
 
192
196
  .kui-toolbar__center {
197
+ /* The center content never compresses below its own width (a group cannot
198
+ shrink or clip its controls). The track above takes this as its minimum, so
199
+ the leading identity gives way first, the way a platform toolbar truncates
200
+ its title before it overlaps the centered controls. */
201
+ min-width: min-content;
193
202
  justify-content: center;
194
203
  grid-column: 2;
195
204
  }
@@ -1,6 +1,6 @@
1
1
  .kui-value-table {
2
2
  --kui-value-table-background: var(--kui-color-surface-lowered);
3
- --kui-value-table-border: var(--kui-color-border);
3
+ --kui-value-table-border: var(--kui-color-neutral-border-normal);
4
4
  --_kui-value-table-icon-size: var(--kui-value-table-icon-size, 1.5rem);
5
5
  display: grid;
6
6
  margin-block: 0;
@@ -22,7 +22,14 @@
22
22
  padding-block: 0.5rem;
23
23
  padding-inline: var(--_kui-value-table-inline-padding);
24
24
  box-sizing: border-box;
25
- grid-template-columns: minmax(6rem, auto) minmax(0, 1fr);
25
+ /* Two content-sized tracks share the free space equally, and each stops
26
+ growing once its content fits. A short value keeps its whole width and a
27
+ long label takes the rest (ending a word that still does not fit in an
28
+ ellipsis); a short label keeps its width beside a long, wrapping value;
29
+ when both are long they split the row. A `1fr` value track would get
30
+ nothing until the label reached its full width, starving the value to 0px
31
+ beside a long single-word label. */
32
+ grid-template-columns: minmax(0, auto) minmax(0, auto);
26
33
  align-items: center;
27
34
  gap: var(--kui-layout-item-gap, 0.5rem);
28
35
  }
@@ -42,7 +49,12 @@
42
49
  }
43
50
  .kui-value-table__row dt {
44
51
  display: flex;
52
+ /* The label hugs its content (up to its column) rather than stretching, so
53
+ its box does not change with the value's width — a placeholder row keeps
54
+ the live row's label geometry beside its skeleton value. */
55
+ max-width: 100%;
45
56
  min-width: 0;
57
+ justify-self: start;
46
58
  color: var(--kui-color-text-quiet);
47
59
  align-items: center;
48
60
  gap: var(--kui-layout-item-gap, 0.5rem);
@@ -60,6 +72,11 @@
60
72
  }
61
73
  .kui-value-table__label {
62
74
  min-width: 0;
75
+ overflow: hidden;
76
+ /* A multi-word label wraps at spaces; a single word wider than the label
77
+ column (a long component or file name) ends in an ellipsis instead of
78
+ running under the value. */
79
+ text-overflow: ellipsis;
63
80
  }
64
81
  .kui-value-table__row dd {
65
82
  display: flex;
@@ -499,7 +499,9 @@
499
499
  padding-inline: var(--kui-wa-control-inset) !important;
500
500
  }
501
501
 
502
- wa-dropdown-item {
502
+ /* A PopupMenu item owns the Select option geometry instead (popup-menu.css),
503
+ so a command menu and a value list draw identical rows. */
504
+ wa-dropdown-item:not([data-component="popup-menu"] > *) {
503
505
  padding-inline: var(--kui-wa-control-inset) !important;
504
506
  }
505
507
 
@@ -55,14 +55,14 @@
55
55
  --_kui-workbench-rail-safe-inset: var(--_kui-workbench-safe-inline-start);
56
56
  --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
57
57
  --kui-edge-inset-inline-end: 0px;
58
- border-inline-end: 1px solid var(--kui-color-border);
58
+ border-inline-end: 1px solid var(--kui-color-neutral-border-normal);
59
59
  }
60
60
 
61
61
  .kui-workbench__rail--right {
62
62
  --_kui-workbench-rail-safe-inset: var(--_kui-workbench-safe-inline-end);
63
63
  --kui-edge-inset-inline-start: 0px;
64
64
  --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
65
- border-inline-start: 1px solid var(--kui-color-border);
65
+ border-inline-start: 1px solid var(--kui-color-neutral-border-normal);
66
66
  }
67
67
 
68
68
  .kui-workbench__rail[data-collapsed="true"] {
@@ -157,30 +157,32 @@
157
157
  var(--kui-edge-inset-block-end);
158
158
  }
159
159
 
160
- .kui-workbench__main:not(
161
- :has(
162
- > :is(
163
- [data-component="pane"],
164
- [data-component="nav-stack"],
165
- [data-component="split-view"],
166
- [data-component="workbench"],
167
- [data-component="tab-scaffold"]
168
- ):only-child
169
- )
170
- )
171
- > *,
172
- .kui-workbench__panel-content:not(
173
- :has(
174
- > :is(
175
- [data-component="pane"],
176
- [data-component="nav-stack"],
177
- [data-component="split-view"],
178
- [data-component="workbench"],
179
- [data-component="tab-scaffold"]
180
- ):only-child
181
- )
182
- )
183
- > * {
160
+ /* Every other child sees a cleared context. The exemption is tested on the
161
+ child itself, never as `region:not(:has(…)) > *`: a `:has()` before a
162
+ universal child makes each child of the region re-check the region's
163
+ `:has()` on every DOM change (~46ms per restyle for 1000 rows in Chromium).
164
+ SplitView, NavStack, TabScaffold, and CollapsiblePanel content use the same
165
+ shape. */
166
+ .kui-workbench__main
167
+ > :not(
168
+ :is(
169
+ [data-component="pane"],
170
+ [data-component="nav-stack"],
171
+ [data-component="split-view"],
172
+ [data-component="workbench"],
173
+ [data-component="tab-scaffold"]
174
+ ):only-child
175
+ ),
176
+ .kui-workbench__panel-content
177
+ > :not(
178
+ :is(
179
+ [data-component="pane"],
180
+ [data-component="nav-stack"],
181
+ [data-component="split-view"],
182
+ [data-component="workbench"],
183
+ [data-component="tab-scaffold"]
184
+ ):only-child
185
+ ) {
184
186
  --kui-edge-inset-block-start: 0px;
185
187
  --kui-edge-inset-block-end: 0px;
186
188
  --kui-edge-inset-inline-start: 0px;
@@ -213,7 +215,7 @@
213
215
  flex: 0 0 var(--_kui-workbench-drawer-extent);
214
216
  min-height: 0;
215
217
  overflow: hidden;
216
- border-block-start: 1px solid var(--kui-color-border);
218
+ border-block-start: 1px solid var(--kui-color-neutral-border-normal);
217
219
  background: var(--kui-color-surface);
218
220
  }
219
221
 
@@ -387,10 +389,64 @@
387
389
  display: none;
388
390
  }
389
391
 
390
- /* Where the engine can extend a clip past the panel's edge, an expanded
391
- panel lets exactly the handle's outer half through, so the hit target
392
- straddles the separator like a ResizableRegion handle does. Collapsing drops
393
- the margin again before the content slides out. */
392
+ /* An expanded panel lets exactly the handle's outer half through its clip, so
393
+ the hit target straddles the separator like a ResizableRegion handle does
394
+ and the grip is drawn centered on the separator line. Collapsing drops the
395
+ extension again before the content slides out. Engines that can extend a
396
+ clip past the panel's edge do it with `overflow-clip-margin`; the rest
397
+ (WebKit) clip with a `clip-path` that reaches past the panel's edges. That
398
+ also makes the panel a stacking context, so the panel stacks at the
399
+ handle's own level to keep the handle's outer half above the work area
400
+ beside it. The clip and the stacking level sit at low specificity, so a
401
+ responsive overlay presentation (below) replaces both. */
402
+ @supports not (overflow-clip-margin: 10px) {
403
+ .kui-workbench__rail[data-resizable="true"][data-presentation="inline"]:not(
404
+ [data-collapsed="true"],
405
+ [data-content-overflow="visible"]
406
+ ),
407
+ .kui-workbench__drawer[data-resizable="true"][data-presentation="inline"]:not(
408
+ [data-collapsed="true"],
409
+ [data-content-overflow="visible"]
410
+ ) {
411
+ overflow: visible;
412
+ }
413
+
414
+ .kui-workbench__rail:where(
415
+ [data-resizable="true"][data-presentation="inline"]:not(
416
+ [data-collapsed="true"],
417
+ [data-content-overflow="visible"]
418
+ )
419
+ ) {
420
+ z-index: 3;
421
+ clip-path: inset(0 -10px);
422
+ }
423
+
424
+ .kui-workbench__drawer:where(
425
+ [data-resizable="true"][data-presentation="inline"]:not(
426
+ [data-collapsed="true"],
427
+ [data-content-overflow="visible"]
428
+ )
429
+ ) {
430
+ z-index: 3;
431
+ clip-path: inset(-10px 0 0);
432
+ }
433
+
434
+ .kui-workbench__rail--left[data-resizable="true"][data-presentation="inline"]
435
+ > .kui-workbench__handle {
436
+ inset-inline-end: -10px;
437
+ }
438
+
439
+ .kui-workbench__rail--right[data-resizable="true"][data-presentation="inline"]
440
+ > .kui-workbench__handle {
441
+ inset-inline-start: -10px;
442
+ }
443
+
444
+ .kui-workbench__drawer[data-resizable="true"][data-presentation="inline"]
445
+ > .kui-workbench__handle {
446
+ inset-block-start: -10px;
447
+ }
448
+ }
449
+
394
450
  @supports (overflow-clip-margin: 10px) {
395
451
  .kui-workbench__rail[data-resizable="true"]:not(
396
452
  [data-collapsed="true"],
@@ -542,6 +598,7 @@
542
598
  max-width: var(--kui-workbench-overlay-max-width, 85vw);
543
599
  inset-block: 0;
544
600
  box-shadow: var(--kui-shadow-l);
601
+ clip-path: none;
545
602
  }
546
603
 
547
604
  .kui-workbench__rail--left[data-responsive-overlay-at="narrow"] {
@@ -592,6 +649,7 @@
592
649
  inset-inline: 0;
593
650
  inset-block-end: 0;
594
651
  box-shadow: var(--kui-shadow-l);
652
+ clip-path: none;
595
653
  }
596
654
 
597
655
  .kui-workbench__drawer[data-responsive-overlay-at="narrow"][data-collapsed="true"] {
@@ -626,6 +684,7 @@
626
684
  max-width: var(--kui-workbench-overlay-max-width, 85vw);
627
685
  inset-block: 0;
628
686
  box-shadow: var(--kui-shadow-l);
687
+ clip-path: none;
629
688
  }
630
689
 
631
690
  .kui-workbench__rail--left[data-responsive-overlay-at="compact"] {
@@ -676,6 +735,7 @@
676
735
  inset-inline: 0;
677
736
  inset-block-end: 0;
678
737
  box-shadow: var(--kui-shadow-l);
738
+ clip-path: none;
679
739
  }
680
740
 
681
741
  .kui-workbench__drawer[data-responsive-overlay-at="compact"][data-collapsed="true"] {
@@ -721,6 +781,8 @@
721
781
  from this point instead of adding its own inset on top. */
722
782
  --kui-floating-toolbar-inset: 0px;
723
783
  position: absolute;
784
+ /* Hidden while an open rail overlay covers the work area (see below). */
785
+ visibility: var(--_kui-floating-covered, inherit);
724
786
  z-index: var(
725
787
  --kui-workbench-restore-z,
726
788
  calc(var(--kui-workbench-overlay-z, 41) - 2)
@@ -788,6 +850,47 @@
788
850
  );
789
851
  }
790
852
 
853
+ /* An open rail overlay — static or a responsive one whose breakpoint applies —
854
+ covers the work area, so the work area's floating controls (a
855
+ FloatingToolbar in main, the drawer's restore corner, the other rail's
856
+ restore corner) are hidden, not merely stacked beneath it: they never paint
857
+ over the overlay whatever stacking context an app puts them in, and hidden
858
+ they leave the tab order and the accessibility tree. They come back as the
859
+ overlay closes. The flag is set on the rail's siblings only (never on the
860
+ open rail), so the overlay's own floating controls stay, and it is read by
861
+ FloatingToolbar and the layouts' restore corners. A direct child
862
+ resolves the Workbench's own container query, never a nested one's. */
863
+ .kui-workbench:has(
864
+ > .kui-workbench__rail[data-presentation="overlay"]:not(
865
+ [data-collapsed="true"]
866
+ )
867
+ )
868
+ > :is(.kui-workbench__center, .kui-workbench__restore) {
869
+ --_kui-floating-covered: hidden;
870
+ }
871
+
872
+ @container kui-workbench (max-width: 44rem) {
873
+ .kui-workbench:has(
874
+ > .kui-workbench__rail[data-responsive-overlay-at="narrow"][data-presentation="inline"]:not(
875
+ [data-collapsed="true"]
876
+ )
877
+ )
878
+ > :is(.kui-workbench__center, .kui-workbench__restore) {
879
+ --_kui-floating-covered: hidden;
880
+ }
881
+ }
882
+
883
+ @container kui-workbench (max-width: 28rem) {
884
+ .kui-workbench:has(
885
+ > .kui-workbench__rail[data-responsive-overlay-at="compact"][data-presentation="inline"]:not(
886
+ [data-collapsed="true"]
887
+ )
888
+ )
889
+ > :is(.kui-workbench__center, .kui-workbench__restore) {
890
+ --_kui-floating-covered: hidden;
891
+ }
892
+ }
893
+
791
894
  @media (prefers-reduced-motion: reduce) {
792
895
  .kui-workbench__panel-content {
793
896
  transition: none;
@@ -1,7 +1,7 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
3
 
4
- interface TabScaffoldTab<Id extends string = string> {
4
+ interface TabScaffoldTabBase<Id extends string> {
5
5
  id: Id;
6
6
  label: string;
7
7
  /** Decorative icon shown above the label in the bottom bar. */
@@ -9,6 +9,40 @@ interface TabScaffoldTab<Id extends string = string> {
9
9
  /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */
10
10
  content: KerfUiContent;
11
11
  }
12
+ /** A tab with an optional count or short-status badge. */
13
+ interface TabScaffoldTabCountBadge {
14
+ /**
15
+ * Optional count or short status shown as a solid danger `Badge` at the
16
+ * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,
17
+ * or non-finite numbers render no badge. The visual badge is `aria-hidden`;
18
+ * its meaning reaches assistive technology through `badgeLabel`.
19
+ *
20
+ * Pass `true` instead for the text-free dot (new content without a count).
21
+ */
22
+ badge?: string | number;
23
+ /**
24
+ * Localized phrase folded into the tab's accessible name as
25
+ * `"<label>, <badgeLabel>"` (for example `"3 unread"` → `"Inbox, 3 unread"`).
26
+ * Defaults to the badge text itself. Ignored when no badge renders.
27
+ */
28
+ badgeLabel?: string;
29
+ }
30
+ /** A tab with the text-free dot badge (new content without a count). */
31
+ interface TabScaffoldTabDotBadge {
32
+ /**
33
+ * `true` shows a solid danger dot `Badge` at the top-trailing corner of the
34
+ * tab icon — the iOS tab-bar dot for new content without a count.
35
+ */
36
+ badge: true;
37
+ /**
38
+ * Required localized phrase folded into the tab's accessible name as
39
+ * `"<label>, <badgeLabel>"` (for example `"New activity"` →
40
+ * `"Feed, New activity"`). A dot has no text, so this is its only meaning.
41
+ */
42
+ badgeLabel: string;
43
+ }
44
+ /** One bottom-bar destination: its label, optional icon and badge, and content. */
45
+ type TabScaffoldTab<Id extends string = string> = TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);
12
46
  interface TabScaffoldProps<Id extends string = string> {
13
47
  id: string;
14
48
  /** Accessible name for the tab bar. */
@@ -1,6 +1,6 @@
1
+ import { Badge } from './chunk-MP2GDBQZ.js';
1
2
  import { jsxs, jsx } from 'kerfjs/jsx-runtime';
2
3
 
3
- // src/tab-scaffold.tsx
4
4
  function TabScaffold({
5
5
  id,
6
6
  label,
@@ -28,25 +28,59 @@ function TabScaffold({
28
28
  children: tab.content
29
29
  }
30
30
  )) }),
31
- /* @__PURE__ */ jsx("nav", { class: "kui-tab-scaffold__bar", role: "tablist", "aria-label": label, children: tabs.map((tab) => /* @__PURE__ */ jsxs(
32
- "button",
33
- {
34
- type: "button",
35
- class: "kui-tab-scaffold__tab",
36
- role: "tab",
37
- "data-tab-scaffold-tab": tab.id,
38
- "aria-selected": String(tab.id === active),
39
- tabindex: tab.id === active ? "0" : "-1",
40
- children: [
41
- tab.icon && /* @__PURE__ */ jsx("span", { class: "kui-tab-scaffold__tab-icon", "aria-hidden": "true", children: tab.icon }),
42
- /* @__PURE__ */ jsx("span", { class: "kui-tab-scaffold__tab-label", children: tab.label })
43
- ]
44
- }
45
- )) })
31
+ /* @__PURE__ */ jsx("nav", { class: "kui-tab-scaffold__bar", role: "tablist", "aria-label": label, children: tabs.map((tab) => {
32
+ const dot = tab.badge === true;
33
+ const badgeText = dot ? void 0 : normalizeBadge(tab.badge);
34
+ const hasBadge = dot || badgeText !== void 0;
35
+ const badge = hasBadge && /* @__PURE__ */ jsx(
36
+ "span",
37
+ {
38
+ class: "kui-tab-scaffold__tab-badge",
39
+ "data-badge-kind": dot ? "dot" : "text",
40
+ children: badgeText === void 0 ? /* @__PURE__ */ jsx(Badge, { tone: "danger", size: "dot", ariaHidden: true }) : /* @__PURE__ */ jsx(
41
+ Badge,
42
+ {
43
+ tone: "danger",
44
+ appearance: "solid",
45
+ size: "compact",
46
+ ariaHidden: true,
47
+ children: badgeText
48
+ }
49
+ )
50
+ }
51
+ );
52
+ const badgeLabel = hasBadge ? tab.badgeLabel?.trim() || badgeText : void 0;
53
+ return /* @__PURE__ */ jsxs(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ class: "kui-tab-scaffold__tab",
58
+ role: "tab",
59
+ "data-tab-scaffold-tab": tab.id,
60
+ "aria-selected": String(tab.id === active),
61
+ tabindex: tab.id === active ? "0" : "-1",
62
+ "aria-label": badgeLabel === void 0 ? void 0 : `${tab.label}, ${badgeLabel}`,
63
+ "data-has-badge": hasBadge ? "true" : void 0,
64
+ children: [
65
+ tab.icon ? /* @__PURE__ */ jsxs("span", { class: "kui-tab-scaffold__tab-icon", "aria-hidden": "true", children: [
66
+ tab.icon,
67
+ badge
68
+ ] }) : badge,
69
+ /* @__PURE__ */ jsx("span", { class: "kui-tab-scaffold__tab-label", children: tab.label })
70
+ ]
71
+ }
72
+ );
73
+ }) })
46
74
  ]
47
75
  }
48
76
  );
49
77
  }
78
+ function normalizeBadge(badge) {
79
+ if (typeof badge === "number")
80
+ return Number.isFinite(badge) ? String(badge) : void 0;
81
+ if (typeof badge === "string" && badge.trim()) return badge.trim();
82
+ return void 0;
83
+ }
50
84
 
51
85
  export { TabScaffold };
52
86
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tab-scaffold.tsx"],"names":[],"mappings":";;;AAiCO,SAAS,WAAA,CAA+B;AAAA,EAC7C,EAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA,GAAY,EAAA;AAAA,EACZ;AACF,CAAA,EAAyB;AACvB,EAAA,uBACE,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MAC5C,EAAA;AAAA,MACA,gBAAA,EAAe,cAAA;AAAA,MACf,sBAAA,EAAsB,EAAA;AAAA,MACtB,IAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,SAAI,KAAA,EAAM,0BAAA,EACR,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACT,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,yBAAA;AAAA,YACN,2BAAyB,GAAA,CAAI,EAAA;AAAA,YAC7B,aAAA,EAAa,MAAA,CAAO,GAAA,CAAI,EAAA,KAAO,MAAM,CAAA;AAAA,YACrC,aAAA,EAAa,MAAA,CAAO,GAAA,CAAI,EAAA,KAAO,MAAM,CAAA;AAAA,YAEpC,QAAA,EAAA,GAAA,CAAI;AAAA;AAAA,SAER,CAAA,EACH,CAAA;AAAA,wBACA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,uBAAA,EAAwB,IAAA,EAAK,SAAA,EAAU,YAAA,EAAY,KAAA,EAC3D,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACT,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,KAAA,EAAM,uBAAA;AAAA,YACN,IAAA,EAAK,KAAA;AAAA,YACL,yBAAuB,GAAA,CAAI,EAAA;AAAA,YAC3B,eAAA,EAAe,MAAA,CAAO,GAAA,CAAI,EAAA,KAAO,MAAM,CAAA;AAAA,YACvC,QAAA,EAAU,GAAA,CAAI,EAAA,KAAO,MAAA,GAAS,GAAA,GAAM,IAAA;AAAA,YAEnC,QAAA,EAAA;AAAA,cAAA,GAAA,CAAI,IAAA,wBACF,MAAA,EAAA,EAAK,KAAA,EAAM,8BAA6B,aAAA,EAAY,MAAA,EAClD,cAAI,IAAA,EACP,CAAA;AAAA,8BAEF,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAM,6BAAA,EAA+B,cAAI,KAAA,EAAM;AAAA;AAAA;AAAA,SAExD,CAAA,EACH;AAAA;AAAA;AAAA,GACF;AAEJ","file":"tab-scaffold.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from './semantic-content.js';\n\nexport interface TabScaffoldTab<Id extends string = 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}\n\nexport interface TabScaffoldProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabScaffoldTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\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 scaffold: 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-scaffold`'s `wireTabScaffold`.\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 TabScaffold<Id extends string>({\n id,\n label,\n tabs,\n active,\n className = '',\n slot,\n}: TabScaffoldProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n aria-hidden={String(tab.id !== active)}\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 <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 >\n {tab.icon && (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n </span>\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n ))}\n </nav>\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-scaffold.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport { Badge } from './badge.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\ninterface TabScaffoldTabBase<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}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabScaffoldTabCountBadge {\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 TabScaffoldTabDotBadge {\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 TabScaffoldTab<Id extends string = string> =\n TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);\n\nexport interface TabScaffoldProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabScaffoldTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\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 scaffold: 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-scaffold`'s `wireTabScaffold`.\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 TabScaffold<Id extends string>({\n id,\n label,\n tabs,\n active,\n className = '',\n slot,\n}: TabScaffoldProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n aria-hidden={String(tab.id !== active)}\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,4 +1,4 @@
1
- import * as kerfjs from 'kerfjs';
1
+ import { SafeHtml } from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
3
 
4
4
  interface TokenSearchToken {
@@ -41,6 +41,8 @@ interface TokenSearchFieldBaseProps {
41
41
  removeAction?: string;
42
42
  clearAction?: string;
43
43
  clearLabel?: string;
44
+ /** Icon for the clear button. Default: the Lucide `x` glyph token chips also use. */
45
+ clearIcon?: SafeHtml;
44
46
  className?: string;
45
47
  editorAttributes?: TokenSearchEditorAttributes;
46
48
  /** Native named-slot assignment when composed inside a web component. */
@@ -64,7 +66,7 @@ interface TokenSearchFieldValue {
64
66
  query: string;
65
67
  tokens: TokenSearchToken[];
66
68
  }
67
- declare function TokenSearchField({ id, label, query, tokens, revision, placeholder, tokenPlaceholder, disabled, autofocus, collapsible, expanded, expandAction, expandLabel, leading, trailing, presentation, editAction, removeAction, clearAction, clearLabel, className, editorAttributes, slot, }: TokenSearchFieldProps): kerfjs.SafeHtml;
69
+ declare function TokenSearchField({ id, label, query, tokens, revision, placeholder, tokenPlaceholder, disabled, autofocus, collapsible, expanded, expandAction, expandLabel, leading, trailing, presentation, editAction, removeAction, clearAction, clearLabel, clearIcon, className, editorAttributes, slot, }: TokenSearchFieldProps): SafeHtml;
68
70
  /** Read editable text and ordered token offsets from a rendered TokenSearchField editor. */
69
71
  declare function readTokenSearchField(editor: HTMLElement, knownTokens?: readonly TokenSearchToken[]): TokenSearchFieldValue;
70
72
  /** Focus an editor and place its caret at a text offset, skipping atomic token chips. */
@@ -1,4 +1,4 @@
1
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-RNRUVPJ3.js';
1
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-63ZWBGE3.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=token-search-field.js.map
@@ -1,4 +1,4 @@
1
- export { ToolbarText } from './chunk-TZ5IHWB2.js';
1
+ export { ToolbarText } from './chunk-K6UNVKDK.js';
2
2
 
3
3
 
4
4