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

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 (282) hide show
  1. package/README.md +63 -32
  2. package/ai/application-ui-diagnostic-ids-v1.json +9 -1
  3. package/ai/application-ui-profile-sync.cjs +1 -1
  4. package/ai/application-ui-profile.defaults.json +63 -8
  5. package/ai/application-ui-profile.mjs +1 -1
  6. package/ai/component-catalog.json +208 -32
  7. package/ai/component-catalog.schema.json +2 -0
  8. package/ai/component-class-owners.cjs +83 -0
  9. package/ai/component-composition-extension.d.ts +7 -0
  10. package/ai/component-composition-extension.schema.json +7 -0
  11. package/ai/{component-catalog-v2-overrides.json → component-composition-rules.json} +2 -1
  12. package/ai/{component-catalog-v2.d.ts → component-composition.d.ts} +9 -5
  13. package/ai/{component-catalog-v2.json → component-composition.json} +155 -24
  14. package/ai/{component-catalog-v2.schema.json → component-composition.schema.json} +21 -5
  15. package/ai/components/README.md +170 -0
  16. package/ai/components/application-tabs.md +65 -0
  17. package/ai/components/badge.md +72 -0
  18. package/ai/components/collapsible-panel.md +102 -0
  19. package/ai/components/content-item.md +90 -0
  20. package/ai/components/disclosure-arrow.md +77 -0
  21. package/ai/components/document-baseline.md +75 -0
  22. package/ai/components/empty-state.md +80 -0
  23. package/ai/components/feedback.md +71 -0
  24. package/ai/components/floating-toolbar.md +80 -0
  25. package/ai/components/foundation.md +65 -0
  26. package/ai/components/grid.md +93 -0
  27. package/ai/components/headers.md +63 -0
  28. package/ai/components/layout.md +75 -0
  29. package/ai/components/list-action-row.md +91 -0
  30. package/ai/components/list-header.md +89 -0
  31. package/ai/components/list-inset-control.md +74 -0
  32. package/ai/components/list-inset-text.md +72 -0
  33. package/ai/components/list-item.md +88 -0
  34. package/ai/components/list.md +103 -0
  35. package/ai/components/loading-spinner.md +72 -0
  36. package/ai/components/lucide-icon.md +66 -0
  37. package/ai/components/nav-stack.md +103 -0
  38. package/ai/components/pane.md +100 -0
  39. package/ai/components/popup-menu.md +82 -0
  40. package/ai/components/recipe-app-shell.md +57 -0
  41. package/ai/components/recipe-collapsible-sidebar.md +57 -0
  42. package/ai/components/recipe-compact-toolbar.md +57 -0
  43. package/ai/components/recipe-composer-form.md +57 -0
  44. package/ai/components/recipe-list-detail-dialog.md +57 -0
  45. package/ai/components/recipe-list-workspace-states.md +57 -0
  46. package/ai/components/recipe-loading-inspector.md +57 -0
  47. package/ai/components/recipe-navigation-sidebar.md +57 -0
  48. package/ai/components/recipe-navigation-stack.md +57 -0
  49. package/ai/components/recipe-workspace-header.md +57 -0
  50. package/ai/components/resize.md +101 -0
  51. package/ai/components/row.md +96 -0
  52. package/ai/components/segmented-control.md +82 -0
  53. package/ai/components/select.md +107 -0
  54. package/ai/components/skeleton.md +84 -0
  55. package/ai/components/spacer.md +86 -0
  56. package/ai/components/split-view.md +102 -0
  57. package/ai/components/state-banner.md +92 -0
  58. package/ai/components/sunken-panel.md +79 -0
  59. package/ai/components/surface-scaffold.md +78 -0
  60. package/ai/components/tab-bar.md +112 -0
  61. package/ai/components/tab-scaffold.md +94 -0
  62. package/ai/components/tabs.md +90 -0
  63. package/ai/components/text.md +82 -0
  64. package/ai/components/token-search-field.md +101 -0
  65. package/ai/components/toolbar-control-group.md +100 -0
  66. package/ai/components/toolbar-text.md +76 -0
  67. package/ai/components/toolbar.md +95 -0
  68. package/ai/components/value-table.md +72 -0
  69. package/ai/components/wa-accordion-item.md +55 -0
  70. package/ai/components/wa-accordion.md +65 -0
  71. package/ai/components/wa-animated-image.md +55 -0
  72. package/ai/components/wa-animation.md +55 -0
  73. package/ai/components/wa-avatar.md +55 -0
  74. package/ai/components/wa-badge.md +55 -0
  75. package/ai/components/wa-breadcrumb-item.md +53 -0
  76. package/ai/components/wa-breadcrumb.md +55 -0
  77. package/ai/components/wa-button-group.md +59 -0
  78. package/ai/components/wa-button.md +55 -0
  79. package/ai/components/wa-callout.md +58 -0
  80. package/ai/components/wa-card.md +59 -0
  81. package/ai/components/wa-carousel-item.md +53 -0
  82. package/ai/components/wa-carousel.md +55 -0
  83. package/ai/components/wa-checkbox-group.md +55 -0
  84. package/ai/components/wa-checkbox.md +55 -0
  85. package/ai/components/wa-color-picker.md +55 -0
  86. package/ai/components/wa-comparison.md +55 -0
  87. package/ai/components/wa-copy-button.md +55 -0
  88. package/ai/components/wa-details.md +61 -0
  89. package/ai/components/wa-dialog.md +59 -0
  90. package/ai/components/wa-divider.md +53 -0
  91. package/ai/components/wa-drawer.md +55 -0
  92. package/ai/components/wa-dropdown-item.md +60 -0
  93. package/ai/components/wa-dropdown.md +60 -0
  94. package/ai/components/wa-format-bytes.md +53 -0
  95. package/ai/components/wa-format-date.md +53 -0
  96. package/ai/components/wa-format-number.md +53 -0
  97. package/ai/components/wa-icon.md +58 -0
  98. package/ai/components/wa-include.md +53 -0
  99. package/ai/components/wa-input.md +59 -0
  100. package/ai/components/wa-intersection-observer.md +53 -0
  101. package/ai/components/wa-known-date.md +53 -0
  102. package/ai/components/wa-markdown.md +53 -0
  103. package/ai/components/wa-mutation-observer.md +53 -0
  104. package/ai/components/wa-number-input.md +55 -0
  105. package/ai/components/wa-option.md +59 -0
  106. package/ai/components/wa-otp-input.md +53 -0
  107. package/ai/components/wa-page.md +53 -0
  108. package/ai/components/wa-pagination.md +55 -0
  109. package/ai/components/wa-popover.md +55 -0
  110. package/ai/components/wa-popup.md +53 -0
  111. package/ai/components/wa-progress-bar.md +53 -0
  112. package/ai/components/wa-progress-ring.md +53 -0
  113. package/ai/components/wa-qr-code.md +53 -0
  114. package/ai/components/wa-radio-group.md +59 -0
  115. package/ai/components/wa-radio.md +55 -0
  116. package/ai/components/wa-random-content.md +53 -0
  117. package/ai/components/wa-rating.md +55 -0
  118. package/ai/components/wa-relative-time.md +53 -0
  119. package/ai/components/wa-resize-observer.md +53 -0
  120. package/ai/components/wa-scroller.md +53 -0
  121. package/ai/components/wa-select.md +63 -0
  122. package/ai/components/wa-skeleton.md +53 -0
  123. package/ai/components/wa-slider.md +55 -0
  124. package/ai/components/wa-spinner.md +58 -0
  125. package/ai/components/wa-split-panel.md +58 -0
  126. package/ai/components/wa-switch.md +53 -0
  127. package/ai/components/wa-tab-group.md +69 -0
  128. package/ai/components/wa-tab-panel.md +60 -0
  129. package/ai/components/wa-tab.md +59 -0
  130. package/ai/components/wa-tag.md +59 -0
  131. package/ai/components/wa-textarea.md +53 -0
  132. package/ai/components/wa-time-input.md +55 -0
  133. package/ai/components/wa-toast-item.md +60 -0
  134. package/ai/components/wa-toast.md +60 -0
  135. package/ai/components/wa-tooltip.md +55 -0
  136. package/ai/components/wa-tree-item.md +55 -0
  137. package/ai/components/wa-tree.md +55 -0
  138. package/ai/components/wa-zoomable-frame.md +55 -0
  139. package/ai/components/workbench.md +114 -0
  140. package/ai/public-api-signatures-v1.md +161 -32
  141. package/ai/skill.md +100 -48
  142. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  143. package/analyzer/component-ownership.d.mts +56 -0
  144. package/analyzer/component-ownership.mjs +184 -0
  145. package/analyzer/index.d.mts +3 -0
  146. package/analyzer/index.mjs +274 -20
  147. package/analyzer/selectors.d.mts +21 -0
  148. package/analyzer/selectors.mjs +212 -0
  149. package/dist/browser/catalog.js +25 -0
  150. package/dist/browser/collapsible-panel.js +7 -0
  151. package/dist/browser/nav-stack.js +8 -0
  152. package/dist/browser/split-view.js +10 -0
  153. package/dist/browser/tab-scaffold.js +4 -0
  154. package/dist/browser/workbench.js +10 -0
  155. package/dist/catalog-resources.d.ts +2 -2
  156. package/dist/catalog.d.ts +3 -3
  157. package/dist/catalog.js +7 -6
  158. package/dist/catalog.js.map +1 -1
  159. package/dist/{chunk-PK4N5QTO.js → chunk-553ASUK6.js} +2 -3
  160. package/dist/chunk-553ASUK6.js.map +1 -0
  161. package/dist/{chunk-G33XC2NB.js → chunk-6YFHCLHG.js} +2 -2
  162. package/dist/chunk-6YFHCLHG.js.map +1 -0
  163. package/dist/{chunk-T4UZTNZ4.js → chunk-EVYKTVBQ.js} +2 -11
  164. package/dist/chunk-EVYKTVBQ.js.map +1 -0
  165. package/dist/{chunk-AK67F6QN.js → chunk-EZVNC4J6.js} +4 -1
  166. package/dist/chunk-EZVNC4J6.js.map +1 -0
  167. package/dist/{chunk-MA6Z6EIM.js → chunk-F4QMLOXJ.js} +3 -2
  168. package/dist/chunk-F4QMLOXJ.js.map +1 -0
  169. package/dist/{chunk-QQOTQTDD.js → chunk-G2RKFVRB.js} +13 -10
  170. package/dist/chunk-G2RKFVRB.js.map +1 -0
  171. package/dist/chunk-JCV4GXWB.js +249 -0
  172. package/dist/chunk-JCV4GXWB.js.map +1 -0
  173. package/dist/chunk-K47P7QZC.js +17 -0
  174. package/dist/chunk-K47P7QZC.js.map +1 -0
  175. package/dist/{chunk-MOBRFZH3.js → chunk-OUK6QRDR.js} +2 -12
  176. package/dist/chunk-OUK6QRDR.js.map +1 -0
  177. package/dist/{chunk-O3EXIBEZ.js → chunk-PWXNJS2B.js} +7 -6
  178. package/dist/chunk-PWXNJS2B.js.map +1 -0
  179. package/dist/{chunk-VNPQDCQZ.js → chunk-QOWG4ZCL.js} +3 -2
  180. package/dist/chunk-QOWG4ZCL.js.map +1 -0
  181. package/dist/{chunk-V65EOWNF.js → chunk-XCO6YCYM.js} +5 -4
  182. package/dist/chunk-XCO6YCYM.js.map +1 -0
  183. package/dist/{chunk-K6UNVKDK.js → chunk-XDKAYSVD.js} +3 -1
  184. package/dist/chunk-XDKAYSVD.js.map +1 -0
  185. package/dist/collapsible-panel.d.ts +3 -3
  186. package/dist/collapsible-panel.js +2 -2
  187. package/dist/collapsible-panel.js.map +1 -1
  188. package/dist/index.d.ts +2 -1
  189. package/dist/index.js +10 -8
  190. package/dist/list-action-row.js +2 -1
  191. package/dist/list-header.js +1 -1
  192. package/dist/list-item.js +2 -1
  193. package/dist/nav-stack.d.ts +13 -1
  194. package/dist/nav-stack.js +1 -1
  195. package/dist/pane.d.ts +20 -3
  196. package/dist/pane.js +1 -1
  197. package/dist/{panel-toolbar-C8sF-YZC.d.ts → panel-toolbar-BN4x0rth.d.ts} +3 -2
  198. package/dist/select.js +1 -1
  199. package/dist/split-view.d.ts +3 -3
  200. package/dist/split-view.js +3 -2
  201. package/dist/split-view.js.map +1 -1
  202. package/dist/state-banner.js +1 -1
  203. package/dist/styles/app-tab.css +31 -0
  204. package/dist/styles/badge.css +19 -11
  205. package/dist/styles/collapsible-panel.css +13 -9
  206. package/dist/styles/floating-toolbar.css +25 -6
  207. package/dist/styles/list-header.css +0 -12
  208. package/dist/styles/list.css +4 -4
  209. package/dist/styles/nav-stack.css +46 -19
  210. package/dist/styles/pane.css +50 -0
  211. package/dist/styles/popup-menu.css +151 -0
  212. package/dist/styles/resizable-region.css +22 -10
  213. package/dist/styles/select.css +28 -14
  214. package/dist/styles/skeleton.css +30 -3
  215. package/dist/styles/split-view.css +10 -7
  216. package/dist/styles/state-banner.css +0 -19
  217. package/dist/styles/surface-scaffold.css +8 -3
  218. package/dist/styles/tab-bar.css +79 -28
  219. package/dist/styles/tab-scaffold.css +25 -1
  220. package/dist/styles/text.css +16 -0
  221. package/dist/styles/toolbar-control-group.css +121 -66
  222. package/dist/styles/toolbar-text.css +21 -0
  223. package/dist/styles/toolbar.css +8 -32
  224. package/dist/styles/workbench.css +18 -18
  225. package/dist/tab-scaffold.d.ts +11 -1
  226. package/dist/tab-scaffold.js +2 -0
  227. package/dist/tab-scaffold.js.map +1 -1
  228. package/dist/toolbar-text.d.ts +7 -1
  229. package/dist/toolbar-text.js +1 -1
  230. package/dist/toolbar.d.ts +8 -1
  231. package/dist/toolbar.js +1 -1
  232. package/dist/{types-DXAGogOS.d.ts → types-BuHELdXR.d.ts} +1 -1
  233. package/dist/wire-catalog.d.ts +3 -1
  234. package/dist/wire-catalog.js +2 -0
  235. package/dist/wire-catalog.js.map +1 -1
  236. package/dist/wire-scroll-dividers.d.ts +69 -0
  237. package/dist/wire-scroll-dividers.js +3 -0
  238. package/dist/wire-scroll-dividers.js.map +1 -0
  239. package/dist/workbench.d.ts +13 -12
  240. package/dist/workbench.js +1 -1
  241. package/docs/app-layouts.md +9 -1
  242. package/docs/catalog.md +10 -5
  243. package/docs/collapsible-panel.md +10 -6
  244. package/docs/component-contract.md +147 -21
  245. package/docs/component-selection.md +18 -6
  246. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -1
  247. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -1
  248. package/docs/design/templates/tab-bar-dark.svg +1 -1
  249. package/docs/design/templates/tab-bar.svg +1 -1
  250. package/docs/design-philosophy.md +30 -3
  251. package/docs/examples/application-ui-profile.json +4 -4
  252. package/docs/examples/{component-catalog-extension-v2.json → component-composition-extension.json} +3 -3
  253. package/docs/layout.md +82 -3
  254. package/docs/nav-stack.md +33 -3
  255. package/docs/split-view.md +10 -5
  256. package/docs/tab-scaffold.md +23 -1
  257. package/docs/type-contracts.md +4 -3
  258. package/docs/ui-analyzer.md +99 -5
  259. package/docs/ui-doctor.md +10 -0
  260. package/docs/workbench.md +19 -9
  261. package/doctor/index.mjs +6 -1
  262. package/llms.txt +85 -20
  263. package/package.json +17 -5
  264. package/ux-demo/recipes/collapsible-sidebar.tsx +0 -1
  265. package/ux-demo/recipes/composer-form.tsx +0 -1
  266. package/ux-demo/recipes/list-detail-dialog.tsx +0 -2
  267. package/ux-demo/recipes/list-workspace-states.tsx +0 -1
  268. package/ux-demo/recipes/loading-inspector.tsx +0 -1
  269. package/ux-demo/recipes/workspace-header.tsx +0 -1
  270. package/ai/component-catalog-extension-v2.d.ts +0 -7
  271. package/ai/component-catalog-extension-v2.schema.json +0 -7
  272. package/dist/chunk-AK67F6QN.js.map +0 -1
  273. package/dist/chunk-G33XC2NB.js.map +0 -1
  274. package/dist/chunk-K6UNVKDK.js.map +0 -1
  275. package/dist/chunk-MA6Z6EIM.js.map +0 -1
  276. package/dist/chunk-MOBRFZH3.js.map +0 -1
  277. package/dist/chunk-O3EXIBEZ.js.map +0 -1
  278. package/dist/chunk-PK4N5QTO.js.map +0 -1
  279. package/dist/chunk-QQOTQTDD.js.map +0 -1
  280. package/dist/chunk-T4UZTNZ4.js.map +0 -1
  281. package/dist/chunk-V65EOWNF.js.map +0 -1
  282. package/dist/chunk-VNPQDCQZ.js.map +0 -1
@@ -21,15 +21,17 @@
21
21
  )
22
22
  );
23
23
  --kui-toolbar-item-radius: var(--kui-control-highlight-radius);
24
- /* Internal group-to-child contract: the control slot a composed child (an
25
- icon-only Select) fills so it is inset evenly on every side and the
26
- group-owned focus ring stays concentric with the visible control. The
27
- group sizes to the child's width. A default slot is 40px tall inside the
28
- 2px inset; a compact slot is the full 32px padding box, and a lone
29
- compact child also overlaps the 1px inline padding. */
30
- --_kui-toolbar-slot-height: 2.5rem;
31
- --_kui-toolbar-slot-overlap: 0px;
32
- --_kui-toolbar-slot-radius: var(--kui-toolbar-item-radius);
24
+ /* Context the group provides to a composed child (an icon-only Select, in
25
+ select.css): the control slot the child fills so it is inset evenly on
26
+ every side and the group-owned focus ring stays concentric with the
27
+ visible control. The group sizes to the child's width. A default slot is
28
+ 40px tall inside the 2px inset; a compact slot is the full 32px padding
29
+ box, and a lone compact child also overlaps the 1px inline padding. The
30
+ values are named after the group that provides them; the child reads
31
+ them from its own stylesheet. */
32
+ --_kui-toolbar-control-group-slot-height: 2.5rem;
33
+ --_kui-toolbar-control-group-slot-overlap: 0px;
34
+ --_kui-toolbar-control-group-slot-radius: var(--kui-toolbar-item-radius);
33
35
  display: inline-flex;
34
36
  height: var(--kui-toolbar-group-size, calc(2px + 2.625rem));
35
37
  min-width: var(--kui-toolbar-group-size, calc(2px + 2.625rem));
@@ -44,6 +46,23 @@
44
46
  gap: 0;
45
47
  }
46
48
 
49
+ /* In a Toolbar zone, a group shorter than the toolbar's control band (the
50
+ `--_kui-toolbar-band` the Toolbar provides) takes half the difference as a
51
+ top margin, so it centers in the band by its own size. */
52
+ :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing)
53
+ > .kui-toolbar-control-group {
54
+ margin-block-start: max(
55
+ 0px,
56
+ calc(
57
+ (
58
+ var(--_kui-toolbar-band) -
59
+ var(--kui-toolbar-group-size, calc(2px + 2.625rem))
60
+ ) /
61
+ 2
62
+ )
63
+ );
64
+ }
65
+
47
66
  .kui-toolbar-control-group[data-visibility="compact-only"] {
48
67
  display: none;
49
68
  }
@@ -80,9 +99,9 @@
80
99
  }
81
100
  .kui-toolbar-control-group[data-size="compact"] {
82
101
  --kui-toolbar-group-size: 2.125rem;
83
- --_kui-toolbar-slot-height: 2rem;
84
- --_kui-toolbar-slot-overlap: 1px;
85
- --_kui-toolbar-slot-radius: max(
102
+ --_kui-toolbar-control-group-slot-height: 2rem;
103
+ --_kui-toolbar-control-group-slot-overlap: 1px;
104
+ --_kui-toolbar-control-group-slot-radius: max(
86
105
  0px,
87
106
  calc(var(--kui-toolbar-group-radius) - 1px)
88
107
  );
@@ -121,13 +140,15 @@
121
140
  display: flex;
122
141
  gap: var(--kui-space-xs);
123
142
  }
124
- .kui-toolbar-control-group[data-content="mixed"] wa-button {
143
+ .kui-toolbar-control-group[data-content="mixed"]
144
+ wa-button:not(:where(.kui-popup-menu > *)) {
125
145
  line-height: normal;
126
146
  }
127
147
  /* Toolbar controls never wrap. Web Awesome's label part is a plain block, so a
128
148
  slotted icon (a block SVG) and its text would otherwise stack vertically —
129
149
  lay them out on one row the way native mixed buttons already are. */
130
- .kui-toolbar-control-group wa-button::part(label) {
150
+ .kui-toolbar-control-group
151
+ wa-button:not(:where(.kui-popup-menu > *))::part(label) {
131
152
  display: inline-flex;
132
153
  align-items: center;
133
154
  gap: var(--kui-space-xs);
@@ -183,11 +204,15 @@
183
204
  background: transparent;
184
205
  }
185
206
 
186
- .kui-toolbar-control-group
187
- > :is(button, wa-button, wa-dropdown, .kui-toolbar-action-link) {
207
+ /* The group lays out its own native and raw Web Awesome children. A composed
208
+ PopupMenu (`wa-dropdown.kui-popup-menu`) or Select sizes itself in the
209
+ group's context from its own stylesheet (popup-menu.css, select.css); the
210
+ group names those children by class only to key its own state on them. */
211
+ .kui-toolbar-control-group > :is(button, wa-button, .kui-toolbar-action-link),
212
+ .kui-toolbar-control-group > wa-dropdown:not(.kui-popup-menu) {
188
213
  flex: 0 0 auto;
189
214
  }
190
- .kui-toolbar-control-group > wa-dropdown {
215
+ .kui-toolbar-control-group > wa-dropdown:not(.kui-popup-menu) {
191
216
  display: inline-flex;
192
217
  }
193
218
 
@@ -268,7 +293,7 @@
268
293
  }
269
294
 
270
295
  .kui-toolbar-control-group > button svg,
271
- .kui-toolbar-control-group wa-button > svg,
296
+ .kui-toolbar-control-group wa-button:not(:where(.kui-popup-menu > *)) > svg,
272
297
  .kui-toolbar-control-group > .kui-toolbar-action-link svg {
273
298
  display: block;
274
299
  width: 1rem;
@@ -276,34 +301,43 @@
276
301
  }
277
302
 
278
303
  /* Nested Web Awesome buttons take the group's control height, not the theme's
279
- 44px standalone control height. */
280
- .kui-toolbar-control-group wa-button {
304
+ 44px standalone control height. The group's `wa-button` rules style only raw
305
+ buttons: a composed PopupMenu's trigger (`.kui-popup-menu > wa-button`) is
306
+ excluded inside a zero-specificity `:where()` and takes the same metrics
307
+ from popup-menu.css. */
308
+ .kui-toolbar-control-group wa-button:not(:where(.kui-popup-menu > *)) {
281
309
  --wa-form-control-height: 2.5rem;
282
310
  }
283
- .kui-toolbar-control-group[data-size="compact"] wa-button {
311
+ .kui-toolbar-control-group[data-size="compact"]
312
+ wa-button:not(:where(.kui-popup-menu > *)) {
284
313
  --wa-form-control-height: 2rem;
285
314
  }
286
315
 
287
316
  .kui-toolbar-control-group > wa-button,
288
- .kui-toolbar-control-group > wa-dropdown,
289
- .kui-toolbar-control-group > wa-dropdown > wa-button,
290
- .kui-toolbar-control-group > wa-dropdown > wa-button::part(base) {
317
+ .kui-toolbar-control-group > wa-dropdown:not(.kui-popup-menu),
318
+ .kui-toolbar-control-group > wa-dropdown:not(.kui-popup-menu) > wa-button,
319
+ .kui-toolbar-control-group
320
+ > wa-dropdown:not(.kui-popup-menu)
321
+ > wa-button::part(base) {
291
322
  min-width: 2.5rem;
292
323
  height: 2.5rem;
293
324
  }
294
325
  .kui-toolbar-control-group[data-size="compact"] > button,
295
326
  .kui-toolbar-control-group[data-size="compact"] > .kui-toolbar-action-link,
296
327
  .kui-toolbar-control-group[data-size="compact"] > wa-button,
297
- .kui-toolbar-control-group[data-size="compact"] > wa-dropdown,
298
- .kui-toolbar-control-group[data-size="compact"] > wa-dropdown > wa-button,
299
328
  .kui-toolbar-control-group[data-size="compact"]
300
- > wa-dropdown
329
+ > wa-dropdown:not(.kui-popup-menu),
330
+ .kui-toolbar-control-group[data-size="compact"]
331
+ > wa-dropdown:not(.kui-popup-menu)
332
+ > wa-button,
333
+ .kui-toolbar-control-group[data-size="compact"]
334
+ > wa-dropdown:not(.kui-popup-menu)
301
335
  > wa-button::part(base),
302
336
  .kui-toolbar-control-group[data-size="compact"][data-nested-dropdown="true"]
303
- wa-dropdown
337
+ wa-dropdown:not(.kui-popup-menu)
304
338
  wa-button,
305
339
  .kui-toolbar-control-group[data-size="compact"][data-nested-dropdown="true"]
306
- wa-dropdown
340
+ wa-dropdown:not(.kui-popup-menu)
307
341
  wa-button::part(base) {
308
342
  min-width: 2rem;
309
343
  height: 2rem;
@@ -320,16 +354,17 @@
320
354
  /* `standard` keeps each menu's own standard inset: a raw wa-dropdown takes the
321
355
  8px surface inset, while a PopupMenu keeps the Select listbox inset it
322
356
  shares (popup-menu.css). The explicit compact and list-zero choices apply to
323
- every nested menu. */
357
+ every nested menu; a PopupMenu applies them to itself (popup-menu.css). */
324
358
  .kui-toolbar-control-group[data-menu-inset="standard"]
325
- wa-dropdown:not([data-component="popup-menu"])::part(menu) {
359
+ wa-dropdown:not(.kui-popup-menu)::part(menu) {
326
360
  padding: var(--kui-wa-surface-inset, 0.5rem);
327
361
  }
328
- .kui-toolbar-control-group[data-menu-inset="compact"] wa-dropdown::part(menu) {
362
+ .kui-toolbar-control-group[data-menu-inset="compact"]
363
+ wa-dropdown:not(.kui-popup-menu)::part(menu) {
329
364
  padding: 0.25rem;
330
365
  }
331
366
  .kui-toolbar-control-group[data-menu-inset="list-zero"]
332
- wa-dropdown::part(menu) {
367
+ wa-dropdown:not(.kui-popup-menu)::part(menu) {
333
368
  padding: 0;
334
369
  }
335
370
 
@@ -350,7 +385,7 @@
350
385
  }
351
386
 
352
387
  .kui-toolbar-control-group[data-size="compact"][data-content="mixed"]
353
- > wa-dropdown,
388
+ > wa-dropdown:not(.kui-popup-menu),
354
389
  .kui-toolbar-control-group[data-size="compact"][data-content="mixed"]
355
390
  > wa-button {
356
391
  margin: -2px;
@@ -361,26 +396,26 @@
361
396
  the fixed 32px base centers both outside its host, painting the label across
362
397
  the separator and the caret beyond the group border. */
363
398
  .kui-toolbar-control-group[data-content="mixed"][data-nested-dropdown="true"]
364
- > wa-dropdown
399
+ > wa-dropdown:not(.kui-popup-menu)
365
400
  > wa-button,
366
401
  .kui-toolbar-control-group[data-content="mixed"][data-nested-dropdown="true"]
367
- > wa-dropdown
402
+ > wa-dropdown:not(.kui-popup-menu)
368
403
  > wa-button::part(base) {
369
404
  min-width: max-content;
370
405
  }
371
406
  .kui-toolbar-control-group[data-content="mixed"][data-nested-dropdown="true"]
372
- > wa-dropdown
407
+ > wa-dropdown:not(.kui-popup-menu)
373
408
  > wa-button::part(base) {
374
409
  width: max-content;
375
410
  }
376
411
 
377
412
  .kui-toolbar-control-group[data-single="true"]
378
- > wa-dropdown
413
+ > wa-dropdown:not(.kui-popup-menu)
379
414
  > wa-button::part(base) {
380
415
  width: max-content;
381
416
  }
382
417
 
383
- .kui-toolbar-control-group wa-button {
418
+ .kui-toolbar-control-group wa-button:not(:where(.kui-popup-menu > *)) {
384
419
  display: inline-flex;
385
420
  line-height: 0;
386
421
  align-items: center;
@@ -388,7 +423,8 @@
388
423
  --wa-color-fill-quiet: transparent;
389
424
  }
390
425
 
391
- .kui-toolbar-control-group wa-button::part(base) {
426
+ .kui-toolbar-control-group
427
+ wa-button:not(:where(.kui-popup-menu > *))::part(base) {
392
428
  /* Match min-width to the 40px height so an icon-only button's highlight is at
393
429
  least a circle, not a vertical oval — the host is 40px wide but the base
394
430
  part was sizing to its ~32px content. Text/caret buttons grow past it. */
@@ -402,20 +438,29 @@
402
438
  );
403
439
  }
404
440
 
405
- .kui-toolbar-control-group wa-button:hover::part(base) {
441
+ .kui-toolbar-control-group
442
+ wa-button:not(:where(.kui-popup-menu > *)):hover::part(base) {
406
443
  border-color: var(--kui-toolbar-control-border);
407
444
  background-color: var(--kui-toolbar-control-hover-background);
408
445
  }
409
446
 
410
447
  .kui-toolbar-control-group[data-single="true"]:has(
411
- > :is(button, wa-button, wa-dropdown, wa-select, .kui-toolbar-action-link)
448
+ > :is(
449
+ button,
450
+ wa-button,
451
+ .kui-popup-menu,
452
+ wa-dropdown:not(.kui-popup-menu),
453
+ .kui-select,
454
+ .kui-toolbar-action-link
455
+ )
412
456
  ):hover {
413
457
  background-color: var(--kui-toolbar-control-hover-background);
414
458
  }
415
459
 
416
460
  .kui-toolbar-control-group[data-single="true"]
417
461
  > :is(button, wa-button, .kui-toolbar-action-link):hover,
418
- .kui-toolbar-control-group[data-single="true"] wa-button:hover::part(base) {
462
+ .kui-toolbar-control-group[data-single="true"]
463
+ wa-button:not(:where(.kui-popup-menu > *)):hover::part(base) {
419
464
  border-color: transparent;
420
465
  background: transparent;
421
466
  }
@@ -425,7 +470,7 @@
425
470
  inner layer inset from the border (KF-J9P1ZD — a heading toolbar's lone Close/
426
471
  Done action). */
427
472
  .kui-toolbar-control-group:not([data-single="true"]):has(
428
- > :is(button, wa-button, wa-select, .kui-toolbar-action-link):only-child
473
+ > :is(button, wa-button, .kui-select, .kui-toolbar-action-link):only-child
429
474
  ):hover {
430
475
  background-color: var(--kui-toolbar-control-hover-background);
431
476
  }
@@ -437,19 +482,13 @@
437
482
  .kui-toolbar-control-group:not([data-single="true"]):has(
438
483
  > :is(button, wa-button):only-child
439
484
  )
440
- wa-button:hover::part(base) {
485
+ > wa-button:hover::part(base) {
441
486
  border-color: transparent;
442
487
  background: transparent;
443
488
  }
444
489
 
445
- /* A toolbar Select beside other controls highlights its own pill on hover,
446
- like its button and dropdown siblings; a lone Select instead lifts the whole
447
- group (above). */
448
- .kui-toolbar-control-group:not([data-single="true"])
449
- > wa-select:not(:only-child):hover::part(combobox) {
450
- border-color: var(--kui-toolbar-control-border);
451
- background: var(--kui-toolbar-control-hover-background);
452
- }
490
+ /* A toolbar Select beside other controls highlights its own pill on hover
491
+ (select.css); a lone Select instead lifts the whole group (above). */
453
492
 
454
493
  /* A plain <button> child paints its own box for the selected state. */
455
494
  .kui-toolbar-control-group[data-single="false"] > button[aria-pressed="true"] {
@@ -481,25 +520,29 @@
481
520
  outline-offset: 1px;
482
521
  }
483
522
 
484
- /* With per-control rings, a composed Select paints its own ring even when it
485
- was told the group owns it: nothing else would show focus. It matches the
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. */
488
- .kui-toolbar-control-group[data-focus-ring="control"]
489
- > wa-select:not([open]):focus-within::part(combobox) {
490
- outline: var(--kui-focus-ring);
491
- outline-offset: 1px;
492
- }
523
+ /* With per-control rings, a composed Select paints its own ring (select.css). */
493
524
 
494
525
  .kui-toolbar-control-group[data-focus-ring="outline"]:focus-within:not(
495
- :where(:has(wa-select[open], wa-dropdown[open]))
526
+ :where(
527
+ :has(
528
+ .kui-select[open],
529
+ .kui-popup-menu[open],
530
+ wa-dropdown:not(.kui-popup-menu)[open]
531
+ )
532
+ )
496
533
  ) {
497
534
  outline: var(--kui-focus-ring);
498
535
  outline-offset: 1px;
499
536
  }
500
537
 
501
538
  .kui-toolbar-control-group[data-focus-ring="halo"]:focus-within:not(
502
- :where(:has(wa-select[open], wa-dropdown[open]))
539
+ :where(
540
+ :has(
541
+ .kui-select[open],
542
+ .kui-popup-menu[open],
543
+ wa-dropdown:not(.kui-popup-menu)[open]
544
+ )
545
+ )
503
546
  ) {
504
547
  border-color: var(--kui-color-text-link);
505
548
  box-shadow: 0 0 0 3px
@@ -517,14 +560,26 @@
517
560
  descendant also owns the group ring. Kept as separate rules so an engine
518
561
  without :has() keeps the :focus-within ring. */
519
562
  .kui-toolbar-control-group[data-focus-ring="outline"]:has(:focus):not(
520
- :where(:has(wa-select[open], wa-dropdown[open]))
563
+ :where(
564
+ :has(
565
+ .kui-select[open],
566
+ .kui-popup-menu[open],
567
+ wa-dropdown:not(.kui-popup-menu)[open]
568
+ )
569
+ )
521
570
  ) {
522
571
  outline: var(--kui-focus-ring);
523
572
  outline-offset: 1px;
524
573
  }
525
574
 
526
575
  .kui-toolbar-control-group[data-focus-ring="halo"]:has(:focus):not(
527
- :where(:has(wa-select[open], wa-dropdown[open]))
576
+ :where(
577
+ :has(
578
+ .kui-select[open],
579
+ .kui-popup-menu[open],
580
+ wa-dropdown:not(.kui-popup-menu)[open]
581
+ )
582
+ )
528
583
  ) {
529
584
  border-color: var(--kui-color-text-link);
530
585
  box-shadow: 0 0 0 3px
@@ -12,6 +12,27 @@
12
12
  align-items: center;
13
13
  }
14
14
 
15
+ /* In a Toolbar zone, a title fills the toolbar's control band (the
16
+ `--_kui-toolbar-band` the Toolbar provides) and centers its first line in
17
+ it; further lines of a wrapped title continue below the band. These rules
18
+ precede the size and wrap rules below, which keep their own minimum
19
+ heights. */
20
+ :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing)
21
+ > .kui-toolbar-text {
22
+ height: auto;
23
+ min-height: var(--_kui-toolbar-band);
24
+ }
25
+
26
+ :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing)
27
+ > .kui-toolbar-text[data-wrap="true"] {
28
+ padding-block: max(0px, calc((var(--_kui-toolbar-band) - 1.25em) / 2));
29
+ }
30
+
31
+ /* A filling title grows into its row's free space and truncates there. */
32
+ .kui-toolbar-text[data-fill="true"] {
33
+ flex: 1 1 auto;
34
+ }
35
+
15
36
  /* The text lives in a block child, not directly in the flex box, so
16
37
  `text-overflow: ellipsis` actually applies (it is a no-op on a flex item). */
17
38
  .kui-toolbar-text__text {
@@ -104,28 +104,28 @@
104
104
  inset 1px 0 var(--_kui-toolbar-divider-left);
105
105
  }
106
106
 
107
- .kui-toolbar[divider-sides*="t"] {
107
+ .kui-toolbar:is([divider-sides*="t"], [data-scroll-divider*="t"]) {
108
108
  --_kui-toolbar-divider-top: var(
109
109
  --kui-toolbar-divider-color,
110
110
  var(--kui-color-neutral-border-normal)
111
111
  );
112
112
  }
113
113
 
114
- .kui-toolbar[divider-sides*="r"] {
114
+ .kui-toolbar:is([divider-sides*="r"], [data-scroll-divider*="r"]) {
115
115
  --_kui-toolbar-divider-right: var(
116
116
  --kui-toolbar-divider-color,
117
117
  var(--kui-color-neutral-border-normal)
118
118
  );
119
119
  }
120
120
 
121
- .kui-toolbar[divider-sides*="b"] {
121
+ .kui-toolbar:is([divider-sides*="b"], [data-scroll-divider*="b"]) {
122
122
  --_kui-toolbar-divider-bottom: var(
123
123
  --kui-toolbar-divider-color,
124
124
  var(--kui-color-neutral-border-normal)
125
125
  );
126
126
  }
127
127
 
128
- .kui-toolbar[divider-sides*="l"] {
128
+ .kui-toolbar:is([divider-sides*="l"], [data-scroll-divider*="l"]) {
129
129
  --_kui-toolbar-divider-left: var(
130
130
  --kui-toolbar-divider-color,
131
131
  var(--kui-color-neutral-border-normal)
@@ -140,7 +140,10 @@
140
140
  down. A growing item therefore never moves the other items or its own top.
141
141
  Centering the zones in the toolbar row instead dropped every control to the
142
142
  middle of a two-line title. The band is resolved here, on the toolbar, so a
143
- compact group's own smaller group size never redefines it. */
143
+ compact group's own smaller group size never redefines it.
144
+ `--_kui-toolbar-band` is context the toolbar provides: a ToolbarControlGroup
145
+ and a ToolbarText each center themselves in it from their own stylesheets
146
+ (toolbar-control-group.css, toolbar-text.css). */
144
147
  .kui-toolbar {
145
148
  --_kui-toolbar-band: var(--kui-toolbar-group-size, calc(2px + 2.625rem));
146
149
  }
@@ -155,33 +158,6 @@
155
158
  gap: var(--kui-toolbar-gap, 0.5rem);
156
159
  }
157
160
 
158
- :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing)
159
- > [data-component="toolbar-control-group"] {
160
- margin-block-start: max(
161
- 0px,
162
- calc(
163
- (
164
- var(--_kui-toolbar-band) -
165
- var(--kui-toolbar-group-size, calc(2px + 2.625rem))
166
- ) /
167
- 2
168
- )
169
- );
170
- }
171
-
172
- /* A title fills the band and centers its first line in it; further lines of a
173
- wrapped title continue below the band. */
174
- :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing)
175
- > [data-component="toolbar-text"] {
176
- height: auto;
177
- min-height: var(--_kui-toolbar-band);
178
- }
179
-
180
- :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing)
181
- > [data-component="toolbar-text"][data-wrap="true"] {
182
- padding-block: max(0px, calc((var(--_kui-toolbar-band) - 1.25em) / 2));
183
- }
184
-
185
161
  /* Pin each zone to its column so a display:none (empty) leading or center zone
186
162
  doesn't let the trailing zone auto-place into the first column — which left an
187
163
  extra gap + empty track to the trailing zone's right (KF-5BEF2M). The trailing
@@ -191,15 +191,11 @@
191
191
 
192
192
  /* A region whose only child is a Pane or layout lets it fill the region, so a
193
193
  composed toolbar's footer (or the app's own) sits at the region's bottom
194
- edge rather than under short content. */
194
+ edge rather than under short content. Each of those components fills the
195
+ region from its own stylesheet (pane.css, nav-stack.css, split-view.css,
196
+ tab-scaffold.css); a nested Workbench fills it here. */
195
197
  :is(.kui-workbench__main, .kui-workbench__panel-content)
196
- > :is(
197
- [data-component="pane"],
198
- [data-component="nav-stack"],
199
- [data-component="split-view"],
200
- [data-component="workbench"],
201
- [data-component="tab-scaffold"]
202
- ):only-child {
198
+ > [data-component="workbench"]:only-child {
203
199
  height: 100%;
204
200
  }
205
201
 
@@ -777,12 +773,15 @@
777
773
  --kui-workbench-restore-inset,
778
774
  1rem
779
775
  );
780
- /* The corner owns the inset, so a FloatingToolbar composed here floats
781
- from this point instead of adding its own inset on top. */
782
- --kui-floating-toolbar-inset: 0px;
783
776
  position: absolute;
784
777
  /* Hidden while an open rail overlay covers the work area (see below). */
785
- visibility: var(--_kui-floating-covered, inherit);
778
+ visibility: var(
779
+ --_kui-workbench-covered,
780
+ var(
781
+ --_kui-collapsible-panel-covered,
782
+ var(--_kui-resizable-region-covered, inherit)
783
+ )
784
+ );
786
785
  z-index: var(
787
786
  --kui-workbench-restore-z,
788
787
  calc(var(--kui-workbench-overlay-z, 41) - 2)
@@ -856,9 +855,10 @@
856
855
  restore corner) are hidden, not merely stacked beneath it: they never paint
857
856
  over the overlay whatever stacking context an app puts them in, and hidden
858
857
  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
858
+ overlay closes. The Workbench's own covered context,
859
+ `--_kui-workbench-covered`, is set on the rail's siblings only (never on
860
+ the open rail), so the overlay's own floating controls stay; FloatingToolbar
861
+ and the layouts' restore corners read it. A direct child
862
862
  resolves the Workbench's own container query, never a nested one's. */
863
863
  .kui-workbench:has(
864
864
  > .kui-workbench__rail[data-presentation="overlay"]:not(
@@ -866,7 +866,7 @@
866
866
  )
867
867
  )
868
868
  > :is(.kui-workbench__center, .kui-workbench__restore) {
869
- --_kui-floating-covered: hidden;
869
+ --_kui-workbench-covered: hidden;
870
870
  }
871
871
 
872
872
  @container kui-workbench (max-width: 44rem) {
@@ -876,7 +876,7 @@
876
876
  )
877
877
  )
878
878
  > :is(.kui-workbench__center, .kui-workbench__restore) {
879
- --_kui-floating-covered: hidden;
879
+ --_kui-workbench-covered: hidden;
880
880
  }
881
881
  }
882
882
 
@@ -887,7 +887,7 @@
887
887
  )
888
888
  )
889
889
  > :is(.kui-workbench__center, .kui-workbench__restore) {
890
- --_kui-floating-covered: hidden;
890
+ --_kui-workbench-covered: hidden;
891
891
  }
892
892
  }
893
893
 
@@ -50,6 +50,16 @@ interface TabScaffoldProps<Id extends string = string> {
50
50
  tabs: readonly TabScaffoldTab<Id>[];
51
51
  /** The controlled active tab id (the app owns selection). */
52
52
  active: NoInfer<Id>;
53
+ /**
54
+ * The line over the tab bar, where it meets the active scene. `scroll`
55
+ * (default) shows it only while more of the scene's content lies below —
56
+ * never at the scroll end or when the content fits — once
57
+ * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above
58
+ * the scaffold; unwired, it does not show. `always` shows it without the
59
+ * wiring; `none` never, even when wired. The line is the bar's own 1px top
60
+ * border, so no state moves the bar or a tab.
61
+ */
62
+ chromeDividers?: 'scroll' | 'always' | 'none';
53
63
  className?: string;
54
64
  /** Native named-slot assignment when composed inside a web component. */
55
65
  slot?: string;
@@ -62,6 +72,6 @@ interface TabScaffoldProps<Id extends string = string> {
62
72
  * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of
63
73
  * a bottom bar. See `docs/23-app-layouts.md` §3.4.
64
74
  */
65
- declare function TabScaffold<Id extends string>({ id, label, tabs, active, className, slot, }: TabScaffoldProps<Id>): SafeHtml;
75
+ declare function TabScaffold<Id extends string>({ id, label, tabs, active, chromeDividers, className, slot, }: TabScaffoldProps<Id>): SafeHtml;
66
76
 
67
77
  export { TabScaffold, type TabScaffoldProps, type TabScaffoldTab };
@@ -6,6 +6,7 @@ function TabScaffold({
6
6
  label,
7
7
  tabs,
8
8
  active,
9
+ chromeDividers = "scroll",
9
10
  className = "",
10
11
  slot
11
12
  }) {
@@ -16,6 +17,7 @@ function TabScaffold({
16
17
  id,
17
18
  "data-component": "tab-scaffold",
18
19
  "data-tab-scaffold-id": id,
20
+ "data-chrome-dividers": chromeDividers === "always" || chromeDividers === "none" ? chromeDividers : void 0,
19
21
  slot,
20
22
  children: [
21
23
  /* @__PURE__ */ jsx("div", { class: "kui-tab-scaffold__scenes", children: tabs.map((tab) => /* @__PURE__ */ jsx(
@@ -1 +1 @@
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
+ {"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 /**\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` never, even when wired. The line is the bar's own 1px top\n * border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\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 chromeDividers = 'scroll',\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 data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\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"]}
@@ -23,6 +23,12 @@ interface ToolbarTextBaseProps {
23
23
  * instead. Default true.
24
24
  */
25
25
  ellipsis?: boolean;
26
+ /**
27
+ * Grow to fill the free space of the flex row it sits in (a Toolbar zone),
28
+ * truncating at the space left by its siblings instead of taking only its
29
+ * text's width. Default false.
30
+ */
31
+ fill?: boolean;
26
32
  /** Native named-slot assignment when composed inside a web component. */
27
33
  slot?: string;
28
34
  }
@@ -35,6 +41,6 @@ type ToolbarTextWrappingProps = {
35
41
  maxLines?: never;
36
42
  };
37
43
  type ToolbarTextProps = ToolbarTextBaseProps & ToolbarTextWrappingProps;
38
- declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, slot, }: ToolbarTextProps): kerfjs.SafeHtml;
44
+ declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, fill, slot, }: ToolbarTextProps): kerfjs.SafeHtml;
39
45
 
40
46
  export { type HeadingLevel, ToolbarText, type ToolbarTextProps, type ToolbarTextSize };