@kerfjs/ui 5.0.0-beta.50 → 5.0.0-beta.52

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 (212) hide show
  1. package/README.md +29 -5
  2. package/ai/compile-time-contracts-v1.json +5 -2
  3. package/ai/component-catalog-v2.d.ts +19 -1
  4. package/ai/component-catalog-v2.json +842 -127
  5. package/ai/component-catalog-v2.schema.json +33 -1
  6. package/ai/component-catalog.json +603 -126
  7. package/ai/component-catalog.schema.json +21 -1
  8. package/ai/public-api-signatures-v1.md +218 -31
  9. package/ai/skill.md +27 -24
  10. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  11. package/analyzer/index.mjs +105 -35
  12. package/dist/app-tab.d.ts +10 -1
  13. package/dist/app-tab.js +1 -1
  14. package/dist/catalog-resources.d.ts +1 -1
  15. package/dist/catalog.d.ts +28 -5
  16. package/dist/catalog.js +66 -25
  17. package/dist/catalog.js.map +1 -1
  18. package/dist/{chunk-OL4TC3U2.js → chunk-2CE22Q2K.js} +9 -2
  19. package/dist/chunk-2CE22Q2K.js.map +1 -0
  20. package/dist/{chunk-SIU2AC5W.js → chunk-2S7OWAEV.js} +3 -3
  21. package/dist/{chunk-SIU2AC5W.js.map → chunk-2S7OWAEV.js.map} +1 -1
  22. package/dist/{chunk-2YOUUEWR.js → chunk-3S7OUHUN.js} +4 -4
  23. package/dist/{chunk-2YOUUEWR.js.map → chunk-3S7OUHUN.js.map} +1 -1
  24. package/dist/{chunk-2WMSE4MK.js → chunk-4XE2CDYB.js} +2 -2
  25. package/dist/{chunk-2WMSE4MK.js.map → chunk-4XE2CDYB.js.map} +1 -1
  26. package/dist/{chunk-V6RZWDII.js → chunk-7GUQYHKA.js} +19 -2
  27. package/dist/chunk-7GUQYHKA.js.map +1 -0
  28. package/dist/{chunk-ZNZ2JDBW.js → chunk-7IUSV4XG.js} +1 -1
  29. package/dist/chunk-7IUSV4XG.js.map +1 -0
  30. package/dist/{chunk-UMGY5JSP.js → chunk-7XJO4D6A.js} +22 -2
  31. package/dist/chunk-7XJO4D6A.js.map +1 -0
  32. package/dist/{chunk-KKNK3JUT.js → chunk-ABAXNUXU.js} +5 -5
  33. package/dist/chunk-ABAXNUXU.js.map +1 -0
  34. package/dist/{chunk-AQSKJ6U6.js → chunk-DNCOFEUL.js} +21 -2
  35. package/dist/chunk-DNCOFEUL.js.map +1 -0
  36. package/dist/chunk-EKYZCRCX.js +256 -0
  37. package/dist/chunk-EKYZCRCX.js.map +1 -0
  38. package/dist/{chunk-COUPLRZG.js → chunk-IR53BQUJ.js} +16 -12
  39. package/dist/chunk-IR53BQUJ.js.map +1 -0
  40. package/dist/{chunk-3V75EQIP.js → chunk-JGFZ42ZF.js} +3 -3
  41. package/dist/{chunk-3V75EQIP.js.map → chunk-JGFZ42ZF.js.map} +1 -1
  42. package/dist/{chunk-54HM36IQ.js → chunk-KAQUUTZR.js} +4 -2
  43. package/dist/chunk-KAQUUTZR.js.map +1 -0
  44. package/dist/{chunk-ZSYLBVWG.js → chunk-LIOXPQ7N.js} +4 -4
  45. package/dist/chunk-LIOXPQ7N.js.map +1 -0
  46. package/dist/{chunk-S6ZTTTV7.js → chunk-M6GLI6A6.js} +3 -3
  47. package/dist/{chunk-S6ZTTTV7.js.map → chunk-M6GLI6A6.js.map} +1 -1
  48. package/dist/{chunk-OQI7RCRP.js → chunk-MTM43SJO.js} +7 -1
  49. package/dist/chunk-MTM43SJO.js.map +1 -0
  50. package/dist/{chunk-5CE64WGC.js → chunk-OSCL5TKR.js} +3 -3
  51. package/dist/{chunk-5CE64WGC.js.map → chunk-OSCL5TKR.js.map} +1 -1
  52. package/dist/{chunk-DARTMI5Z.js → chunk-SV22UIW4.js} +2 -2
  53. package/dist/{chunk-DARTMI5Z.js.map → chunk-SV22UIW4.js.map} +1 -1
  54. package/dist/{chunk-ELRMBQEC.js → chunk-T7C2VZUB.js} +17 -2
  55. package/dist/chunk-T7C2VZUB.js.map +1 -0
  56. package/dist/{chunk-DJKWFYAA.js → chunk-YZV2PTY4.js} +3 -3
  57. package/dist/{chunk-DJKWFYAA.js.map → chunk-YZV2PTY4.js.map} +1 -1
  58. package/dist/collapsible-panel.d.ts +1 -1
  59. package/dist/collapsible-panel.js.map +1 -1
  60. package/dist/css-values.d.ts +41 -3
  61. package/dist/css-values.js +1 -1
  62. package/dist/grid.d.ts +15 -1
  63. package/dist/grid.js +2 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +19 -19
  66. package/dist/list-action-row.js +1 -1
  67. package/dist/list-header.js +1 -1
  68. package/dist/list-item.js +1 -1
  69. package/dist/list.d.ts +21 -2
  70. package/dist/list.js +2 -1
  71. package/dist/nav-stack.js +1 -1
  72. package/dist/pane.d.ts +17 -2
  73. package/dist/pane.js +1 -1
  74. package/dist/row.d.ts +19 -1
  75. package/dist/row.js +2 -1
  76. package/dist/segmented-control.js +1 -1
  77. package/dist/select.d.ts +13 -3
  78. package/dist/select.js +1 -1
  79. package/dist/skeleton.d.ts +5 -1
  80. package/dist/skeleton.js +1 -1
  81. package/dist/spacer.js +1 -1
  82. package/dist/split-view.js +1 -1
  83. package/dist/state-banner.js +1 -1
  84. package/dist/styles/app-tab.css +9 -0
  85. package/dist/styles/catalog/components/catalog-example-stack.css +4 -1
  86. package/dist/styles/catalog/components/catalog-example.css +145 -0
  87. package/dist/styles/collapsible-panel.css +132 -4
  88. package/dist/styles/document.css +23 -0
  89. package/dist/styles/foundation.css +21 -7
  90. package/dist/styles/grid.css +10 -0
  91. package/dist/styles/list-header.css +45 -0
  92. package/dist/styles/list-inset-control.css +8 -1
  93. package/dist/styles/list-item.css +6 -0
  94. package/dist/styles/list.css +11 -0
  95. package/dist/styles/nav-stack.css +89 -2
  96. package/dist/styles/pane.css +86 -1
  97. package/dist/styles/resizable-region.css +93 -5
  98. package/dist/styles/row.css +10 -0
  99. package/dist/styles/select.css +39 -4
  100. package/dist/styles/skeleton.css +4 -1
  101. package/dist/styles/split-view.css +107 -1
  102. package/dist/styles/tab-scaffold.css +64 -1
  103. package/dist/styles/text.css +6 -0
  104. package/dist/styles/toolbar-control-group.css +90 -11
  105. package/dist/styles/toolbar-text.css +4 -1
  106. package/dist/styles/toolbar.css +130 -38
  107. package/dist/styles/webawesome.css +48 -2
  108. package/dist/styles/workbench.css +136 -4
  109. package/dist/text.d.ts +4 -3
  110. package/dist/text.js +1 -1
  111. package/dist/toolbar-text.js +1 -1
  112. package/dist/toolbar.d.ts +24 -3
  113. package/dist/toolbar.js +1 -1
  114. package/dist/{types-CzgSrFXP.d.ts → types-CEsieBZi.d.ts} +6 -1
  115. package/dist/value-table.js +1 -1
  116. package/dist/wire-resizable-regions.js +1 -1
  117. package/dist/wire-sidebar.d.ts +18 -3
  118. package/dist/wire-sidebar.js +96 -15
  119. package/dist/wire-sidebar.js.map +1 -1
  120. package/docs/accessibility.md +49 -9
  121. package/docs/app-layouts.md +73 -0
  122. package/docs/catalog.md +23 -2
  123. package/docs/collapsible-panel.md +83 -9
  124. package/docs/component-contract.md +36 -12
  125. package/docs/component-integration.md +9 -4
  126. package/docs/component-selection.md +25 -6
  127. package/docs/design/templates/list/stack-dark.svg +1 -1
  128. package/docs/design/templates/list/stack.svg +1 -1
  129. package/docs/design/templates/list-dark.svg +1 -1
  130. package/docs/design/templates/list-header/action-dark.svg +1 -1
  131. package/docs/design/templates/list-header/action.svg +1 -1
  132. package/docs/design/templates/list-header/count-dark.svg +1 -1
  133. package/docs/design/templates/list-header/count.svg +1 -1
  134. package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
  135. package/docs/design/templates/list-header/inline-action.svg +1 -1
  136. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  137. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  138. package/docs/design/templates/list-header-dark.svg +1 -1
  139. package/docs/design/templates/list-header.svg +1 -1
  140. package/docs/design/templates/list.svg +1 -1
  141. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  142. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  143. package/docs/design/templates/state-banner-dark.svg +1 -1
  144. package/docs/design/templates/state-banner.svg +1 -1
  145. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  146. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  147. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
  148. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
  149. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  150. package/docs/design/templates/toolbar-control-group.svg +1 -1
  151. package/docs/design/templates/toolbar-dark.svg +1 -1
  152. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  153. package/docs/design/templates/toolbar-text/default.svg +1 -1
  154. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  155. package/docs/design/templates/toolbar-text/large.svg +1 -1
  156. package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
  157. package/docs/design/templates/toolbar-text/small.svg +1 -1
  158. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  159. package/docs/design/templates/toolbar-text.svg +1 -1
  160. package/docs/design/templates/toolbar.svg +1 -1
  161. package/docs/document-baseline.md +31 -0
  162. package/docs/layout.md +86 -21
  163. package/docs/nav-stack.md +8 -0
  164. package/docs/recipes.md +81 -44
  165. package/docs/split-view.md +8 -0
  166. package/docs/tab-scaffold.md +4 -2
  167. package/docs/type-contracts.md +21 -17
  168. package/docs/ui-analyzer.md +19 -0
  169. package/docs/ui-evaluator.md +17 -0
  170. package/docs/ux-demo.md +5 -5
  171. package/docs/webawesome-theme.md +20 -2
  172. package/docs/workbench.md +20 -1
  173. package/evaluator/index.mjs +73 -8
  174. package/llms.txt +32 -14
  175. package/package.json +4 -2
  176. package/ux-demo/recipes/app-shell.tsx +209 -178
  177. package/ux-demo/recipes/collapsible-sidebar.tsx +189 -159
  178. package/ux-demo/recipes/compact-toolbar.tsx +48 -35
  179. package/ux-demo/recipes/composer-form.tsx +87 -79
  180. package/ux-demo/recipes/list-detail-dialog.tsx +124 -142
  181. package/ux-demo/recipes/list-workspace-states.tsx +83 -83
  182. package/ux-demo/recipes/loaders.ts +5 -2
  183. package/ux-demo/recipes/loading-inspector.tsx +157 -160
  184. package/ux-demo/recipes/navigation-sidebar.tsx +111 -109
  185. package/ux-demo/recipes/navigation-stack.tsx +50 -49
  186. package/ux-demo/recipes/types.ts +14 -0
  187. package/ux-demo/recipes/workspace-header.tsx +73 -56
  188. package/dist/chunk-54HM36IQ.js.map +0 -1
  189. package/dist/chunk-AQSKJ6U6.js.map +0 -1
  190. package/dist/chunk-COUPLRZG.js.map +0 -1
  191. package/dist/chunk-ELRMBQEC.js.map +0 -1
  192. package/dist/chunk-J3EE6XWD.js +0 -115
  193. package/dist/chunk-J3EE6XWD.js.map +0 -1
  194. package/dist/chunk-KKNK3JUT.js.map +0 -1
  195. package/dist/chunk-OL4TC3U2.js.map +0 -1
  196. package/dist/chunk-OQI7RCRP.js.map +0 -1
  197. package/dist/chunk-UMGY5JSP.js.map +0 -1
  198. package/dist/chunk-V6RZWDII.js.map +0 -1
  199. package/dist/chunk-ZNZ2JDBW.js.map +0 -1
  200. package/dist/chunk-ZSYLBVWG.js.map +0 -1
  201. package/ux-demo/recipes/app-shell.css +0 -87
  202. package/ux-demo/recipes/collapsible-sidebar.css +0 -83
  203. package/ux-demo/recipes/compact-toolbar.css +0 -35
  204. package/ux-demo/recipes/composer-form.css +0 -19
  205. package/ux-demo/recipes/list-detail-dialog.css +0 -42
  206. package/ux-demo/recipes/list-workspace-states.css +0 -30
  207. package/ux-demo/recipes/loading-inspector.css +0 -7
  208. package/ux-demo/recipes/navigation-sidebar.css +0 -12
  209. package/ux-demo/recipes/navigation-stack.css +0 -12
  210. package/ux-demo/recipes/recipe-root.css +0 -67
  211. package/ux-demo/recipes/recipe-root.tsx +0 -87
  212. package/ux-demo/recipes/workspace-header.css +0 -7
@@ -1,11 +1,7 @@
1
1
  .kui-toolbar {
2
2
  display: grid;
3
3
  width: 100%;
4
- min-height: calc(
5
- var(--kui-toolbar-group-size, calc(2px + 2.625rem)) +
6
- (2 * var(--kui-toolbar-gap, 0.5rem))
7
- );
8
- padding: var(--kui-toolbar-gap, 0.5rem);
4
+ /* min-height and padding: see the safe-area rules below. */
9
5
  box-sizing: border-box;
10
6
  color: var(--kui-color-text);
11
7
  background: transparent;
@@ -16,6 +12,78 @@
16
12
  font-family: var(--kui-font-sans);
17
13
  }
18
14
 
15
+ /* Safe areas: a toolbar compensates only for the inline edges its owner hands
16
+ it through `--kui-edge-inset-inline-*` (a Pane header or footer does), so its
17
+ box and dividers keep reaching the edge while its zones clear the unsafe
18
+ area. Unset means none: a toolbar embedded in content never pads for the
19
+ device. An app bar or bottom bar at a screen edge claims its sides with
20
+ `safeAreaEdges`; each claimed side pads by the routed edge context, or the
21
+ full device inset when nothing routes it (the Pane rule), so the top bar of
22
+ a shell clears the status area. A Pane header or footer clears the context
23
+ for its children, so a claimed side there never double-insets. Its zones
24
+ then see a cleared context. */
25
+ .kui-toolbar {
26
+ --_kui-toolbar-safe-block-start: 0px;
27
+ --_kui-toolbar-safe-block-end: 0px;
28
+ --_kui-toolbar-safe-inline-start: var(--kui-edge-inset-inline-start, 0px);
29
+ --_kui-toolbar-safe-inline-end: var(--kui-edge-inset-inline-end, 0px);
30
+ /* The minimum height grows by any claimed block inset, so a toolbar a flex
31
+ column shrinks still keeps a whole control row clear of the unsafe area. */
32
+ min-height: calc(
33
+ var(--kui-toolbar-group-size, calc(2px + 2.625rem)) +
34
+ (2 * var(--kui-toolbar-gap, 0.5rem)) +
35
+ var(--_kui-toolbar-safe-block-start) + var(--_kui-toolbar-safe-block-end)
36
+ );
37
+ padding-block: calc(
38
+ var(--kui-toolbar-gap, 0.5rem) + var(--_kui-toolbar-safe-block-start)
39
+ )
40
+ calc(
41
+ var(--kui-toolbar-gap, 0.5rem) + var(--_kui-toolbar-safe-block-end)
42
+ );
43
+ padding-inline: calc(
44
+ var(--kui-toolbar-gap, 0.5rem) +
45
+ var(--_kui-toolbar-safe-inline-start)
46
+ )
47
+ calc(
48
+ var(--kui-toolbar-gap, 0.5rem) + var(--_kui-toolbar-safe-inline-end)
49
+ );
50
+ }
51
+
52
+ .kui-toolbar[data-safe-area-block-start="true"] {
53
+ --_kui-toolbar-safe-block-start: var(
54
+ --kui-edge-inset-block-start,
55
+ var(--kui-safe-area-block-start, env(safe-area-inset-top, 0px))
56
+ );
57
+ }
58
+
59
+ .kui-toolbar[data-safe-area-block-end="true"] {
60
+ --_kui-toolbar-safe-block-end: var(
61
+ --kui-edge-inset-block-end,
62
+ var(--kui-safe-area-block-end, env(safe-area-inset-bottom, 0px))
63
+ );
64
+ }
65
+
66
+ .kui-toolbar[data-safe-area-inline-start="true"] {
67
+ --_kui-toolbar-safe-inline-start: var(
68
+ --kui-edge-inset-inline-start,
69
+ var(--kui-safe-area-inline-start, env(safe-area-inset-left, 0px))
70
+ );
71
+ }
72
+
73
+ .kui-toolbar[data-safe-area-inline-end="true"] {
74
+ --_kui-toolbar-safe-inline-end: var(
75
+ --kui-edge-inset-inline-end,
76
+ var(--kui-safe-area-inline-end, env(safe-area-inset-right, 0px))
77
+ );
78
+ }
79
+
80
+ .kui-toolbar > * {
81
+ --kui-edge-inset-block-start: 0px;
82
+ --kui-edge-inset-block-end: 0px;
83
+ --kui-edge-inset-inline-start: 0px;
84
+ --kui-edge-inset-inline-end: 0px;
85
+ }
86
+
19
87
  .kui-toolbar[data-has-center="false"] {
20
88
  grid-template-columns: minmax(0, 1fr) auto;
21
89
  }
@@ -97,6 +165,35 @@
97
165
  .kui-toolbar__trailing {
98
166
  justify-content: flex-end;
99
167
  grid-column: -2 / -1;
168
+ /* Trailing actions never clip (KF-4EYES0): the zone wraps its groups, so its
169
+ track can shrink only to the widest single group. When one row cannot hold
170
+ every group, the next group moves to a new trailing-aligned row instead of
171
+ overflowing the toolbar. */
172
+ flex-wrap: wrap;
173
+ }
174
+
175
+ /* Content-driven wrap: one row while every zone fits at its natural width;
176
+ otherwise the trailing zone moves below the identity, still trailing-aligned,
177
+ instead of truncating the leading title or clipping an action. For panel,
178
+ inspector, and dialog headings whose identity must stay whole. */
179
+ .kui-toolbar[data-responsive="wrap"] {
180
+ display: flex;
181
+ flex-wrap: wrap;
182
+ }
183
+
184
+ .kui-toolbar[data-responsive="wrap"] > .kui-toolbar__leading {
185
+ max-width: 100%;
186
+ flex: 1 1 auto;
187
+ }
188
+
189
+ .kui-toolbar[data-responsive="wrap"] > .kui-toolbar__center {
190
+ flex: 1 1 auto;
191
+ }
192
+
193
+ .kui-toolbar[data-responsive="wrap"] > .kui-toolbar__trailing {
194
+ max-width: 100%;
195
+ flex: 0 1 auto;
196
+ margin-inline-start: auto;
100
197
  }
101
198
 
102
199
  .kui-toolbar[data-has-center="false"] .kui-toolbar__center,
@@ -105,59 +202,54 @@
105
202
  display: none;
106
203
  }
107
204
 
205
+ /* Stacked topology. A container query only matches an ancestor container, so
206
+ these rules style the zones (whose query container is this toolbar) rather
207
+ than the toolbar's own grid template, which a self query can never reach.
208
+ Each zone spans every column track, one zone per row, so no empty track or
209
+ column gap narrows a stacked zone. Stacked control zones wrap at group
210
+ granularity (KF-4EYES0: a stacked trailing zone clipped its last action at
211
+ phone widths), so every group stays whole, reachable, and full size; the
212
+ leading identity keeps its single-line truncation. */
108
213
  @container kui-toolbar (max-width: 52rem) {
109
- .kui-toolbar[data-responsive="stack"][data-responsive-at="narrow"] {
110
- height: auto;
111
- grid-template-columns: minmax(0, 1fr);
112
- grid-template-areas: "leading" "center" "trailing";
113
- row-gap: var(--kui-toolbar-gap, 0.5rem);
114
- }
115
-
116
214
  .kui-toolbar[data-responsive="stack"][data-responsive-at="narrow"]
117
- > .kui-toolbar__leading,
118
- .kui-toolbar[data-responsive="stack"][data-responsive-at="narrow"]
119
- > .kui-toolbar__center,
120
- .kui-toolbar[data-responsive="stack"][data-responsive-at="narrow"]
121
- > .kui-toolbar__trailing {
215
+ > :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing) {
122
216
  width: 100%;
123
- grid-column: 1;
217
+ grid-column: 1 / -1;
124
218
  justify-content: flex-start;
125
219
  }
126
- }
127
220
 
128
- @container kui-toolbar (max-width: 44rem) {
129
- .kui-toolbar[data-responsive="stack"][data-responsive-at="compact"] {
130
- height: auto;
131
- grid-template-columns: minmax(0, 1fr);
132
- grid-template-areas: "leading" "center" "trailing";
133
- row-gap: var(--kui-toolbar-gap, 0.5rem);
221
+ .kui-toolbar[data-responsive="stack"][data-responsive-at="narrow"]
222
+ > :is(.kui-toolbar__center, .kui-toolbar__trailing) {
223
+ flex-wrap: wrap;
134
224
  }
225
+ }
135
226
 
227
+ @container kui-toolbar (max-width: 44rem) {
136
228
  .kui-toolbar[data-responsive="stack"][data-responsive-at="compact"]
137
- > .kui-toolbar__leading,
138
- .kui-toolbar[data-responsive="stack"][data-responsive-at="compact"]
139
- > .kui-toolbar__center,
140
- .kui-toolbar[data-responsive="stack"][data-responsive-at="compact"]
141
- > .kui-toolbar__trailing {
229
+ > :is(.kui-toolbar__leading, .kui-toolbar__center, .kui-toolbar__trailing) {
142
230
  width: 100%;
143
- grid-column: 1;
231
+ grid-column: 1 / -1;
144
232
  justify-content: flex-start;
145
233
  }
146
- }
147
234
 
148
- @container kui-toolbar (max-width: 42rem) {
149
- .kui-toolbar[data-responsive="center-priority"]:has(
150
- > .kui-toolbar__center [data-expanded="true"]
151
- ) {
152
- grid-template-columns: minmax(0, 1fr);
235
+ .kui-toolbar[data-responsive="stack"][data-responsive-at="compact"]
236
+ > :is(.kui-toolbar__center, .kui-toolbar__trailing) {
237
+ flex-wrap: wrap;
153
238
  }
239
+ }
154
240
 
241
+ /* Center priority. As with the stacked topology, the toolbar cannot query its
242
+ own width, so these rules style the zones: while the center holds an expanded
243
+ control, the leading and trailing zones leave and the center spans every
244
+ column track (including the gaps the vacated tracks still own), taking the
245
+ whole row. */
246
+ @container kui-toolbar (max-width: 42rem) {
155
247
  .kui-toolbar[data-responsive="center-priority"]:has(
156
248
  > .kui-toolbar__center [data-expanded="true"]
157
249
  )
158
250
  > .kui-toolbar__center {
159
251
  width: 100%;
160
- grid-column: 1;
252
+ grid-column: 1 / -1;
161
253
  }
162
254
 
163
255
  .kui-toolbar[data-responsive="center-priority"]:has(
@@ -177,8 +177,8 @@
177
177
  --wa-form-control-activated-color: var(--wa-color-brand-fill-loud);
178
178
  /* Field inset: a 1px border with 8px inside it, the same content-item
179
179
  geometry the Kerf primitives use (docs/layout.md). WA's defaults are a
180
- larger 0.75em/1em pad; this brings every form field onto our 8px rhythm
181
- (and a ~40px single-line control height). */
180
+ larger 0.75em/1em pad; this brings every form field onto our 8px rhythm.
181
+ The single-line control height is set on the controls below. */
182
182
  --wa-form-control-border-width: var(--kui-layout-item-border-width, 1px);
183
183
  --wa-form-control-padding-block: var(
184
184
  --kui-layout-item-padding,
@@ -193,6 +193,18 @@
193
193
  --wa-tooltip-content-color: var(--wa-color-neutral-on-loud);
194
194
  --wa-tooltip-arrow-size: 0px;
195
195
  --kui-wa-popover-arrow-size: 0px;
196
+ /* Web Awesome's own `ui-monospace, monospace` renders the generic face
197
+ (Courier) in Chromium; use Kerf's named code stack for its components. */
198
+ --wa-font-family-code: var(
199
+ --kui-font-mono,
200
+ ui-monospace,
201
+ SFMono-Regular,
202
+ "SF Mono",
203
+ Menlo,
204
+ Consolas,
205
+ "Liberation Mono",
206
+ monospace
207
+ );
196
208
  --kui-wa-carousel-dot-size: 7px;
197
209
  --kui-wa-carousel-dot-hit-size: 20px;
198
210
  --kui-wa-carousel-control-size: 28px;
@@ -403,6 +415,17 @@
403
415
  padding: var(--kui-wa-surface-inset);
404
416
  }
405
417
 
418
+ /* A plain-string `label` renders straight into the title part, so it would
419
+ sit at the bare surface inset while body content (list-inset text, Text)
420
+ starts one bordered item further in. Inset the default title by that item
421
+ step so the header label lines up with the body's text and icons. A
422
+ slotted label element owns its own geometry and is left alone. */
423
+ wa-dialog:not(:has(> [slot="label"]))::part(title) {
424
+ padding-inline: calc(
425
+ var(--kui-layout-item-padding, 0.5rem) + 0.0625rem
426
+ );
427
+ }
428
+
406
429
  wa-dialog::part(body) {
407
430
  padding: var(--kui-wa-surface-inset);
408
431
  }
@@ -457,6 +480,29 @@
457
480
  --arrow-size: var(--kui-wa-popover-arrow-size);
458
481
  }
459
482
 
483
+ /* Default-size single-line controls share Kerf's 44px control height (the
484
+ Select field, toolbar groups, and list rows), so a Web Awesome field,
485
+ button, or button radio sits flush beside a Kerf Select instead of 9px
486
+ shorter. The height is 2.75em of the control's own font size (44px at the
487
+ 16px default), so size="large" grows proportionally; size="small" stays
488
+ Web Awesome's deliberately compact secondary control. It is scoped to the
489
+ controls rather than the root because Web Awesome also derives tag, copy
490
+ button, and toast geometry from this token. A ToolbarControlGroup still
491
+ sizes its own nested buttons. */
492
+ :is(
493
+ wa-button,
494
+ wa-color-picker,
495
+ wa-input,
496
+ wa-known-date,
497
+ wa-number-input,
498
+ wa-radio-group,
499
+ wa-select,
500
+ wa-textarea,
501
+ wa-time-input
502
+ ):not([size="small"]) {
503
+ --wa-form-control-height: round(2.75em, 1px);
504
+ }
505
+
460
506
  /* A form field's top label is inset by the field's own border + inline padding
461
507
  (1px + 8px = 9px) so the label text lines up with the value inside the field,
462
508
  and it is styled exactly like a ListHeader label: uppercase, xs, quiet. */
@@ -6,23 +6,56 @@
6
6
  height: 100%;
7
7
  overflow: hidden;
8
8
  background: var(--kui-color-surface);
9
+ /* Safe areas: every track paints through the unsafe area it reaches (a rail
10
+ or drawer grows by that inset), and each region's content compensates only
11
+ for the edges the region still touches. An expanded inline rail takes its
12
+ edge away from the center; collapsing it hands the edge back. See pane.css
13
+ for the `--kui-edge-inset-*` edge context. */
14
+ --_kui-workbench-safe-block-start: var(
15
+ --kui-edge-inset-block-start,
16
+ var(--kui-safe-area-block-start, env(safe-area-inset-top, 0px))
17
+ );
18
+ --_kui-workbench-safe-block-end: var(
19
+ --kui-edge-inset-block-end,
20
+ var(--kui-safe-area-block-end, env(safe-area-inset-bottom, 0px))
21
+ );
22
+ --_kui-workbench-safe-inline-start: var(
23
+ --kui-edge-inset-inline-start,
24
+ var(--kui-safe-area-inline-start, env(safe-area-inset-left, 0px))
25
+ );
26
+ --_kui-workbench-safe-inline-end: var(
27
+ --kui-edge-inset-inline-end,
28
+ var(--kui-safe-area-inline-end, env(safe-area-inset-right, 0px))
29
+ );
9
30
  }
10
31
 
11
32
  /* Side rails. Collapsing snaps the flex track to zero (one reflow) while the
12
33
  fixed-width content slides out via transform (composited, clipped) — never a
13
34
  per-frame width animation. See docs/18 / the KF-7QKJRK catalog sidebar. */
14
35
  .kui-workbench__rail {
15
- flex: 0 0 var(--kui-workbench-rail-width, 17.5rem);
36
+ --kui-edge-inset-block-start: var(--_kui-workbench-safe-block-start);
37
+ --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
38
+ --_kui-workbench-rail-extent: calc(
39
+ var(--kui-workbench-rail-width, 17.5rem) +
40
+ var(--_kui-workbench-rail-safe-inset)
41
+ );
42
+ flex: 0 0 var(--_kui-workbench-rail-extent);
16
43
  min-width: 0;
17
44
  overflow: hidden;
18
45
  background: var(--kui-color-surface);
19
46
  }
20
47
 
21
48
  .kui-workbench__rail--left {
49
+ --_kui-workbench-rail-safe-inset: var(--_kui-workbench-safe-inline-start);
50
+ --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
51
+ --kui-edge-inset-inline-end: 0px;
22
52
  border-inline-end: 1px solid var(--kui-color-border);
23
53
  }
24
54
 
25
55
  .kui-workbench__rail--right {
56
+ --_kui-workbench-rail-safe-inset: var(--_kui-workbench-safe-inline-end);
57
+ --kui-edge-inset-inline-start: 0px;
58
+ --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
26
59
  border-inline-start: 1px solid var(--kui-color-border);
27
60
  }
28
61
 
@@ -37,6 +70,10 @@
37
70
  }
38
71
 
39
72
  .kui-workbench__center {
73
+ --kui-edge-inset-block-start: var(--_kui-workbench-safe-block-start);
74
+ --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
75
+ --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
76
+ --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
40
77
  display: flex;
41
78
  flex: 1;
42
79
  min-width: 0;
@@ -45,16 +82,111 @@
45
82
  flex-direction: column;
46
83
  }
47
84
 
85
+ .kui-workbench:has(
86
+ > .kui-workbench__rail--left[data-presentation="inline"]:not(
87
+ [data-collapsed="true"]
88
+ )
89
+ )
90
+ > .kui-workbench__center {
91
+ --kui-edge-inset-inline-start: 0px;
92
+ }
93
+
94
+ .kui-workbench:has(
95
+ > .kui-workbench__rail--right[data-presentation="inline"]:not(
96
+ [data-collapsed="true"]
97
+ )
98
+ )
99
+ > .kui-workbench__center {
100
+ --kui-edge-inset-inline-end: 0px;
101
+ }
102
+
48
103
  .kui-workbench__main {
49
104
  flex: 1;
50
105
  min-height: 0;
51
106
  overflow: auto;
52
107
  }
53
108
 
109
+ .kui-workbench__main:has(
110
+ ~ .kui-workbench__drawer[data-presentation="inline"]:not(
111
+ [data-collapsed="true"]
112
+ )
113
+ ) {
114
+ --kui-edge-inset-block-end: 0px;
115
+ }
116
+
117
+ /* The main area and each panel's content are the consumers: they pad for the
118
+ edges their region still touches, the main area inside its scroller so
119
+ content scrolls under an unsafe edge but can always be scrolled clear. A
120
+ region whose only child is a Pane or layout lets that child own the insets. */
121
+ .kui-workbench__main:not(
122
+ :has(
123
+ > :is(
124
+ [data-component="pane"],
125
+ [data-component="nav-stack"],
126
+ [data-component="split-view"],
127
+ [data-component="workbench"],
128
+ [data-component="tab-scaffold"]
129
+ ):only-child
130
+ )
131
+ ),
132
+ .kui-workbench__panel-content:not(
133
+ :has(
134
+ > :is(
135
+ [data-component="pane"],
136
+ [data-component="nav-stack"],
137
+ [data-component="split-view"],
138
+ [data-component="workbench"],
139
+ [data-component="tab-scaffold"]
140
+ ):only-child
141
+ )
142
+ ) {
143
+ padding-block: var(--kui-edge-inset-block-start)
144
+ var(--kui-edge-inset-block-end);
145
+ padding-inline: var(--kui-edge-inset-inline-start)
146
+ var(--kui-edge-inset-inline-end);
147
+ scroll-padding-block: var(--kui-edge-inset-block-start)
148
+ var(--kui-edge-inset-block-end);
149
+ }
150
+
151
+ .kui-workbench__main:not(
152
+ :has(
153
+ > :is(
154
+ [data-component="pane"],
155
+ [data-component="nav-stack"],
156
+ [data-component="split-view"],
157
+ [data-component="workbench"],
158
+ [data-component="tab-scaffold"]
159
+ ):only-child
160
+ )
161
+ )
162
+ > *,
163
+ .kui-workbench__panel-content:not(
164
+ :has(
165
+ > :is(
166
+ [data-component="pane"],
167
+ [data-component="nav-stack"],
168
+ [data-component="split-view"],
169
+ [data-component="workbench"],
170
+ [data-component="tab-scaffold"]
171
+ ):only-child
172
+ )
173
+ )
174
+ > * {
175
+ --kui-edge-inset-block-start: 0px;
176
+ --kui-edge-inset-block-end: 0px;
177
+ --kui-edge-inset-inline-start: 0px;
178
+ --kui-edge-inset-inline-end: 0px;
179
+ }
180
+
54
181
  /* Bottom drawer — the vertical analogue of a rail. */
55
182
  .kui-workbench__drawer {
183
+ --kui-edge-inset-block-start: 0px;
184
+ --_kui-workbench-drawer-extent: calc(
185
+ var(--kui-workbench-drawer-height, 13.75rem) +
186
+ var(--_kui-workbench-safe-block-end)
187
+ );
56
188
  position: relative;
57
- flex: 0 0 var(--kui-workbench-drawer-height, 13.75rem);
189
+ flex: 0 0 var(--_kui-workbench-drawer-extent);
58
190
  min-height: 0;
59
191
  overflow: hidden;
60
192
  border-block-start: 1px solid var(--kui-color-border);
@@ -80,7 +212,7 @@
80
212
  /* The fixed-size sliding content. The rail/drawer keeps a definite size on its
81
213
  content so the transform reads as a slide, not a squeeze. */
82
214
  .kui-workbench__rail .kui-workbench__panel-content {
83
- width: var(--kui-workbench-rail-width, 17.5rem);
215
+ width: var(--_kui-workbench-rail-extent);
84
216
  height: 100%;
85
217
  transition: transform 200ms ease;
86
218
  }
@@ -90,7 +222,7 @@
90
222
  inset-inline: 0;
91
223
  inset-block-end: 0;
92
224
  width: 100%;
93
- height: var(--kui-workbench-drawer-height, 13.75rem);
225
+ height: var(--_kui-workbench-drawer-extent);
94
226
  transition: transform 200ms ease;
95
227
  }
96
228
 
package/dist/text.d.ts CHANGED
@@ -2,14 +2,14 @@ import * as kerfjs from 'kerfjs';
2
2
  import { KerfBaseAttrs } from 'kerfjs/jsx-runtime';
3
3
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
4
4
 
5
- type TextVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
5
+ type TextVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span';
6
6
  type TextTone = 'default' | 'quiet' | 'danger';
7
7
  type TextSize = 'default' | 'compact';
8
8
  type TextFont = 'default' | 'monospace';
9
9
  type TextBorder = 'transparent' | 'none';
10
10
  type TextContent = KerfUiContent | string | number | readonly TextContent[];
11
11
  type TextProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> & {
12
- /** Native heading or paragraph element to render. Defaults to `p`. */
12
+ /** Native heading, paragraph, or inline span element to render. Defaults to `p`. */
13
13
  variant?: TextVariant;
14
14
  /** Semantic foreground treatment. Defaults to the inherited foreground. */
15
15
  tone?: TextTone;
@@ -24,7 +24,8 @@ type TextProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> & {
24
24
  className?: string;
25
25
  };
26
26
  /**
27
- * Semantic heading or paragraph text with the standard content-item padding.
27
+ * Semantic heading, paragraph, or inline text. Block variants use the standard
28
+ * content-item padding; `span` adds no box geometry.
28
29
  * All ordinary native heading/paragraph attributes pass through to the element.
29
30
  */
30
31
  declare function Text({ variant: Variant, tone, size, font, border, children, class: classValue, className, ...attributes }: TextProps): kerfjs.SafeHtml;
package/dist/text.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Text } from './chunk-ZNZ2JDBW.js';
1
+ export { Text } from './chunk-7IUSV4XG.js';
2
2
 
3
3
  //# sourceMappingURL=text.js.map
@@ -1,4 +1,4 @@
1
- export { ToolbarText } from './chunk-DJKWFYAA.js';
1
+ export { ToolbarText } from './chunk-YZV2PTY4.js';
2
2
 
3
3
 
4
4
 
package/dist/toolbar.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as kerfjs from 'kerfjs';
2
2
  import { D as DividerSides } from './divider-sides-BzB6rphT.js';
3
+ import { PaneSeparatorSide } from './pane.js';
3
4
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
4
5
 
5
6
  interface ToolbarProps {
@@ -11,14 +12,34 @@ interface ToolbarProps {
11
12
  dividerSides?: DividerSides;
12
13
  /** Horizontal treatment of the center zone. Defaults to centered content. */
13
14
  centerAlign?: 'center' | 'stretch';
14
- /** Component-owned responsive layout; applications choose the policy rather than restyling toolbar internals. */
15
- responsive?: 'none' | 'stack' | 'center-priority';
15
+ /**
16
+ * Component-owned responsive layout; applications choose the policy rather
17
+ * than restyling toolbar internals. Under every policy the trailing zone
18
+ * wraps its groups instead of clipping an action.
19
+ * - `none` keeps one row; the leading identity truncates first.
20
+ * - `stack` stacks the zones at `responsiveAt`, wrapping stacked control
21
+ * groups onto further rows.
22
+ * - `wrap` keeps one row while every zone fits at its natural width, and
23
+ * otherwise moves the trailing zone below a whole leading identity.
24
+ * - `center-priority` gives an expanded center control the full row.
25
+ */
26
+ responsive?: 'none' | 'stack' | 'wrap' | 'center-priority';
16
27
  /** Container width at which `responsive="stack"` activates. */
17
28
  responsiveAt?: 'compact' | 'narrow';
29
+ /**
30
+ * Screen edges this toolbar claims for device safe-area compensation, for an
31
+ * app bar or bottom bar that sits directly at a screen edge rather than in a
32
+ * Pane header or footer. Each listed side pads by the inset its surrounding
33
+ * layout reports through `--kui-edge-inset-*`, or the full device inset when
34
+ * nothing routes that edge, while the toolbar's box and dividers paint
35
+ * through. Omitted, the toolbar pads only the inline edges an owner such as
36
+ * a Pane header hands it, and never a block edge.
37
+ */
38
+ safeAreaEdges?: readonly PaneSeparatorSide[];
18
39
  className?: string;
19
40
  /** Native named-slot assignment when composed inside a web component. */
20
41
  slot?: string;
21
42
  }
22
- declare function Toolbar({ leading, center, trailing, label, dividerSides, centerAlign, responsive, responsiveAt, className, slot, }: ToolbarProps): kerfjs.SafeHtml;
43
+ declare function Toolbar({ leading, center, trailing, label, dividerSides, centerAlign, responsive, responsiveAt, safeAreaEdges, className, slot, }: ToolbarProps): kerfjs.SafeHtml;
23
44
 
24
45
  export { DividerSides, Toolbar, type ToolbarProps };
package/dist/toolbar.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Toolbar } from './chunk-OQI7RCRP.js';
1
+ export { Toolbar } from './chunk-MTM43SJO.js';
2
2
 
3
3
  //# sourceMappingURL=toolbar.js.map
@@ -40,6 +40,9 @@ interface CatalogBrand {
40
40
  /** Logo image URL (rendered decorative). Omit for a text-only brand. */
41
41
  logoUrl?: string;
42
42
  }
43
+ type CatalogStageRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
44
+ 'data-catalog-stage'?: never;
45
+ }>;
43
46
  interface CatalogProps {
44
47
  brand: CatalogBrand;
45
48
  sections: readonly CatalogSection[];
@@ -52,6 +55,8 @@ interface CatalogProps {
52
55
  sidebarFooter?: KerfUiContent;
53
56
  status?: KerfUiContent;
54
57
  geometryOverlay?: boolean;
58
+ /** Consumer-owned data attributes applied to the preview stage. */
59
+ stageRootAttributes?: CatalogStageRootAttributes;
55
60
  selectAction?: string;
56
61
  toggleSidebarAction?: string;
57
62
  toggleThemeAction?: string;
@@ -61,4 +66,4 @@ interface CatalogProps {
61
66
  slot?: string;
62
67
  }
63
68
 
64
- export type { CatalogResource as C, CatalogProps as a, CatalogBrand as b, CatalogEntry as c, CatalogRelated as d, CatalogSecondaryGroup as e, CatalogSection as f };
69
+ export type { CatalogResource as C, CatalogProps as a, CatalogBrand as b, CatalogEntry as c, CatalogRelated as d, CatalogSecondaryGroup as e, CatalogSection as f, CatalogStageRootAttributes as g };
@@ -1,4 +1,4 @@
1
- export { ValueTable, ValueTableRow } from './chunk-SIU2AC5W.js';
1
+ export { ValueTable, ValueTableRow } from './chunk-2S7OWAEV.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { wireResizableRegions } from './chunk-J3EE6XWD.js';
1
+ export { wireResizableRegions } from './chunk-EKYZCRCX.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=wire-resizable-regions.js.map
@@ -14,9 +14,18 @@ interface WireSidebarPanel {
14
14
  collapsed: Signal<boolean>;
15
15
  /** `data-action` value the panel's toggle button(s) carry. */
16
16
  toggleAction: string;
17
- /** When set, the collapsed state is loaded from and saved to `storage` under
18
- * this key (a persistence hook), so the panel remembers its state. */
17
+ /** When set, the panel's inline collapsed state is loaded from and saved to
18
+ * `storage` under this key (a persistence hook), so the panel remembers the
19
+ * user's inline choice. A compact overlay's open/closed state is transient
20
+ * and is never persisted. */
19
21
  storageKey?: string;
22
+ /** The panel's inline (non-compact) collapsed state when `storage` holds no
23
+ * choice for it; wire-up seeds the signal with it. Defaults to the signal's
24
+ * own value. Set it when the app seeds the signal from the device class
25
+ * (`signal(device.value.compact)`) so a compact first render already starts
26
+ * collapsed while a later crossing to a wide class still restores this
27
+ * inline default. */
28
+ inlineCollapsed?: boolean;
20
29
  }
21
30
  interface WireSidebarOptions {
22
31
  panels: readonly WireSidebarPanel[];
@@ -26,6 +35,12 @@ interface WireSidebarOptions {
26
35
  * dismissable backdrop, Escape and backdrop-click collapse it, and focus is
27
36
  * trapped within the open panel (the ARIA dialog pattern). Without it the panel
28
37
  * is always inline.
38
+ *
39
+ * An overlay is transient and user-initiated: whenever the compact overlay
40
+ * presentation begins (wire-up on a compact device, or a crossing from a wide
41
+ * class), every panel starts collapsed so nothing blocks the page until the
42
+ * user opens it. The inline state is remembered and restored when the device
43
+ * crosses back to a wide class (and on disposal).
29
44
  */
30
45
  deviceClass?: ReadonlySignal<DeviceClass>;
31
46
  /** Compact devices either overlay the panels (default) or hide them in favor
@@ -41,7 +56,7 @@ interface WireSidebarOptions {
41
56
  * delegation with focus restore, focus-into on open, an optional compact overlay
42
57
  * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional
43
58
  * persistence hook. The app owns each `collapsed` signal and the layout; this wire
44
- * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.
59
+ * owns the interaction. Returns a disposer. See `ui/docs/collapsible-panel.md` and `docs/23-app-layouts.md`.
45
60
  */
46
61
  declare function wireSidebar(root: HTMLElement, { panels, deviceClass, compactPresentation, exclusiveCompact, storage, }: WireSidebarOptions): () => void;
47
62