@kerfjs/ui 5.0.0-beta.61 → 5.0.0-beta.63

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 (191) hide show
  1. package/README.md +4 -0
  2. package/ai/component-catalog-extension.schema.json +13 -0
  3. package/ai/component-catalog.json +20 -7
  4. package/ai/component-composition.json +17 -3
  5. package/ai/components/list-item.md +1 -1
  6. package/ai/components/pane.md +3 -2
  7. package/ai/components/select.md +1 -0
  8. package/ai/components/toolbar-text.md +2 -1
  9. package/ai/components/workbench.md +3 -2
  10. package/ai/public-api-signatures-v1.md +162 -48
  11. package/ai/skill.md +11 -5
  12. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  13. package/analyzer/index.mjs +3 -2
  14. package/catalog-projection-cli.mjs +96 -0
  15. package/catalog-projection.d.mts +55 -0
  16. package/catalog-projection.mjs +62 -0
  17. package/dist/browser/catalog.js +1 -1
  18. package/dist/browser/collapsible-panel.js +2 -0
  19. package/dist/browser/nav-stack.js +1 -0
  20. package/dist/browser/split-view.js +1 -0
  21. package/dist/browser/toolbar-control-group.js +1 -0
  22. package/dist/browser/workbench.js +1 -0
  23. package/dist/catalog-resources.d.ts +1 -1
  24. package/dist/catalog.d.ts +1 -1
  25. package/dist/catalog.js +11 -10
  26. package/dist/catalog.js.map +1 -1
  27. package/dist/chip.d.ts +9 -3
  28. package/dist/chip.js +1 -1
  29. package/dist/{chunk-EZVNC4J6.js → chunk-3BYFWTP3.js} +7 -3
  30. package/dist/chunk-3BYFWTP3.js.map +1 -0
  31. package/dist/{chunk-5Q3N6EV7.js → chunk-5IJ6UGNZ.js} +3 -3
  32. package/dist/{chunk-5Q3N6EV7.js.map → chunk-5IJ6UGNZ.js.map} +1 -1
  33. package/dist/chunk-7AKGPBCC.js +76 -0
  34. package/dist/chunk-7AKGPBCC.js.map +1 -0
  35. package/dist/{chunk-KROD7KZK.js → chunk-AUVG54KC.js} +2 -2
  36. package/dist/{chunk-KROD7KZK.js.map → chunk-AUVG54KC.js.map} +1 -1
  37. package/dist/{chunk-AUKT3KMG.js → chunk-CQHN45XR.js} +17 -2
  38. package/dist/chunk-CQHN45XR.js.map +1 -0
  39. package/dist/{chunk-SWVW56CV.js → chunk-EN4LDR5Y.js} +2 -2
  40. package/dist/{chunk-SWVW56CV.js.map → chunk-EN4LDR5Y.js.map} +1 -1
  41. package/dist/{chunk-TEYARRAK.js → chunk-F7OCOBTG.js} +5 -2
  42. package/dist/chunk-F7OCOBTG.js.map +1 -0
  43. package/dist/{chunk-5PESG2VP.js → chunk-HXCMWANP.js} +6 -5
  44. package/dist/chunk-HXCMWANP.js.map +1 -0
  45. package/dist/{chunk-NYL63PSD.js → chunk-IA4D5XNT.js} +61 -10
  46. package/dist/chunk-IA4D5XNT.js.map +1 -0
  47. package/dist/{chunk-F4QMLOXJ.js → chunk-IAOAZUOO.js} +2 -2
  48. package/dist/{chunk-F4QMLOXJ.js.map → chunk-IAOAZUOO.js.map} +1 -1
  49. package/dist/{chunk-Y74NWGQW.js → chunk-KFYANKJI.js} +66 -22
  50. package/dist/chunk-KFYANKJI.js.map +1 -0
  51. package/dist/chunk-MOSIE4Z2.js +51 -0
  52. package/dist/chunk-MOSIE4Z2.js.map +1 -0
  53. package/dist/{chunk-7JUBXOIV.js → chunk-NXWE5S57.js} +25 -6
  54. package/dist/chunk-NXWE5S57.js.map +1 -0
  55. package/dist/{chunk-NNMLEDMD.js → chunk-OBAFAH5B.js} +36 -8
  56. package/dist/chunk-OBAFAH5B.js.map +1 -0
  57. package/dist/{chunk-A3CKOW2R.js → chunk-R75GGNWY.js} +123 -20
  58. package/dist/chunk-R75GGNWY.js.map +1 -0
  59. package/dist/chunk-RGS4WX2M.js +44 -0
  60. package/dist/chunk-RGS4WX2M.js.map +1 -0
  61. package/dist/{chunk-N3GKMPDW.js → chunk-RKTTF3L4.js} +3 -1
  62. package/dist/chunk-RKTTF3L4.js.map +1 -0
  63. package/dist/{chunk-RRB26JRJ.js → chunk-RTJNJTG3.js} +2 -2
  64. package/dist/{chunk-RRB26JRJ.js.map → chunk-RTJNJTG3.js.map} +1 -1
  65. package/dist/{chunk-EAC4CDY7.js → chunk-SMZGAPCU.js} +15 -2
  66. package/dist/chunk-SMZGAPCU.js.map +1 -0
  67. package/dist/{chunk-C4OYOKWX.js → chunk-UA6JW52D.js} +36 -2
  68. package/dist/chunk-UA6JW52D.js.map +1 -0
  69. package/dist/{chunk-3SXMEK3B.js → chunk-V3DX3K65.js} +1 -1
  70. package/dist/chunk-V3DX3K65.js.map +1 -0
  71. package/dist/{chunk-6ZXOQEAN.js → chunk-WF75U3V4.js} +7 -1
  72. package/dist/chunk-WF75U3V4.js.map +1 -0
  73. package/dist/{chunk-VCBTX62E.js → chunk-ZRWXFDDS.js} +2 -2
  74. package/dist/chunk-ZRWXFDDS.js.map +1 -0
  75. package/dist/collapsible-panel.d.ts +18 -7
  76. package/dist/collapsible-panel.js +19 -3
  77. package/dist/collapsible-panel.js.map +1 -1
  78. package/dist/content-item.d.ts +36 -5
  79. package/dist/content-item.js +1 -1
  80. package/dist/disclosure-arrow.js +2 -1
  81. package/dist/index.d.ts +3 -3
  82. package/dist/index.js +22 -22
  83. package/dist/list-action-row.js +1 -1
  84. package/dist/list-header.js +1 -1
  85. package/dist/list-item.js +1 -1
  86. package/dist/lucide-icon.d.ts +5 -2
  87. package/dist/lucide-icon.js +2 -1
  88. package/dist/nav-stack.d.ts +3 -1
  89. package/dist/nav-stack.js +2 -1
  90. package/dist/pane.d.ts +10 -4
  91. package/dist/pane.js +1 -1
  92. package/dist/{panel-toolbar-DqwtBxQL.d.ts → panel-toolbar-DDKh4uKd.d.ts} +11 -2
  93. package/dist/popup-menu-register.js +38 -1
  94. package/dist/popup-menu-register.js.map +1 -1
  95. package/dist/popup-menu.d.ts +2 -2
  96. package/dist/popup-menu.js +1 -1
  97. package/dist/select.d.ts +2 -0
  98. package/dist/select.js +1 -1
  99. package/dist/split-view.d.ts +1 -1
  100. package/dist/split-view.js +2 -1
  101. package/dist/split-view.js.map +1 -1
  102. package/dist/styles/app-tab.css +84 -0
  103. package/dist/styles/chip.css +14 -0
  104. package/dist/styles/content-item.css +36 -0
  105. package/dist/styles/list-action-row.css +4 -3
  106. package/dist/styles/list-header.css +4 -3
  107. package/dist/styles/list-item.css +21 -11
  108. package/dist/styles/lucide-icon.css +5 -0
  109. package/dist/styles/nav-stack.css +13 -0
  110. package/dist/styles/pane.css +36 -2
  111. package/dist/styles/popup-menu.css +15 -10
  112. package/dist/styles/resizable-region.css +9 -0
  113. package/dist/styles/select.css +26 -0
  114. package/dist/styles/split-view.css +6 -0
  115. package/dist/styles/sunken-panel.css +7 -0
  116. package/dist/styles/tab-bar.css +3 -0
  117. package/dist/styles/tab-scaffold.css +13 -0
  118. package/dist/styles/text.css +29 -0
  119. package/dist/styles/token-search-field.css +29 -6
  120. package/dist/styles/toolbar-control-group.css +35 -0
  121. package/dist/styles/toolbar-text.css +15 -6
  122. package/dist/styles/toolbar.css +7 -0
  123. package/dist/styles/workbench.css +260 -90
  124. package/dist/sunken-panel.d.ts +6 -1
  125. package/dist/sunken-panel.js +1 -1
  126. package/dist/tab-bar.d.ts +6 -2
  127. package/dist/tab-bar.js +1 -1
  128. package/dist/tab-scaffold.d.ts +3 -0
  129. package/dist/tab-scaffold.js +1 -0
  130. package/dist/tab-scaffold.js.map +1 -1
  131. package/dist/text.d.ts +14 -3
  132. package/dist/text.js +1 -1
  133. package/dist/token-search-field.js +2 -1
  134. package/dist/toolbar-control-group.d.ts +5 -1
  135. package/dist/toolbar-control-group.js +2 -1
  136. package/dist/toolbar-text.d.ts +1 -1
  137. package/dist/toolbar-text.js +1 -1
  138. package/dist/toolbar.d.ts +7 -4
  139. package/dist/toolbar.js +1 -1
  140. package/dist/wire-catalog.js +1 -1
  141. package/dist/wire-content-items.d.ts +9 -0
  142. package/dist/wire-content-items.js +62 -0
  143. package/dist/wire-content-items.js.map +1 -0
  144. package/dist/wire-tab-bars.js +1 -1
  145. package/dist/wire-token-search-fields.js +2 -1
  146. package/dist/wire-workbench.d.ts +7 -0
  147. package/dist/wire-workbench.js +1 -1
  148. package/dist/workbench.d.ts +24 -8
  149. package/dist/workbench.js +2 -1
  150. package/docs/accessibility.md +21 -5
  151. package/docs/catalog.md +36 -0
  152. package/docs/collapsible-panel.md +12 -4
  153. package/docs/component-contract.md +10 -4
  154. package/docs/component-selection.md +65 -15
  155. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  156. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  157. package/docs/design/templates/toolbar-dark.svg +1 -1
  158. package/docs/design/templates/toolbar.svg +1 -1
  159. package/docs/examples/component-catalog-extension.json +6 -0
  160. package/docs/layout.md +61 -3
  161. package/docs/nav-stack.md +5 -0
  162. package/docs/split-view.md +5 -0
  163. package/docs/sunken-panel.md +30 -1
  164. package/docs/tab-scaffold.md +5 -0
  165. package/docs/ui-analyzer.md +4 -0
  166. package/docs/webawesome-theme.md +36 -6
  167. package/docs/workbench.md +60 -14
  168. package/llms.txt +12 -4
  169. package/package.json +16 -2
  170. package/scripts/component-catalog-extension-validation.mjs +28 -0
  171. package/dist/chunk-3SXMEK3B.js.map +0 -1
  172. package/dist/chunk-4R777DUZ.js +0 -34
  173. package/dist/chunk-4R777DUZ.js.map +0 -1
  174. package/dist/chunk-5PESG2VP.js.map +0 -1
  175. package/dist/chunk-6ZXOQEAN.js.map +0 -1
  176. package/dist/chunk-7JUBXOIV.js.map +0 -1
  177. package/dist/chunk-A3CKOW2R.js.map +0 -1
  178. package/dist/chunk-AUKT3KMG.js.map +0 -1
  179. package/dist/chunk-C4OYOKWX.js.map +0 -1
  180. package/dist/chunk-EAC4CDY7.js.map +0 -1
  181. package/dist/chunk-EZVNC4J6.js.map +0 -1
  182. package/dist/chunk-MA73TA6D.js +0 -42
  183. package/dist/chunk-MA73TA6D.js.map +0 -1
  184. package/dist/chunk-MERWRTBG.js +0 -45
  185. package/dist/chunk-MERWRTBG.js.map +0 -1
  186. package/dist/chunk-N3GKMPDW.js.map +0 -1
  187. package/dist/chunk-NNMLEDMD.js.map +0 -1
  188. package/dist/chunk-NYL63PSD.js.map +0 -1
  189. package/dist/chunk-TEYARRAK.js.map +0 -1
  190. package/dist/chunk-VCBTX62E.js.map +0 -1
  191. package/dist/chunk-Y74NWGQW.js.map +0 -1
@@ -91,21 +91,11 @@
91
91
  flex-direction: column;
92
92
  }
93
93
 
94
- .kui-workbench:has(
95
- > .kui-workbench__rail--left[data-presentation="inline"]:not(
96
- [data-collapsed="true"]
97
- )
98
- )
99
- > .kui-workbench__center {
94
+ .kui-workbench[data-left-inline-expanded="true"] > .kui-workbench__center {
100
95
  --kui-edge-inset-inline-start: 0px;
101
96
  }
102
97
 
103
- .kui-workbench:has(
104
- > .kui-workbench__rail--right[data-presentation="inline"]:not(
105
- [data-collapsed="true"]
106
- )
107
- )
108
- > .kui-workbench__center {
98
+ .kui-workbench[data-right-inline-expanded="true"] > .kui-workbench__center {
109
99
  --kui-edge-inset-inline-end: 0px;
110
100
  }
111
101
 
@@ -115,18 +105,64 @@
115
105
  overflow: auto;
116
106
  }
117
107
 
118
- .kui-workbench__main:has(
119
- ~ .kui-workbench__drawer[data-presentation="inline"]:not(
120
- [data-collapsed="true"]
121
- )
122
- ) {
123
- --kui-edge-inset-block-end: 0px;
108
+ /* When no Pane chrome is composed, the Workbench region owns its scroll.
109
+ A resizable auto-overflow panel instead scrolls its content box so its
110
+ separator handle remains fixed. Paint only that owner. */
111
+ .kui-workbench__main[data-appearance="sunken"],
112
+ :is(.kui-workbench__rail, .kui-workbench__drawer)[data-appearance="sunken"] {
113
+ background: var(
114
+ --kui-sunken-panel-background,
115
+ var(--kui-color-surface-lowered)
116
+ );
117
+ }
118
+
119
+ :is(
120
+ .kui-workbench__rail,
121
+ .kui-workbench__drawer
122
+ )[data-appearance="sunken"][data-resizable="true"][data-content-overflow="auto"] {
123
+ background: transparent;
124
+ }
125
+
126
+ :is(
127
+ .kui-workbench__rail,
128
+ .kui-workbench__drawer
129
+ )[data-appearance="sunken"][data-resizable="true"][data-content-overflow="auto"]
130
+ > .kui-workbench__panel-content {
131
+ background: var(
132
+ --kui-sunken-panel-background,
133
+ var(--kui-color-surface-lowered)
134
+ );
135
+ }
136
+
137
+ .kui-workbench__main[data-drawer-inline-expanded="true"] {
138
+ --_kui-workbench-content-edge-block-end: 0px;
124
139
  }
125
140
 
126
141
  /* The main area and each panel's content are the consumers: they pad for the
127
142
  edges their region still touches, the main area inside its scroller so
128
143
  content scrolls under an unsafe edge but can always be scrolled clear. A
129
144
  region whose only child is a Pane or layout lets that child own the insets. */
145
+ .kui-workbench__center,
146
+ .kui-workbench__rail,
147
+ .kui-workbench__drawer {
148
+ --_kui-workbench-content-edge-block-start: var(--kui-edge-inset-block-start);
149
+ --_kui-workbench-content-edge-block-end: var(--kui-edge-inset-block-end);
150
+ --_kui-workbench-content-edge-inline-start: var(
151
+ --kui-edge-inset-inline-start
152
+ );
153
+ --_kui-workbench-content-edge-inline-end: var(--kui-edge-inset-inline-end);
154
+ }
155
+
156
+ .kui-workbench__main,
157
+ .kui-workbench__panel-content {
158
+ /* Clear the public context at the region, so arbitrary app children inherit
159
+ zero without a selector whose subject is every child in the document. */
160
+ --kui-edge-inset-block-start: 0px;
161
+ --kui-edge-inset-block-end: 0px;
162
+ --kui-edge-inset-inline-start: 0px;
163
+ --kui-edge-inset-inline-end: 0px;
164
+ }
165
+
130
166
  .kui-workbench__main:not(
131
167
  :has(
132
168
  > :is(
@@ -149,44 +185,24 @@
149
185
  ):only-child
150
186
  )
151
187
  ) {
152
- padding-block: var(--kui-edge-inset-block-start)
153
- var(--kui-edge-inset-block-end);
154
- padding-inline: var(--kui-edge-inset-inline-start)
155
- var(--kui-edge-inset-inline-end);
156
- scroll-padding-block: var(--kui-edge-inset-block-start)
157
- var(--kui-edge-inset-block-end);
158
- }
159
-
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
- ) {
186
- --kui-edge-inset-block-start: 0px;
187
- --kui-edge-inset-block-end: 0px;
188
- --kui-edge-inset-inline-start: 0px;
189
- --kui-edge-inset-inline-end: 0px;
188
+ padding-block: var(--_kui-workbench-content-edge-block-start)
189
+ var(--_kui-workbench-content-edge-block-end);
190
+ padding-inline: var(--_kui-workbench-content-edge-inline-start)
191
+ var(--_kui-workbench-content-edge-inline-end);
192
+ scroll-padding-block: var(--_kui-workbench-content-edge-block-start)
193
+ var(--_kui-workbench-content-edge-block-end);
194
+ }
195
+
196
+ /* A nested Workbench owns its safe-area insets. Other sole layout children
197
+ restore this context from their own stylesheets. */
198
+ :is(.kui-workbench__main, .kui-workbench__panel-content)
199
+ > [data-component="workbench"]:only-child {
200
+ --kui-edge-inset-block-start: var(--_kui-workbench-content-edge-block-start);
201
+ --kui-edge-inset-block-end: var(--_kui-workbench-content-edge-block-end);
202
+ --kui-edge-inset-inline-start: var(
203
+ --_kui-workbench-content-edge-inline-start
204
+ );
205
+ --kui-edge-inset-inline-end: var(--_kui-workbench-content-edge-inline-end);
190
206
  }
191
207
 
192
208
  /* A region whose only child is a Pane or layout lets it fill the region, so a
@@ -231,6 +247,11 @@
231
247
  overflow: visible;
232
248
  }
233
249
 
250
+ .kui-workbench__rail:has(wa-dropdown[open]),
251
+ .kui-workbench__drawer:has(wa-dropdown[open]) {
252
+ z-index: var(--kui-workbench-popup-z, 5);
253
+ }
254
+
234
255
  /* The fixed-size sliding content. The rail/drawer keeps a definite size on its
235
256
  content so the transform reads as a slide, not a squeeze. */
236
257
  .kui-workbench__rail .kui-workbench__panel-content {
@@ -254,6 +275,11 @@
254
275
  transition: none;
255
276
  }
256
277
 
278
+ :is(.kui-workbench__rail, .kui-workbench__drawer)[data-resizing="true"]
279
+ > .kui-workbench__panel-content {
280
+ transition: none;
281
+ }
282
+
257
283
  .kui-workbench__rail[data-collapse-motion="fade-slide"][data-collapsed="true"]
258
284
  .kui-workbench__panel-content,
259
285
  .kui-workbench__drawer[data-collapse-motion="fade-slide"][data-collapsed="true"]
@@ -542,6 +568,25 @@
542
568
  inset-block-end: 0;
543
569
  }
544
570
 
571
+ /* Optional shared backdrop sits over work-area controls and restores, below
572
+ the overlay drawer and rails. It is last in document order so an equal-z
573
+ restore control cannot receive the outside press. */
574
+ .kui-workbench__backdrop {
575
+ position: absolute;
576
+ z-index: calc(var(--kui-workbench-overlay-z, 41) - 2);
577
+ display: none;
578
+ inset: 0;
579
+ background: var(--kui-color-scrim, rgb(0 0 0 / 32%));
580
+ }
581
+
582
+ .kui-workbench[data-overlay-backdrop="true"]:is(
583
+ [data-rail-overlay-expanded="true"],
584
+ [data-drawer-overlay-expanded="true"]
585
+ )
586
+ > .kui-workbench__backdrop {
587
+ display: block;
588
+ }
589
+
545
590
  /* A collapsed overlay keeps its box for the slide-out, so it drops its surface
546
591
  and shadow; the sliding content carries the surface instead. */
547
592
  .kui-workbench__rail[data-presentation="overlay"][data-collapsed="true"],
@@ -579,14 +624,112 @@
579
624
  `responsiveFillAt`), and inline above it. The Workbench becomes a named
580
625
  inline-size container only when a panel opts in. The work area keeps its
581
626
  safe-area inset under an overlay panel. */
627
+ .kui-workbench[data-responsive-overlay="true"] {
628
+ container: kui-workbench / inline-size;
629
+ }
630
+
631
+ /* A numeric pixel threshold is measured by wireWorkbench and marked on the
632
+ panel. Keep its overlay geometry and covered work area in step with that
633
+ single mark, including when the app changes the threshold at render time. */
634
+ .kui-workbench__rail[data-responsive-overlay-active="true"] {
635
+ position: absolute;
636
+ z-index: var(--kui-workbench-overlay-z, 41);
637
+ width: var(--_kui-workbench-rail-extent);
638
+ max-width: var(--kui-workbench-overlay-max-width, 85vw);
639
+ inset-block: 0;
640
+ box-shadow: var(--kui-shadow-l);
641
+ clip-path: none;
642
+ }
643
+
644
+ .kui-workbench__rail--left[data-responsive-overlay-active="true"] {
645
+ inset-inline-start: 0;
646
+ }
647
+
648
+ .kui-workbench__rail--right[data-responsive-overlay-active="true"] {
649
+ inset-inline-end: 0;
650
+ }
651
+
652
+ .kui-workbench__rail[data-responsive-overlay-active="true"][data-collapsed="true"],
653
+ .kui-workbench__drawer[data-responsive-overlay-active="true"][data-collapsed="true"] {
654
+ background: transparent;
655
+ box-shadow: none;
656
+ pointer-events: none;
657
+ }
658
+
659
+ .kui-workbench__rail[data-responsive-overlay-active="true"]
660
+ > .kui-workbench__panel-content {
661
+ width: 100%;
662
+ background: var(--kui-color-surface);
663
+ }
664
+
665
+ .kui-workbench__rail[data-responsive-overlay-active="true"]
666
+ > .kui-workbench__handle,
667
+ .kui-workbench__drawer[data-responsive-overlay-active="true"]
668
+ > .kui-workbench__handle {
669
+ display: none;
670
+ }
671
+
582
672
  .kui-workbench:has(
583
- > .kui-workbench__rail[data-responsive-overlay-at],
584
- > .kui-workbench__center > .kui-workbench__drawer[data-responsive-overlay-at]
673
+ > .kui-workbench__rail--left[data-responsive-overlay-active="true"]
674
+ )
675
+ > .kui-workbench__center {
676
+ --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
677
+ }
678
+
679
+ .kui-workbench:has(
680
+ > .kui-workbench__rail--right[data-responsive-overlay-active="true"]
681
+ )
682
+ > .kui-workbench__center {
683
+ --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
684
+ }
685
+
686
+ .kui-workbench__drawer[data-responsive-overlay-active="true"] {
687
+ position: absolute;
688
+ anchor-name: none;
689
+ z-index: calc(var(--kui-workbench-overlay-z, 41) - 1);
690
+ height: var(--_kui-workbench-drawer-extent);
691
+ max-height: var(--kui-workbench-overlay-max-height, 85vh);
692
+ inset-inline: 0;
693
+ inset-block-end: 0;
694
+ box-shadow: var(--kui-shadow-l);
695
+ clip-path: none;
696
+ }
697
+
698
+ .kui-workbench__drawer[data-responsive-overlay-active="true"]
699
+ > .kui-workbench__panel-content {
700
+ height: 100%;
701
+ background: var(--kui-color-surface);
702
+ }
703
+
704
+ .kui-workbench__main:has(
705
+ + .kui-workbench__drawer[data-responsive-overlay-active="true"]
585
706
  ) {
586
- container: kui-workbench / inline-size;
707
+ --_kui-workbench-content-edge-block-end: var(--_kui-workbench-safe-block-end);
708
+ }
709
+
710
+ .kui-workbench[data-overlay-backdrop="true"]:has(
711
+ > .kui-workbench__rail[data-responsive-overlay-active="true"]:not(
712
+ [data-collapsed="true"]
713
+ ),
714
+ > .kui-workbench__center
715
+ > .kui-workbench__drawer[data-responsive-overlay-active="true"]:not(
716
+ [data-collapsed="true"]
717
+ )
718
+ )
719
+ > .kui-workbench__backdrop {
720
+ display: block;
587
721
  }
588
722
 
589
723
  @container kui-workbench (max-width: 44rem) {
724
+ .kui-workbench[data-overlay-backdrop="true"]:is(
725
+ [data-left-responsive-expanded="narrow"],
726
+ [data-right-responsive-expanded="narrow"],
727
+ [data-drawer-responsive-expanded="narrow"]
728
+ )
729
+ > .kui-workbench__backdrop {
730
+ display: block;
731
+ }
732
+
590
733
  .kui-workbench__rail[data-responsive-overlay-at="narrow"] {
591
734
  position: absolute;
592
735
  z-index: var(--kui-workbench-overlay-z, 41);
@@ -622,16 +765,12 @@
622
765
  display: none;
623
766
  }
624
767
 
625
- .kui-workbench:has(
626
- > .kui-workbench__rail--left[data-responsive-overlay-at="narrow"][data-presentation="inline"]
627
- )
768
+ .kui-workbench[data-left-responsive-inline="narrow"]
628
769
  > .kui-workbench__center {
629
770
  --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
630
771
  }
631
772
 
632
- .kui-workbench:has(
633
- > .kui-workbench__rail--right[data-responsive-overlay-at="narrow"][data-presentation="inline"]
634
- )
773
+ .kui-workbench[data-right-responsive-inline="narrow"]
635
774
  > .kui-workbench__center {
636
775
  --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
637
776
  }
@@ -665,14 +804,23 @@
665
804
  display: none;
666
805
  }
667
806
 
668
- .kui-workbench__main:has(
669
- ~ .kui-workbench__drawer[data-responsive-overlay-at="narrow"][data-presentation="inline"]
670
- ) {
671
- --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
807
+ .kui-workbench__main[data-drawer-responsive-inline="narrow"] {
808
+ --_kui-workbench-content-edge-block-end: var(
809
+ --_kui-workbench-safe-block-end
810
+ );
672
811
  }
673
812
  }
674
813
 
675
814
  @container kui-workbench (max-width: 28rem) {
815
+ .kui-workbench[data-overlay-backdrop="true"]:is(
816
+ [data-left-responsive-expanded="compact"],
817
+ [data-right-responsive-expanded="compact"],
818
+ [data-drawer-responsive-expanded="compact"]
819
+ )
820
+ > .kui-workbench__backdrop {
821
+ display: block;
822
+ }
823
+
676
824
  .kui-workbench__rail[data-responsive-overlay-at="compact"] {
677
825
  position: absolute;
678
826
  z-index: var(--kui-workbench-overlay-z, 41);
@@ -708,16 +856,12 @@
708
856
  display: none;
709
857
  }
710
858
 
711
- .kui-workbench:has(
712
- > .kui-workbench__rail--left[data-responsive-overlay-at="compact"][data-presentation="inline"]
713
- )
859
+ .kui-workbench[data-left-responsive-inline="compact"]
714
860
  > .kui-workbench__center {
715
861
  --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
716
862
  }
717
863
 
718
- .kui-workbench:has(
719
- > .kui-workbench__rail--right[data-responsive-overlay-at="compact"][data-presentation="inline"]
720
- )
864
+ .kui-workbench[data-right-responsive-inline="compact"]
721
865
  > .kui-workbench__center {
722
866
  --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
723
867
  }
@@ -751,13 +895,28 @@
751
895
  display: none;
752
896
  }
753
897
 
754
- .kui-workbench__main:has(
755
- ~ .kui-workbench__drawer[data-responsive-overlay-at="compact"][data-presentation="inline"]
756
- ) {
757
- --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
898
+ .kui-workbench__main[data-drawer-responsive-inline="compact"] {
899
+ --_kui-workbench-content-edge-block-end: var(
900
+ --_kui-workbench-safe-block-end
901
+ );
758
902
  }
759
903
  }
760
904
 
905
+ /* Release the panel and its sliding/scrolling content while a PopupMenu or
906
+ Web Awesome dropdown is open. This comes after resize clipping and keeps
907
+ the popup in place through wa-show/wa-after-hide without a prop change. */
908
+ .kui-workbench__rail:not([data-collapsed="true"]):has(wa-dropdown[open]),
909
+ .kui-workbench__drawer:not([data-collapsed="true"]):has(wa-dropdown[open]) {
910
+ overflow: visible;
911
+ clip-path: none;
912
+ }
913
+ .kui-workbench__rail:not([data-collapsed="true"]):has(wa-dropdown[open])
914
+ > .kui-workbench__panel-content,
915
+ .kui-workbench__drawer:not([data-collapsed="true"]):has(wa-dropdown[open])
916
+ > .kui-workbench__panel-content {
917
+ overflow: visible;
918
+ }
919
+
761
920
  /* A collapsed panel's restore control floats in a corner of the Workbench
762
921
  itself, never of the viewport, so an embedded Workbench cannot float it over
763
922
  page chrome outside it. It is inset from the corner plus the unsafe area of
@@ -860,8 +1019,13 @@
860
1019
  the open rail), so the overlay's own floating controls stay; FloatingToolbar
861
1020
  and the layouts' restore corners read it. A direct child
862
1021
  resolves the Workbench's own container query, never a nested one's. */
1022
+ .kui-workbench[data-rail-overlay-expanded="true"]
1023
+ > :is(.kui-workbench__center, .kui-workbench__restore) {
1024
+ --_kui-workbench-covered: hidden;
1025
+ }
1026
+
863
1027
  .kui-workbench:has(
864
- > .kui-workbench__rail[data-presentation="overlay"]:not(
1028
+ > .kui-workbench__rail[data-responsive-overlay-active="true"]:not(
865
1029
  [data-collapsed="true"]
866
1030
  )
867
1031
  )
@@ -870,10 +1034,9 @@
870
1034
  }
871
1035
 
872
1036
  @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
- )
1037
+ .kui-workbench:is(
1038
+ [data-left-responsive-expanded="narrow"],
1039
+ [data-right-responsive-expanded="narrow"]
877
1040
  )
878
1041
  > :is(.kui-workbench__center, .kui-workbench__restore) {
879
1042
  --_kui-workbench-covered: hidden;
@@ -881,10 +1044,9 @@
881
1044
  }
882
1045
 
883
1046
  @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
- )
1047
+ .kui-workbench:is(
1048
+ [data-left-responsive-expanded="compact"],
1049
+ [data-right-responsive-expanded="compact"]
888
1050
  )
889
1051
  > :is(.kui-workbench__center, .kui-workbench__restore) {
890
1052
  --_kui-workbench-covered: hidden;
@@ -902,14 +1064,22 @@
902
1064
  closes it (the wiring's outside press); `compactOverlay: "full"` fills the
903
1065
  Workbench. The rail's resize and configured size stop applying there. */
904
1066
  @container kui-workbench (max-width: 28rem) {
905
- .kui-workbench__rail[data-responsive-overlay-at] {
1067
+ .kui-workbench__rail:is(
1068
+ [data-responsive-overlay-at="narrow"],
1069
+ [data-responsive-overlay-at="compact"],
1070
+ [data-responsive-overlay-active="true"]
1071
+ ) {
906
1072
  width: calc(
907
1073
  100% - var(--kui-workbench-overlay-dismiss-margin, 2.75rem)
908
1074
  );
909
1075
  max-width: none;
910
1076
  }
911
1077
 
912
- .kui-workbench__rail[data-responsive-overlay-at][data-compact-overlay="full"] {
1078
+ .kui-workbench__rail:is(
1079
+ [data-responsive-overlay-at="narrow"],
1080
+ [data-responsive-overlay-at="compact"],
1081
+ [data-responsive-overlay-active="true"]
1082
+ )[data-compact-overlay="full"] {
913
1083
  width: 100%;
914
1084
  }
915
1085
  }
@@ -1,4 +1,5 @@
1
1
  import * as kerfjs from 'kerfjs';
2
+ import { CssFlexKeyword, CssFlex } from './css-values.js';
2
3
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
4
 
4
5
  type SunkenPanelShape = 'rounded' | 'square';
@@ -8,6 +9,10 @@ interface SunkenPanelProps {
8
9
  ariaLabel?: string;
9
10
  /** Corner shape: a rounded rectangle (default) or square corners. */
10
11
  shape?: SunkenPanelShape;
12
+ /** Grow or shrink within a flex parent, using Grid's typed flex contract. */
13
+ flex?: boolean | CssFlexKeyword | CssFlex;
14
+ /** Fill a parent with a definite height; use flex inside a flex layout. */
15
+ fill?: boolean;
11
16
  className?: string;
12
17
  /** Native named-slot assignment when composed inside a web component. */
13
18
  slot?: string;
@@ -17,6 +22,6 @@ interface SunkenPanelProps {
17
22
  * stack. The panel owns its background and padding; children own their own
18
23
  * borders and internal geometry.
19
24
  */
20
- declare function SunkenPanel({ children, ariaLabel, shape, className, slot, }: SunkenPanelProps): kerfjs.SafeHtml;
25
+ declare function SunkenPanel({ children, ariaLabel, shape, flex, fill, className, slot, }: SunkenPanelProps): kerfjs.SafeHtml;
21
26
 
22
27
  export { SunkenPanel, type SunkenPanelProps, type SunkenPanelShape };
@@ -1,3 +1,3 @@
1
- export { SunkenPanel } from './chunk-6ZXOQEAN.js';
1
+ export { SunkenPanel } from './chunk-WF75U3V4.js';
2
2
 
3
3
  //# sourceMappingURL=sunken-panel.js.map
package/dist/tab-bar.d.ts CHANGED
@@ -5,6 +5,8 @@ type TabActivation = 'automatic' | 'manual';
5
5
  type TabBarAllocation = 'intrinsic' | 'fill';
6
6
  type TabBarPresentation = 'rail' | 'segmented' | 'inspector';
7
7
  type TabBarTrailingPlacement = 'separate' | 'adjacent';
8
+ /** Tablist width where segmented AppTabs switch to icon-only. */
9
+ type TabBarIconOnlyAt = 'wide' | 'narrow' | 'compact';
8
10
  interface TabBarProps {
9
11
  id: string;
10
12
  label: string;
@@ -28,12 +30,14 @@ interface TabBarProps {
28
30
  allocation?: TabBarAllocation;
29
31
  /** Named strip chrome for application rails, segmented tabs, or inspectors. */
30
32
  presentation?: TabBarPresentation;
33
+ /** Switch segmented AppTabs to icon-only at tablist widths of 832, 704, or 448px. Each tab needs a leading icon. */
34
+ iconOnlyAt?: TabBarIconOnlyAt;
31
35
  /** Keep a trailing action beside the final tab or at the far edge of the bar. */
32
36
  trailingPlacement?: TabBarTrailingPlacement;
33
37
  /** Native named-slot assignment when composed inside a web component. */
34
38
  slot?: string;
35
39
  }
36
40
  /** Render a controlled tab strip. The application owns selection, order, and persistence. */
37
- declare function TabBar({ id, label, children, leading, trailing, end, className, activation, allocation, presentation, trailingPlacement, slot, }: TabBarProps): kerfjs.SafeHtml;
41
+ declare function TabBar({ id, label, children, leading, trailing, end, className, activation, allocation, presentation, iconOnlyAt, trailingPlacement, slot, }: TabBarProps): kerfjs.SafeHtml;
38
42
 
39
- export { type TabActivation, TabBar, type TabBarAllocation, type TabBarPresentation, type TabBarProps, type TabBarTrailingPlacement };
43
+ export { type TabActivation, TabBar, type TabBarAllocation, type TabBarIconOnlyAt, type TabBarPresentation, type TabBarProps, type TabBarTrailingPlacement };
package/dist/tab-bar.js CHANGED
@@ -1,3 +1,3 @@
1
- export { TabBar } from './chunk-N3GKMPDW.js';
1
+ export { TabBar } from './chunk-RKTTF3L4.js';
2
2
 
3
3
  //# sourceMappingURL=tab-bar.js.map
@@ -1,4 +1,5 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
+ import { PaneAppearance } from './pane.js';
2
3
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
4
 
4
5
  interface TabScaffoldTabBase<Id extends string> {
@@ -8,6 +9,8 @@ interface TabScaffoldTabBase<Id extends string> {
8
9
  icon?: SafeHtml;
9
10
  /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */
10
11
  content: KerfUiContent;
12
+ /** Background of this scene's scrolling work surface. */
13
+ appearance?: PaneAppearance;
11
14
  }
12
15
  /** A tab with an optional count or short-status badge. */
13
16
  interface TabScaffoldTabCountBadge {
@@ -26,6 +26,7 @@ function TabScaffold({
26
26
  class: "kui-tab-scaffold__scene",
27
27
  "data-tab-scaffold-scene": tab.id,
28
28
  "data-active": String(tab.id === active),
29
+ "data-appearance": tab.appearance === "sunken" ? "sunken" : void 0,
29
30
  "aria-hidden": String(tab.id !== active),
30
31
  children: tab.content
31
32
  }
@@ -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 /**\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"]}
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 { PaneAppearance } from './pane.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 /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\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 data-appearance={tab.appearance === 'sunken' ? 'sunken' : undefined}\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"]}
package/dist/text.d.ts CHANGED
@@ -8,8 +8,9 @@ type TextSize = 'compact' | 'default' | 'large' | 'xlarge';
8
8
  type TextFont = 'default' | 'monospace';
9
9
  type TextBorder = 'transparent' | 'none';
10
10
  type TextLineHeight = 'default' | 'tight';
11
+ type TextWrap = 'normal' | 'anywhere' | 'nowrap' | 'truncate';
11
12
  type TextContent = KerfUiContent | string | number | readonly TextContent[];
12
- type TextProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> & {
13
+ type TextCommonProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> & {
13
14
  /** Native heading, paragraph, or inline span element to render. Defaults to `p`. */
14
15
  variant?: TextVariant;
15
16
  /** Semantic foreground treatment. Defaults to the inherited foreground. */
@@ -28,11 +29,21 @@ type TextProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> & {
28
29
  class?: string;
29
30
  className?: string;
30
31
  };
32
+ type TextProps = TextCommonProps & ({
33
+ /** Wrap normally or break long unbroken strings. Defaults to normal. */
34
+ wrap?: 'normal' | 'anywhere';
35
+ /** Cap wrapped text to this positive number of lines. */
36
+ maxLines?: number;
37
+ } | {
38
+ /** Keep one line, with or without an ellipsis. */
39
+ wrap: 'nowrap' | 'truncate';
40
+ maxLines?: never;
41
+ });
31
42
  /**
32
43
  * Semantic heading, paragraph, or inline text. Block variants use the standard
33
44
  * content-item padding; `span` adds no box geometry.
34
45
  * All ordinary native heading/paragraph attributes pass through to the element.
35
46
  */
36
- declare function Text({ variant: Variant, tone, size, font, border, flush, lineHeight, children, class: classValue, className, ...attributes }: TextProps): kerfjs.SafeHtml;
47
+ declare function Text({ variant: Variant, tone, size, font, border, flush, lineHeight, wrap, maxLines, children, class: classValue, className, ...attributes }: TextProps): kerfjs.SafeHtml;
37
48
 
38
- export { Text, type TextBorder, type TextContent, type TextFont, type TextLineHeight, type TextProps, type TextSize, type TextTone, type TextVariant };
49
+ export { Text, type TextBorder, type TextContent, type TextFont, type TextLineHeight, type TextProps, type TextSize, type TextTone, type TextVariant, type TextWrap };
package/dist/text.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Text } from './chunk-AUKT3KMG.js';
1
+ export { Text } from './chunk-CQHN45XR.js';
2
2
 
3
3
  //# sourceMappingURL=text.js.map