@kerfjs/ui 5.0.0-beta.3 → 5.0.0-beta.31

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 (470) hide show
  1. package/README.md +320 -107
  2. package/ai/application-ui-diagnostic-ids-v1.json +100 -0
  3. package/ai/application-ui-profile-sync.cjs +529 -0
  4. package/ai/application-ui-profile.d.mts +1 -0
  5. package/ai/application-ui-profile.d.ts +102 -0
  6. package/ai/application-ui-profile.defaults.json +41 -0
  7. package/ai/application-ui-profile.mjs +626 -0
  8. package/ai/application-ui-profile.schema.json +130 -0
  9. package/ai/catalog-authoring.json +25 -0
  10. package/ai/catalog-authoring.schema.json +61 -0
  11. package/ai/compile-time-contracts-v1.json +131 -0
  12. package/ai/compile-time-contracts-v1.schema.json +51 -0
  13. package/ai/component-catalog-extension-v2.d.ts +7 -0
  14. package/ai/component-catalog-extension-v2.schema.json +7 -0
  15. package/ai/component-catalog-extension.schema.json +79 -0
  16. package/ai/component-catalog-v2-overrides.json +486 -0
  17. package/ai/component-catalog-v2.d.ts +90 -0
  18. package/ai/component-catalog-v2.json +5455 -0
  19. package/ai/component-catalog-v2.schema.json +298 -0
  20. package/ai/component-catalog.json +2437 -419
  21. package/ai/component-catalog.schema.json +39 -0
  22. package/ai/public-api-signatures-v1.md +1442 -124
  23. package/ai/skill.md +131 -54
  24. package/ai/webawesome-jsx-signatures-v1.md +2 -1
  25. package/analyzer/cli.mjs +49 -0
  26. package/analyzer/index.d.mts +46 -0
  27. package/analyzer/index.mjs +1086 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +15 -4
  30. package/dist/app-tab.js +3 -1
  31. package/dist/browser/app-tab.js +1 -0
  32. package/dist/browser/floating-toolbar.js +3 -0
  33. package/dist/browser/list-action-row.js +5 -0
  34. package/dist/browser/{menu-header.js → list-header.js} +3 -2
  35. package/dist/browser/list-inset-control.js +3 -0
  36. package/dist/browser/list-inset-text.js +3 -0
  37. package/dist/browser/list-item.js +5 -0
  38. package/dist/browser/list.js +3 -0
  39. package/dist/browser/pane.js +3 -0
  40. package/dist/browser/segmented-control.js +1 -0
  41. package/dist/browser/select.js +1 -0
  42. package/dist/browser/skeleton.js +3 -0
  43. package/dist/browser/state-banner.js +1 -0
  44. package/dist/browser/sunken-panel.js +3 -0
  45. package/dist/browser/surface-scaffold.js +3 -0
  46. package/dist/browser/toolbar-text.js +1 -0
  47. package/dist/browser/value-table.js +1 -0
  48. package/dist/catalog-resources.d.ts +35 -0
  49. package/dist/catalog-resources.js +22 -0
  50. package/dist/catalog-resources.js.map +1 -0
  51. package/dist/catalog.d.ts +151 -0
  52. package/dist/catalog.js +379 -0
  53. package/dist/catalog.js.map +1 -0
  54. package/dist/chunk-6NDBE2OK.js +32 -0
  55. package/dist/chunk-6NDBE2OK.js.map +1 -0
  56. package/dist/chunk-6WTETUZ6.js +21 -0
  57. package/dist/chunk-6WTETUZ6.js.map +1 -0
  58. package/dist/chunk-AQFWBG23.js +83 -0
  59. package/dist/chunk-AQFWBG23.js.map +1 -0
  60. package/dist/chunk-B6JYRX3P.js +51 -0
  61. package/dist/chunk-B6JYRX3P.js.map +1 -0
  62. package/dist/chunk-CAB6NQ2T.js +102 -0
  63. package/dist/chunk-CAB6NQ2T.js.map +1 -0
  64. package/dist/chunk-CK6P5AUK.js +136 -0
  65. package/dist/chunk-CK6P5AUK.js.map +1 -0
  66. package/dist/chunk-DAO2VFTT.js +129 -0
  67. package/dist/chunk-DAO2VFTT.js.map +1 -0
  68. package/dist/chunk-DYBPI5O2.js +25 -0
  69. package/dist/chunk-DYBPI5O2.js.map +1 -0
  70. package/dist/{chunk-TRNBHPNW.js → chunk-E34TE2ZI.js} +12 -8
  71. package/dist/chunk-E34TE2ZI.js.map +1 -0
  72. package/dist/chunk-I45QO2IZ.js +88 -0
  73. package/dist/chunk-I45QO2IZ.js.map +1 -0
  74. package/dist/chunk-I4V3UDSA.js +79 -0
  75. package/dist/chunk-I4V3UDSA.js.map +1 -0
  76. package/dist/chunk-KFUUFNTS.js +15 -0
  77. package/dist/chunk-KFUUFNTS.js.map +1 -0
  78. package/dist/chunk-MFR2IIFG.js +20 -0
  79. package/dist/chunk-MFR2IIFG.js.map +1 -0
  80. package/dist/chunk-OC275HHS.js +128 -0
  81. package/dist/chunk-OC275HHS.js.map +1 -0
  82. package/dist/chunk-OL4TC3U2.js +156 -0
  83. package/dist/chunk-OL4TC3U2.js.map +1 -0
  84. package/dist/chunk-OMSRSL3B.js +120 -0
  85. package/dist/chunk-OMSRSL3B.js.map +1 -0
  86. package/dist/chunk-RL6COPKF.js +44 -0
  87. package/dist/chunk-RL6COPKF.js.map +1 -0
  88. package/dist/chunk-SOK5EUF7.js +25 -0
  89. package/dist/chunk-SOK5EUF7.js.map +1 -0
  90. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  91. package/dist/chunk-SRSJO5QE.js.map +1 -0
  92. package/dist/chunk-T7DOWS6T.js +61 -0
  93. package/dist/chunk-T7DOWS6T.js.map +1 -0
  94. package/dist/chunk-TIGSG6BX.js +218 -0
  95. package/dist/chunk-TIGSG6BX.js.map +1 -0
  96. package/dist/chunk-UDHHKZWO.js +41 -0
  97. package/dist/chunk-UDHHKZWO.js.map +1 -0
  98. package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
  99. package/dist/chunk-UKKSMZYT.js.map +1 -0
  100. package/dist/chunk-V6GQKOX2.js +39 -0
  101. package/dist/chunk-V6GQKOX2.js.map +1 -0
  102. package/dist/chunk-V6HCTCVD.js +46 -0
  103. package/dist/chunk-V6HCTCVD.js.map +1 -0
  104. package/dist/chunk-VDUJZF5G.js +515 -0
  105. package/dist/chunk-VDUJZF5G.js.map +1 -0
  106. package/dist/chunk-VXFGMDA7.js +31 -0
  107. package/dist/chunk-VXFGMDA7.js.map +1 -0
  108. package/dist/chunk-WN52USHV.js +115 -0
  109. package/dist/chunk-WN52USHV.js.map +1 -0
  110. package/dist/chunk-WSXUPSID.js +43 -0
  111. package/dist/chunk-WSXUPSID.js.map +1 -0
  112. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  113. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  114. package/dist/chunk-ZPWZIVI6.js +47 -0
  115. package/dist/chunk-ZPWZIVI6.js.map +1 -0
  116. package/dist/collapsible-panel.d.ts +77 -0
  117. package/dist/collapsible-panel.js +84 -0
  118. package/dist/collapsible-panel.js.map +1 -0
  119. package/dist/css-values.d.ts +75 -0
  120. package/dist/css-values.js +3 -0
  121. package/dist/css-values.js.map +1 -0
  122. package/dist/device-class.d.ts +62 -0
  123. package/dist/device-class.js +103 -0
  124. package/dist/device-class.js.map +1 -0
  125. package/dist/disclosure-arrow.js +1 -1
  126. package/dist/divider-sides-267FA7sY.d.ts +4 -0
  127. package/dist/empty-state.d.ts +3 -2
  128. package/dist/empty-state.js +1 -1
  129. package/dist/floating-toolbar.d.ts +29 -0
  130. package/dist/floating-toolbar.js +3 -0
  131. package/dist/floating-toolbar.js.map +1 -0
  132. package/dist/index.d.ts +23 -14
  133. package/dist/index.js +30 -23
  134. package/dist/{menu-action-row.d.ts → list-action-row.d.ts} +19 -8
  135. package/dist/list-action-row.js +7 -0
  136. package/dist/list-action-row.js.map +1 -0
  137. package/dist/list-header.d.ts +77 -0
  138. package/dist/list-header.js +8 -0
  139. package/dist/list-header.js.map +1 -0
  140. package/dist/list-inset-control.d.ts +17 -0
  141. package/dist/list-inset-control.js +3 -0
  142. package/dist/list-inset-control.js.map +1 -0
  143. package/dist/list-inset-text.d.ts +26 -0
  144. package/dist/list-inset-text.js +3 -0
  145. package/dist/list-inset-text.js.map +1 -0
  146. package/dist/list-item.d.ts +45 -0
  147. package/dist/list-item.js +7 -0
  148. package/dist/list-item.js.map +1 -0
  149. package/dist/list.d.ts +21 -0
  150. package/dist/list.js +4 -0
  151. package/dist/list.js.map +1 -0
  152. package/dist/loading-spinner.js +1 -1
  153. package/dist/nav-stack.d.ts +41 -0
  154. package/dist/nav-stack.js +6 -0
  155. package/dist/nav-stack.js.map +1 -0
  156. package/dist/pane.d.ts +50 -0
  157. package/dist/pane.js +4 -0
  158. package/dist/pane.js.map +1 -0
  159. package/dist/resizable-region.d.ts +21 -3
  160. package/dist/resizable-region.js +1 -1
  161. package/dist/segmented-control.d.ts +11 -8
  162. package/dist/segmented-control.js +3 -1
  163. package/dist/select-register.d.ts +5 -1
  164. package/dist/select-register.js +3 -0
  165. package/dist/select-register.js.map +1 -1
  166. package/dist/select.d.ts +34 -8
  167. package/dist/select.js +3 -1
  168. package/dist/semantic-content-BbzjvSu9.d.ts +14 -0
  169. package/dist/skeleton.d.ts +25 -0
  170. package/dist/skeleton.js +4 -0
  171. package/dist/skeleton.js.map +1 -0
  172. package/dist/split-view.d.ts +43 -0
  173. package/dist/split-view.js +82 -0
  174. package/dist/split-view.js.map +1 -0
  175. package/dist/state-banner.d.ts +11 -5
  176. package/dist/state-banner.js +3 -1
  177. package/dist/styles/app-tab.css +26 -0
  178. package/dist/styles/catalog.css +582 -0
  179. package/dist/styles/collapsible-panel.css +212 -0
  180. package/dist/styles/floating-toolbar.css +38 -0
  181. package/dist/styles/foundation.css +80 -0
  182. package/dist/styles/layout.css +2 -20
  183. package/dist/styles/list-action-row.css +224 -0
  184. package/dist/styles/list-header.css +198 -0
  185. package/dist/styles/list-inset-control.css +7 -0
  186. package/dist/styles/list-inset-text.css +14 -0
  187. package/dist/styles/list-item.css +171 -0
  188. package/dist/styles/list.css +43 -0
  189. package/dist/styles/nav-stack.css +141 -0
  190. package/dist/styles/pane.css +62 -0
  191. package/dist/styles/resizable-region.css +158 -0
  192. package/dist/styles/segmented-control.css +10 -7
  193. package/dist/styles/select.css +115 -2
  194. package/dist/styles/skeleton.css +31 -0
  195. package/dist/styles/split-view.css +35 -0
  196. package/dist/styles/state-banner.css +49 -10
  197. package/dist/styles/styles.css +7 -5
  198. package/dist/styles/sunken-panel.css +22 -0
  199. package/dist/styles/surface-scaffold.css +51 -0
  200. package/dist/styles/tab-bar.css +24 -0
  201. package/dist/styles/tab-scaffold.css +85 -0
  202. package/dist/styles/token-search-field.css +14 -2
  203. package/dist/styles/toolbar-control-group.css +224 -6
  204. package/dist/styles/toolbar-text.css +65 -2
  205. package/dist/styles/toolbar.css +47 -2
  206. package/dist/styles/webawesome.css +165 -0
  207. package/dist/styles/workbench.css +187 -0
  208. package/dist/sunken-panel.d.ts +20 -0
  209. package/dist/sunken-panel.js +3 -0
  210. package/dist/sunken-panel.js.map +1 -0
  211. package/dist/surface-scaffold.d.ts +26 -0
  212. package/dist/surface-scaffold.js +3 -0
  213. package/dist/surface-scaffold.js.map +1 -0
  214. package/dist/tab-bar.d.ts +24 -6
  215. package/dist/tab-bar.js +1 -1
  216. package/dist/tab-scaffold.d.ts +31 -0
  217. package/dist/tab-scaffold.js +51 -0
  218. package/dist/tab-scaffold.js.map +1 -0
  219. package/dist/token-search-field.d.ts +30 -13
  220. package/dist/token-search-field.js +1 -1
  221. package/dist/toolbar-control-group.d.ts +33 -7
  222. package/dist/toolbar-control-group.js +1 -1
  223. package/dist/toolbar-text.d.ts +31 -4
  224. package/dist/toolbar-text.js +3 -1
  225. package/dist/toolbar.d.ts +10 -7
  226. package/dist/toolbar.js +1 -1
  227. package/dist/value-table.d.ts +7 -3
  228. package/dist/value-table.js +3 -1
  229. package/dist/webawesome.d.ts +1 -0
  230. package/dist/wire-catalog.d.ts +57 -0
  231. package/dist/wire-catalog.js +302 -0
  232. package/dist/wire-catalog.js.map +1 -0
  233. package/dist/wire-nav-stack.d.ts +18 -0
  234. package/dist/wire-nav-stack.js +228 -0
  235. package/dist/wire-nav-stack.js.map +1 -0
  236. package/dist/wire-resizable-regions.d.ts +1 -1
  237. package/dist/wire-resizable-regions.js +1 -1
  238. package/dist/wire-sidebar.d.ts +48 -0
  239. package/dist/wire-sidebar.js +147 -0
  240. package/dist/wire-sidebar.js.map +1 -0
  241. package/dist/wire-tab-bars.d.ts +19 -2
  242. package/dist/wire-tab-bars.js +1 -1
  243. package/dist/wire-tab-scaffold.d.ts +11 -0
  244. package/dist/wire-tab-scaffold.js +21 -0
  245. package/dist/wire-tab-scaffold.js.map +1 -0
  246. package/dist/wire-token-search-fields.d.ts +104 -4
  247. package/dist/wire-token-search-fields.js +1 -1
  248. package/dist/workbench.d.ts +44 -0
  249. package/dist/workbench.js +106 -0
  250. package/dist/workbench.js.map +1 -0
  251. package/docs/accessibility.md +137 -20
  252. package/docs/app-layouts.md +61 -0
  253. package/docs/catalog.md +364 -0
  254. package/docs/collapsible-panel.md +142 -0
  255. package/docs/component-contract.md +253 -26
  256. package/docs/component-selection.md +213 -57
  257. package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
  258. package/docs/design/templates/empty-state/filtered.svg +1 -0
  259. package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
  260. package/docs/design/templates/empty-state/with-action.svg +1 -0
  261. package/docs/design/templates/empty-state-dark.svg +1 -0
  262. package/docs/design/templates/empty-state.svg +1 -0
  263. package/docs/design/templates/list/compact-dark.svg +1 -0
  264. package/docs/design/templates/list/compact.svg +1 -0
  265. package/docs/design/templates/list/stack-dark.svg +1 -0
  266. package/docs/design/templates/list/stack.svg +1 -0
  267. package/docs/design/templates/list-action-row/default-dark.svg +1 -0
  268. package/docs/design/templates/list-action-row/default.svg +1 -0
  269. package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
  270. package/docs/design/templates/list-action-row/selected.svg +1 -0
  271. package/docs/design/templates/list-action-row-dark.svg +1 -0
  272. package/docs/design/templates/list-action-row.svg +1 -0
  273. package/docs/design/templates/list-dark.svg +1 -0
  274. package/docs/design/templates/list-header/action-dark.svg +1 -0
  275. package/docs/design/templates/list-header/action.svg +1 -0
  276. package/docs/design/templates/list-header/count-dark.svg +1 -0
  277. package/docs/design/templates/list-header/count.svg +1 -0
  278. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
  279. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
  280. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
  281. package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
  282. package/docs/design/templates/list-header-dark.svg +1 -0
  283. package/docs/design/templates/list-header.svg +1 -0
  284. package/docs/design/templates/list-item/default-dark.svg +1 -0
  285. package/docs/design/templates/list-item/default.svg +1 -0
  286. package/docs/design/templates/list-item/multiline-dark.svg +1 -0
  287. package/docs/design/templates/list-item/multiline.svg +1 -0
  288. package/docs/design/templates/list-item/selected-dark.svg +1 -0
  289. package/docs/design/templates/list-item/selected.svg +1 -0
  290. package/docs/design/templates/list-item/trailing-dark.svg +1 -0
  291. package/docs/design/templates/list-item/trailing.svg +1 -0
  292. package/docs/design/templates/list-item-dark.svg +1 -0
  293. package/docs/design/templates/list-item.svg +1 -0
  294. package/docs/design/templates/list.svg +1 -0
  295. package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
  296. package/docs/design/templates/segmented-control/equal.svg +1 -0
  297. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
  298. package/docs/design/templates/segmented-control/pill-small.svg +1 -0
  299. package/docs/design/templates/segmented-control-dark.svg +1 -0
  300. package/docs/design/templates/segmented-control.svg +1 -0
  301. package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
  302. package/docs/design/templates/skeleton/avatar.svg +1 -0
  303. package/docs/design/templates/skeleton/block-dark.svg +1 -0
  304. package/docs/design/templates/skeleton/block.svg +1 -0
  305. package/docs/design/templates/skeleton/lines-dark.svg +1 -0
  306. package/docs/design/templates/skeleton/lines.svg +1 -0
  307. package/docs/design/templates/skeleton-dark.svg +1 -0
  308. package/docs/design/templates/skeleton.svg +1 -0
  309. package/docs/design/templates/state-banner/danger-dark.svg +1 -0
  310. package/docs/design/templates/state-banner/danger.svg +1 -0
  311. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
  312. package/docs/design/templates/state-banner/info-badge.svg +1 -0
  313. package/docs/design/templates/state-banner/info-dark.svg +1 -0
  314. package/docs/design/templates/state-banner/info.svg +1 -0
  315. package/docs/design/templates/state-banner/success-dark.svg +1 -0
  316. package/docs/design/templates/state-banner/success.svg +1 -0
  317. package/docs/design/templates/state-banner/warning-dark.svg +1 -0
  318. package/docs/design/templates/state-banner/warning.svg +1 -0
  319. package/docs/design/templates/state-banner-dark.svg +1 -0
  320. package/docs/design/templates/state-banner.svg +1 -0
  321. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
  322. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
  323. package/docs/design/templates/tab-bar-dark.svg +1 -0
  324. package/docs/design/templates/tab-bar.svg +1 -0
  325. package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
  326. package/docs/design/templates/token-search-field/empty.svg +1 -0
  327. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
  328. package/docs/design/templates/token-search-field/tokens.svg +1 -0
  329. package/docs/design/templates/token-search-field-dark.svg +1 -0
  330. package/docs/design/templates/token-search-field.svg +1 -0
  331. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
  332. package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
  333. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  334. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  335. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  336. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  337. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  338. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  339. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  340. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  341. package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
  342. package/docs/design/templates/toolbar-control-group.svg +1 -0
  343. package/docs/design/templates/toolbar-dark.svg +1 -0
  344. package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
  345. package/docs/design/templates/toolbar-text/default.svg +1 -0
  346. package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
  347. package/docs/design/templates/toolbar-text/large.svg +1 -0
  348. package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
  349. package/docs/design/templates/toolbar-text/small.svg +1 -0
  350. package/docs/design/templates/toolbar-text-dark.svg +1 -0
  351. package/docs/design/templates/toolbar-text.svg +1 -0
  352. package/docs/design/templates/toolbar.svg +1 -0
  353. package/docs/design/templates/value-table/details-dark.svg +1 -0
  354. package/docs/design/templates/value-table/details.svg +1 -0
  355. package/docs/design/templates/value-table-dark.svg +1 -0
  356. package/docs/design/templates/value-table.svg +1 -0
  357. package/docs/design/templates.md +128 -0
  358. package/docs/design-philosophy.md +33 -11
  359. package/docs/device-class.md +54 -0
  360. package/docs/examples/application-ui-profile.json +60 -0
  361. package/docs/examples/command-palette-adapter.tsx +67 -17
  362. package/docs/examples/component-catalog-extension-v2.json +80 -0
  363. package/docs/examples/component-catalog-extension.json +45 -0
  364. package/docs/layout.md +98 -37
  365. package/docs/nav-stack.md +77 -0
  366. package/docs/recipes.md +75 -19
  367. package/docs/split-view.md +55 -0
  368. package/docs/sunken-panel.md +53 -0
  369. package/docs/surface-scaffold.md +25 -0
  370. package/docs/tab-scaffold.md +52 -0
  371. package/docs/type-contracts.md +127 -0
  372. package/docs/ui-analyzer.md +118 -0
  373. package/docs/ui-doctor.md +91 -0
  374. package/docs/ui-evaluator.md +122 -0
  375. package/docs/ux-demo.md +24 -13
  376. package/docs/webawesome-theme.md +82 -8
  377. package/docs/workbench.md +123 -0
  378. package/doctor/cli.mjs +115 -0
  379. package/doctor/config.schema.json +68 -0
  380. package/doctor/eslint-diagnostics.d.mts +24 -0
  381. package/doctor/eslint-diagnostics.mjs +24 -0
  382. package/doctor/index.d.mts +116 -0
  383. package/doctor/index.mjs +1272 -0
  384. package/doctor/report.schema.json +118 -0
  385. package/evaluator/cli.mjs +89 -0
  386. package/evaluator/index.d.mts +90 -0
  387. package/evaluator/index.mjs +1138 -0
  388. package/evaluator/report.schema.json +152 -0
  389. package/llms.txt +253 -43
  390. package/package.json +180 -37
  391. package/traversal-exclusions.mjs +27 -0
  392. package/ux-demo/recipes/app-shell.tsx +222 -13
  393. package/ux-demo/recipes/collapsible-sidebar.tsx +229 -0
  394. package/ux-demo/recipes/compact-toolbar.tsx +115 -12
  395. package/ux-demo/recipes/composer-form.tsx +119 -18
  396. package/ux-demo/recipes/list-detail-dialog.tsx +197 -0
  397. package/ux-demo/recipes/list-workspace-states.tsx +175 -9
  398. package/ux-demo/recipes/loaders.ts +13 -4
  399. package/ux-demo/recipes/loading-inspector.tsx +186 -0
  400. package/ux-demo/recipes/mount-recipe.ts +31 -5
  401. package/ux-demo/recipes/navigation-sidebar.tsx +122 -11
  402. package/ux-demo/recipes/navigation-stack.tsx +118 -0
  403. package/ux-demo/recipes/recipes.css +143 -18
  404. package/ux-demo/recipes/types.ts +9 -1
  405. package/ux-demo/recipes/workspace-header.tsx +68 -3
  406. package/dist/browser/dialog-header.js +0 -5
  407. package/dist/browser/menu-action-row.js +0 -3
  408. package/dist/browser/menu-item.js +0 -3
  409. package/dist/browser/page-header.js +0 -3
  410. package/dist/chunk-2PES33HS.js +0 -13
  411. package/dist/chunk-2PES33HS.js.map +0 -1
  412. package/dist/chunk-55DATV3A.js +0 -10
  413. package/dist/chunk-55DATV3A.js.map +0 -1
  414. package/dist/chunk-7BR3DO2A.js.map +0 -1
  415. package/dist/chunk-7LDCTU7D.js +0 -134
  416. package/dist/chunk-7LDCTU7D.js.map +0 -1
  417. package/dist/chunk-AQNKJRFH.js +0 -79
  418. package/dist/chunk-AQNKJRFH.js.map +0 -1
  419. package/dist/chunk-CTQKI2XL.js +0 -28
  420. package/dist/chunk-CTQKI2XL.js.map +0 -1
  421. package/dist/chunk-DAFFDO33.js +0 -14
  422. package/dist/chunk-DAFFDO33.js.map +0 -1
  423. package/dist/chunk-EGHL5S4P.js +0 -33
  424. package/dist/chunk-EGHL5S4P.js.map +0 -1
  425. package/dist/chunk-GY5WH7TO.js +0 -10
  426. package/dist/chunk-GY5WH7TO.js.map +0 -1
  427. package/dist/chunk-H5AGGVU5.js +0 -75
  428. package/dist/chunk-H5AGGVU5.js.map +0 -1
  429. package/dist/chunk-K3G72I6D.js +0 -24
  430. package/dist/chunk-K3G72I6D.js.map +0 -1
  431. package/dist/chunk-M5JRI6MN.js +0 -27
  432. package/dist/chunk-M5JRI6MN.js.map +0 -1
  433. package/dist/chunk-MBR3TYNL.js +0 -14
  434. package/dist/chunk-MBR3TYNL.js.map +0 -1
  435. package/dist/chunk-MFKMHK5W.js +0 -10
  436. package/dist/chunk-MFKMHK5W.js.map +0 -1
  437. package/dist/chunk-MQIZAD4A.js.map +0 -1
  438. package/dist/chunk-NPPD2A6O.js +0 -40
  439. package/dist/chunk-NPPD2A6O.js.map +0 -1
  440. package/dist/chunk-R6JHHBKQ.js +0 -19
  441. package/dist/chunk-R6JHHBKQ.js.map +0 -1
  442. package/dist/chunk-S3TPM2LP.js +0 -23
  443. package/dist/chunk-S3TPM2LP.js.map +0 -1
  444. package/dist/chunk-TRNBHPNW.js.map +0 -1
  445. package/dist/chunk-VHW5YNIB.js +0 -17
  446. package/dist/chunk-VHW5YNIB.js.map +0 -1
  447. package/dist/chunk-WVPYVDXY.js +0 -15
  448. package/dist/chunk-WVPYVDXY.js.map +0 -1
  449. package/dist/chunk-XZXRLY5Q.js +0 -33
  450. package/dist/chunk-XZXRLY5Q.js.map +0 -1
  451. package/dist/dialog-header.d.ts +0 -15
  452. package/dist/dialog-header.js +0 -5
  453. package/dist/dialog-header.js.map +0 -1
  454. package/dist/menu-action-row.js +0 -4
  455. package/dist/menu-action-row.js.map +0 -1
  456. package/dist/menu-header.d.ts +0 -41
  457. package/dist/menu-header.js +0 -6
  458. package/dist/menu-header.js.map +0 -1
  459. package/dist/menu-item.d.ts +0 -31
  460. package/dist/menu-item.js +0 -4
  461. package/dist/menu-item.js.map +0 -1
  462. package/dist/page-header.d.ts +0 -9
  463. package/dist/page-header.js +0 -3
  464. package/dist/page-header.js.map +0 -1
  465. package/dist/styles/dialog-header.css +0 -87
  466. package/dist/styles/menu-action-row.css +0 -154
  467. package/dist/styles/menu-header.css +0 -143
  468. package/dist/styles/menu-item.css +0 -118
  469. package/dist/styles/page-header.css +0 -26
  470. package/ux-demo/recipes/master-detail-dialog.tsx +0 -45
@@ -1,6 +1,86 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.3` and `kerfjs@5.0.0-beta.3`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.31` and `kerfjs@5.0.0-beta.31`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
+
5
+ ## `@kerfjs/ui/css-values`
6
+
7
+ ```ts
8
+ declare const cssValueBrand: unique symbol;
9
+ declare const cssLengthBrand: unique symbol;
10
+ declare const cssLengthExpressionBrand: unique symbol;
11
+ declare const cssFlexBrand: unique symbol;
12
+ declare const cssColorBrand: unique symbol;
13
+ /**
14
+ * A complete typed CSS value minted by a property-specific Kerf UI builder.
15
+ *
16
+ * This brand is an authoring correctness aid, not a sanitizer or security
17
+ * boundary. Prefer the narrower property grammar, such as {@link CssLength},
18
+ * whenever one is available.
19
+ */
20
+ type CssValue = string & {
21
+ readonly [cssValueBrand]: 'CssValue';
22
+ };
23
+ /**
24
+ * A complete CSS length-percentage value suitable for dimension-valued UI
25
+ * props. Despite the concise name, percentages are intentionally included.
26
+ */
27
+ type CssLength = CssValue & {
28
+ readonly [cssLengthBrand]: 'CssLength';
29
+ };
30
+ /**
31
+ * An incomplete arithmetic expression. Wrap it with {@link calc} before
32
+ * passing it to a prop that accepts {@link CssLength}.
33
+ */
34
+ type CssLengthExpression = string & {
35
+ readonly [cssLengthExpressionBrand]: 'CssLengthExpression';
36
+ };
37
+ /** A complete CSS `flex` shorthand. It is not interchangeable with a length. */
38
+ type CssFlex = CssValue & {
39
+ readonly [cssFlexBrand]: 'CssFlex';
40
+ };
41
+ /** A complete CSS color value. It is not interchangeable with a length. */
42
+ type CssColor = CssValue & {
43
+ readonly [cssColorBrand]: 'CssColor';
44
+ };
45
+ type CssFlexKeyword = 'none' | 'auto' | 'initial';
46
+ type CssFlexBasis = CssLength | 'auto' | 'content' | 'min-content' | 'max-content' | 'fit-content';
47
+ type CssSizeKeyword = 'auto' | 'min-content' | 'max-content' | 'fit-content';
48
+ /** A complete width/height value accepted by dimension-valued UI props. */
49
+ type CssSize = CssLength | CssSizeKeyword;
50
+ declare const uiColorNames: readonly ["accent", "accent-text", "border", "border-quiet", "brand-border-loud", "brand-border-normal", "brand-border-quiet", "brand-fill-loud", "brand-fill-normal", "brand-fill-quiet", "brand-on-fill", "brand-on-loud", "brand-on-normal", "brand-on-quiet", "danger", "danger-border-loud", "danger-border-normal", "danger-border-quiet", "danger-fill-loud", "danger-fill-normal", "danger-fill-quiet", "danger-on-loud", "danger-on-normal", "danger-on-quiet", "danger-text", "neutral-border-loud", "neutral-border-normal", "neutral-border-quiet", "neutral-fill-loud", "neutral-fill-normal", "neutral-fill-quiet", "neutral-on-loud", "neutral-on-normal", "neutral-on-quiet", "pop", "pop-border-loud", "pop-border-normal", "pop-border-quiet", "pop-fill-loud", "pop-fill-normal", "pop-fill-quiet", "pop-on-fill", "pop-on-loud", "pop-on-normal", "pop-on-quiet", "pop-text", "success", "success-border-loud", "success-border-normal", "success-border-quiet", "success-fill-loud", "success-fill-normal", "success-fill-quiet", "success-on-fill", "success-on-loud", "success-on-normal", "success-on-quiet", "success-text", "surface", "surface-lowered", "surface-raised", "text", "text-link", "text-quiet", "warning", "warning-border-loud", "warning-border-normal", "warning-border-quiet", "warning-fill-loud", "warning-fill-normal", "warning-fill-quiet", "warning-on-fill", "warning-on-loud", "warning-on-normal", "warning-on-quiet", "warning-text"];
51
+ /** Names of the public `--kui-color-*` semantic tokens. */
52
+ type UiColorName = (typeof uiColorNames)[number];
53
+ /** Kerf UI's complete spacing-token vocabulary. `s` and `xl` are exceptions. */
54
+ type UiSpaceName = 'none' | '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl';
55
+ /** Create a complete pixel length. */
56
+ declare function px(value: number): CssLength;
57
+ /** Create a complete root-font-relative length. */
58
+ declare function rem(value: number): CssLength;
59
+ /** Create a complete current-font-relative length. */
60
+ declare function em(value: number): CssLength;
61
+ /** Create a complete percentage length. */
62
+ declare function pct(value: number): CssLength;
63
+ /** Resolve a Kerf UI spacing step to its public custom property. */
64
+ declare function space(name: UiSpaceName): CssLength;
65
+ /**
66
+ * Reference an application-owned custom property whose contract is a CSS
67
+ * length-percentage. The deliberately narrow name grammar keeps this helper
68
+ * from becoming an arbitrary CSS-string constructor.
69
+ */
70
+ declare function lengthVar(name: `--${string}`, fallback?: CssLength): CssLength;
71
+ /** Combine two or more complete lengths into a non-standalone sum. */
72
+ declare function plus(first: CssLength, second: CssLength, ...rest: readonly CssLength[]): CssLengthExpression;
73
+ /** Turn a typed length expression into a complete CSS `calc()` value. */
74
+ declare function calc(expression: CssLengthExpression): CssLength;
75
+ /** Build a complete, structured CSS flex shorthand. */
76
+ declare function flex(grow: number, shrink?: number, basis?: CssFlexBasis): CssFlex;
77
+ /** Resolve a public Kerf UI semantic color token. */
78
+ declare function uiColor(name: UiColorName): CssColor;
79
+ /** Reference an application-owned custom property whose contract is a color. */
80
+ declare function colorVar(name: `--${string}`, fallback?: CssColor): CssColor;
81
+
82
+ export { type CssColor, type CssFlex, type CssFlexBasis, type CssFlexKeyword, type CssLength, type CssLengthExpression, type CssSize, type CssSizeKeyword, type CssValue, type UiColorName, type UiSpaceName, calc, colorVar, em, flex, lengthVar, pct, plus, px, rem, space, uiColor };
83
+ ```
4
84
 
5
85
  ## `@kerfjs/ui/disclosure-arrow`
6
86
 
@@ -24,19 +104,22 @@ export { DisclosureArrow, type DisclosureArrowProps, type DisclosureDirection };
24
104
  ## `@kerfjs/ui/toolbar`
25
105
 
26
106
  ```ts
27
- import { SafeHtml } from 'kerfjs';
107
+ import * as kerfjs from 'kerfjs';
108
+ import { D as DividerSides } from './divider-sides-267FA7sY.js';
109
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
28
110
 
29
111
  interface ToolbarProps {
30
- leading?: SafeHtml;
31
- center?: SafeHtml;
32
- trailing?: SafeHtml;
112
+ leading?: KerfUiContent;
113
+ center?: KerfUiContent;
114
+ trailing?: KerfUiContent;
33
115
  label?: string;
34
- divider?: boolean;
116
+ /** Physical divider edges in canonical top/right/bottom/left order. Defaults to bottom. */
117
+ dividerSides?: DividerSides;
35
118
  className?: string;
36
119
  }
37
- declare function Toolbar({ leading, center, trailing, label, divider, className }: ToolbarProps): SafeHtml;
120
+ declare function Toolbar({ leading, center, trailing, label, dividerSides, className, }: ToolbarProps): kerfjs.SafeHtml;
38
121
 
39
- export { Toolbar, type ToolbarProps };
122
+ export { DividerSides, Toolbar, type ToolbarProps };
40
123
  ```
41
124
 
42
125
  ## `@kerfjs/ui/toolbar-text`
@@ -44,99 +127,252 @@ export { Toolbar, type ToolbarProps };
44
127
  ```ts
45
128
  import * as kerfjs from 'kerfjs';
46
129
 
47
- type ToolbarTextSize = 'large' | 'default' | 'small';
48
- interface ToolbarTextProps {
130
+ type ToolbarTextSize = 'xlarge' | 'large' | 'default' | 'small';
131
+ /** ARIA heading level for a title exposed as a heading landmark. */
132
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
133
+ interface ToolbarTextBaseProps {
49
134
  text: string;
50
135
  size?: ToolbarTextSize;
51
136
  className?: string;
137
+ /** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */
138
+ id?: string;
139
+ /**
140
+ * Expose heading semantics (`role="heading"` + `aria-level`) so the text acts as
141
+ * a heading landmark — e.g. a page's primary title. Omit to keep the plain span
142
+ * (the default), which suits a dialog title referenced via `aria-labelledby`.
143
+ */
144
+ headingLevel?: HeadingLevel;
145
+ /** Render the text as an unanimated loading skeleton instead of its value. */
146
+ placeholder?: boolean;
147
+ /**
148
+ * Show a trailing ellipsis (…) where the text is truncated — on the single line
149
+ * (default), or at the `maxLines` boundary when wrapping. Set false to hard-clip
150
+ * instead. Default true.
151
+ */
152
+ ellipsis?: boolean;
52
153
  }
53
- declare function ToolbarText({ text, size, className }: ToolbarTextProps): kerfjs.SafeHtml;
154
+ type ToolbarTextWrappingProps = {
155
+ /** Wrap onto multiple lines; combine with `maxLines` to cap them. */
156
+ wrap: true;
157
+ maxLines?: number | null;
158
+ } | {
159
+ wrap?: false;
160
+ maxLines?: never;
161
+ };
162
+ type ToolbarTextProps = ToolbarTextBaseProps & ToolbarTextWrappingProps;
163
+ declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, }: ToolbarTextProps): kerfjs.SafeHtml;
54
164
 
55
- export { ToolbarText, type ToolbarTextProps, type ToolbarTextSize };
165
+ export { type HeadingLevel, ToolbarText, type ToolbarTextProps, type ToolbarTextSize };
56
166
  ```
57
167
 
58
168
  ## `@kerfjs/ui/toolbar-control-group`
59
169
 
60
170
  ```ts
61
- import { SafeHtml } from 'kerfjs';
62
-
171
+ import * as kerfjs from 'kerfjs';
172
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
173
+
174
+ type ToolbarControlGroupAppearance = 'contained' | 'borderless';
175
+ type ToolbarControlGroupTone = 'default' | 'dark';
176
+ type ToolbarControlGroupButtonAppearance = 'plain' | 'push';
177
+ type ToolbarControlGroupShape = 'pill' | 'rounded';
178
+ type ToolbarControlGroupSize = 'default' | 'compact';
179
+ type ToolbarControlGroupDensity = 'comfortable' | 'tight';
180
+ type ToolbarControlGroupContent = 'icon' | 'text' | 'mixed' | 'avatar';
181
+ type ToolbarControlGroupSelectedChrome = 'raised' | 'filled' | 'outline';
182
+ type ToolbarControlGroupSelectedTone = 'brand' | 'neutral' | 'pop';
63
183
  interface ToolbarControlGroupProps {
64
- children: SafeHtml | SafeHtml[];
184
+ children: KerfUiContent;
65
185
  label?: string;
66
186
  className?: string;
67
187
  expanded?: boolean;
68
188
  single?: boolean;
69
- appearance?: 'contained' | 'borderless';
70
- tone?: 'default' | 'dark';
71
- buttonAppearance?: 'plain' | 'push';
189
+ appearance?: ToolbarControlGroupAppearance;
190
+ tone?: ToolbarControlGroupTone;
191
+ buttonAppearance?: ToolbarControlGroupButtonAppearance;
192
+ /** Corner shape: fully round `pill` (default) or a softer `rounded` rectangle. */
193
+ shape?: ToolbarControlGroupShape;
194
+ size?: ToolbarControlGroupSize;
195
+ density?: ToolbarControlGroupDensity;
196
+ content?: ToolbarControlGroupContent;
197
+ selectedChrome?: ToolbarControlGroupSelectedChrome;
198
+ selectedTone?: ToolbarControlGroupSelectedTone;
199
+ /** Size a nested Web Awesome dropdown trigger as part of this group. */
200
+ nestedDropdown?: boolean;
201
+ /** Add contrast behind photo-backed avatar content. */
202
+ scrim?: boolean;
203
+ /**
204
+ * Avatar image URL. A single-control group paints it on the group; a
205
+ * multi-control group paints it only on the pressed selection highlight.
206
+ */
207
+ avatarImage?: string;
72
208
  }
73
- declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance }: ToolbarControlGroupProps): SafeHtml;
209
+ declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, selectedChrome, selectedTone, nestedDropdown, scrim, avatarImage, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
74
210
 
75
- export { ToolbarControlGroup, type ToolbarControlGroupProps };
211
+ export { ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone };
76
212
  ```
77
213
 
78
- ## `@kerfjs/ui/menu-header`
214
+ ## `@kerfjs/ui/floating-toolbar`
215
+
216
+ ```ts
217
+ import * as kerfjs from 'kerfjs';
218
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
219
+
220
+ /** Where a {@link FloatingToolbar} floats within its positioned container. */
221
+ type FloatingToolbarPosition = 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';
222
+ interface FloatingToolbarProps {
223
+ /** Toolbar contents — normally one or more `ToolbarControlGroup`s. */
224
+ children: KerfUiContent;
225
+ /** Accessible name for the toolbar (required — it exposes `role="toolbar"`). */
226
+ label: string;
227
+ /**
228
+ * Corner or edge it floats to inside its nearest positioned ancestor.
229
+ * Default: `'bottom-end'`.
230
+ */
231
+ position?: FloatingToolbarPosition;
232
+ className?: string;
233
+ }
234
+ /**
235
+ * A toolbar that floats above the main content of its nearest positioned
236
+ * ancestor — a transparent, forced-dark cluster of controls (e.g. a drawer
237
+ * restore button) that sits over the content but NOT over dialogs or overlays
238
+ * (it is not in the top layer). It is inset from the container edges by
239
+ * `--kui-floating-toolbar-inset` (default `--kui-space-m`, i.e. 8px more than a
240
+ * top toolbar's own inset); override that token to move it. The app owns the
241
+ * controls and their behavior — wire them with `delegate()` as usual.
242
+ */
243
+ declare function FloatingToolbar({ children, label, position, className, }: FloatingToolbarProps): kerfjs.SafeHtml;
244
+
245
+ export { FloatingToolbar, type FloatingToolbarPosition, type FloatingToolbarProps };
246
+ ```
247
+
248
+ ## `@kerfjs/ui/list-header`
79
249
 
80
250
  ```ts
81
251
  import { SafeHtml } from 'kerfjs';
82
252
 
83
- type MenuHeaderRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
253
+ type ListHeaderRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
84
254
  'data-component'?: never;
85
255
  'data-action'?: never;
86
256
  'data-has-badge'?: never;
87
257
  'data-has-count'?: never;
258
+ 'data-density'?: never;
259
+ 'data-divider'?: never;
260
+ 'data-indicator-tone'?: never;
88
261
  'data-toggle'?: never;
89
262
  }>;
90
- type MenuHeaderTriggerAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
263
+ type ListHeaderTriggerAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
91
264
  'data-action'?: never;
92
265
  popoverTarget?: string;
93
266
  popoverTargetAction?: 'toggle' | 'show' | 'hide';
94
267
  'aria-controls'?: string;
95
268
  'aria-haspopup'?: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | 'true';
96
269
  }>;
97
- interface MenuHeaderBaseProps {
270
+ interface ListHeaderBaseProps {
98
271
  label: string;
99
- action?: string;
100
- actionLabel?: string;
272
+ density?: 'standard' | 'compact';
273
+ divider?: 'none' | 'before' | 'after' | 'both';
274
+ indicatorTone?: 'neutral' | 'accent' | 'pop' | 'danger';
275
+ /** Render as an unanimated loading skeleton: keep the label and action affordance, disable interaction. */
276
+ placeholder?: boolean;
277
+ rootAttributes?: ListHeaderRootAttributes;
278
+ triggerAttributes?: ListHeaderTriggerAttributes;
279
+ }
280
+ type ListHeaderModeProps = {
281
+ /** Render the title as a controlled disclosure trigger. */
282
+ toggle: true;
283
+ action: string;
284
+ expanded: boolean;
101
285
  actionIcon?: SafeHtml;
286
+ actionLabel?: never;
102
287
  actionDisabled?: boolean;
103
288
  disabledReason?: string;
104
- expanded?: boolean;
105
- toggle?: boolean;
106
- rootAttributes?: MenuHeaderRootAttributes;
107
- triggerAttributes?: MenuHeaderTriggerAttributes;
108
- }
109
- type MenuHeaderIndicatorProps = {
289
+ } | {
290
+ /** Render a separately named trailing action. */
291
+ toggle?: false;
292
+ action: string;
293
+ actionLabel: string;
294
+ actionIcon: SafeHtml;
295
+ expanded?: never;
296
+ actionDisabled?: boolean;
297
+ disabledReason?: string;
298
+ } | {
299
+ /** Render a passive section heading. */
300
+ toggle?: false;
301
+ action?: never;
302
+ actionLabel?: never;
303
+ actionIcon?: never;
304
+ expanded?: never;
305
+ actionDisabled?: never;
306
+ disabledReason?: never;
307
+ };
308
+ type ListHeaderIndicatorProps = {
110
309
  count: number;
111
310
  countLabel: string;
112
311
  badge?: never;
312
+ status?: never;
313
+ } | {
314
+ count?: never;
315
+ countLabel?: never;
316
+ badge: SafeHtml;
317
+ status?: never;
113
318
  } | {
114
319
  count?: never;
115
320
  countLabel?: never;
116
- badge?: SafeHtml;
321
+ badge?: never;
322
+ status?: SafeHtml;
117
323
  };
118
- type MenuHeaderProps = MenuHeaderBaseProps & MenuHeaderIndicatorProps;
119
- declare function MenuHeader({ label, count, countLabel, badge, action, actionLabel, actionIcon, actionDisabled, disabledReason, expanded, toggle, rootAttributes, triggerAttributes }: MenuHeaderProps): SafeHtml;
324
+ type ListHeaderProps = ListHeaderBaseProps & ListHeaderIndicatorProps & ListHeaderModeProps;
325
+ declare function ListHeader({ label, count, countLabel, badge, status, density, divider, indicatorTone, action, actionLabel, actionIcon, actionDisabled, disabledReason, expanded, toggle, placeholder, rootAttributes, triggerAttributes, }: ListHeaderProps): SafeHtml;
326
+
327
+ export { ListHeader, type ListHeaderProps };
328
+ ```
329
+
330
+ ## `@kerfjs/ui/list`
331
+
332
+ ```ts
333
+ import * as kerfjs from 'kerfjs';
334
+ import { UiSpaceName, CssLength, CssFlexKeyword, CssFlex } from './css-values.js';
335
+ import { D as DividerSides } from './divider-sides-267FA7sY.js';
336
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
337
+
338
+ interface ListProps {
339
+ children?: KerfUiContent;
340
+ /** Use the standard item gap, a named UI spacing token, or a typed CSS length. Defaults to no gap. */
341
+ gap?: boolean | UiSpaceName | CssLength;
342
+ /** Allow this list to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */
343
+ flex?: boolean | CssFlexKeyword | CssFlex;
344
+ /** Own vertical scrolling and overscroll containment. */
345
+ scrollable?: boolean;
346
+ /** Physical divider edges in canonical top/right/bottom/left order. */
347
+ dividerSides?: DividerSides;
348
+ className?: string;
349
+ }
350
+ /** A stretch-aligned vertical stack with optional gap, flex, scroll, and dividers. */
351
+ declare function List({ children, gap, flex, scrollable, dividerSides, className, }: ListProps): kerfjs.SafeHtml;
120
352
 
121
- export { MenuHeader, type MenuHeaderProps };
353
+ export { CssFlex, CssFlexKeyword, CssLength, DividerSides, List, type ListProps, UiSpaceName };
122
354
  ```
123
355
 
124
- ## `@kerfjs/ui/menu-action-row`
356
+ ## `@kerfjs/ui/list-action-row`
125
357
 
126
358
  ```ts
127
359
  import { SafeHtml } from 'kerfjs';
128
360
 
129
- type MenuActionRowRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
361
+ type ListActionRowRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
130
362
  'data-component'?: never;
131
363
  'data-action'?: never;
132
364
  'data-item-id'?: never;
133
365
  'data-has-icon'?: never;
134
366
  'data-multiline'?: never;
367
+ 'data-density'?: never;
368
+ 'data-divider'?: never;
369
+ 'data-busy'?: never;
370
+ 'data-trailing-visibility'?: never;
135
371
  'data-state'?: never;
136
372
  'data-selected'?: never;
137
373
  'data-pressed'?: never;
138
374
  }>;
139
- type MenuActionRowTrailingAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
375
+ type ListActionRowTrailingAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
140
376
  'data-component'?: never;
141
377
  'data-action'?: never;
142
378
  'data-item-id'?: never;
@@ -145,9 +381,14 @@ type MenuActionRowTrailingAttributes = Readonly<Record<`data-${string}`, string
145
381
  'aria-controls'?: string;
146
382
  'aria-haspopup'?: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | 'true';
147
383
  }>;
148
- interface MenuActionRowProps {
384
+ interface ListActionRowProps {
149
385
  /** Visible dormant content for the primary button. Must not contain interactive descendants. */
150
386
  label: string | SafeHtml;
387
+ description?: string | SafeHtml;
388
+ status?: string | SafeHtml;
389
+ busy?: boolean;
390
+ density?: 'standard' | 'compact';
391
+ divider?: 'none' | 'before' | 'after' | 'both';
151
392
  /** Decorative dormant content for the primary button. Must not contain interactive descendants. */
152
393
  icon?: SafeHtml;
153
394
  action: string;
@@ -160,44 +401,58 @@ interface MenuActionRowProps {
160
401
  state?: string;
161
402
  disabled?: boolean;
162
403
  tabIndex?: number;
404
+ /** Render as an unanimated loading skeleton, disabling both actions. */
405
+ placeholder?: boolean;
163
406
  trailingAction: string;
164
407
  trailingActionLabel: string;
165
408
  /** Decorative dormant content for the trailing button. Must not contain interactive descendants. */
166
409
  trailingActionIcon: SafeHtml;
167
410
  trailingActionDisabled?: boolean;
168
411
  trailingActionTitle?: string;
412
+ trailingActionVisibility?: 'always' | 'interaction';
169
413
  className?: string;
170
- style?: string;
171
- rootAttributes?: MenuActionRowRootAttributes;
172
- trailingActionAttributes?: MenuActionRowTrailingAttributes;
414
+ rootAttributes?: ListActionRowRootAttributes;
415
+ trailingActionAttributes?: ListActionRowTrailingAttributes;
173
416
  }
174
- declare function MenuActionRow({ label, icon, action, itemId, selected, pressed, accessibleLabel, title, multiline, state, disabled, tabIndex, trailingAction, trailingActionLabel, trailingActionIcon, trailingActionDisabled, trailingActionTitle, className, style, rootAttributes, trailingActionAttributes }: MenuActionRowProps): SafeHtml;
417
+ declare function ListActionRow({ label, description, status, busy, density, divider, icon, action, itemId, selected, pressed, accessibleLabel, title, multiline, state, disabled, tabIndex, placeholder, trailingAction, trailingActionLabel, trailingActionIcon, trailingActionDisabled, trailingActionTitle, trailingActionVisibility, className, rootAttributes, trailingActionAttributes, }: ListActionRowProps): SafeHtml;
175
418
 
176
- export { MenuActionRow, type MenuActionRowProps };
419
+ export { ListActionRow, type ListActionRowProps };
177
420
  ```
178
421
 
179
- ## `@kerfjs/ui/menu-item`
422
+ ## `@kerfjs/ui/list-item`
180
423
 
181
424
  ```ts
182
425
  import { SafeHtml } from 'kerfjs';
426
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
183
427
 
184
- type MenuItemRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
428
+ type ListItemRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
185
429
  'data-component'?: never;
186
430
  'data-action'?: never;
187
431
  'data-item-id'?: never;
188
432
  'data-has-icon'?: never;
433
+ 'data-has-description'?: never;
189
434
  'data-multiline'?: never;
435
+ 'data-density'?: never;
436
+ 'data-divider'?: never;
437
+ 'data-busy'?: never;
190
438
  'data-state'?: never;
191
439
  }>;
192
- interface MenuItemProps {
440
+ interface ListItemProps {
193
441
  label: string | SafeHtml;
442
+ /** App-owned supporting text rendered in the component's stable label stack. */
443
+ description?: string | SafeHtml;
194
444
  icon?: SafeHtml;
195
- trailing?: SafeHtml;
445
+ trailing?: KerfUiContent;
446
+ /** Dormant status metadata rendered before trailing content. */
447
+ status?: string | SafeHtml;
448
+ /** Show a progress indicator and expose the row as busy without replacing its content. */
449
+ busy?: boolean;
450
+ density?: 'standard' | 'compact';
451
+ divider?: 'none' | 'before' | 'after' | 'both';
196
452
  selected?: boolean;
197
453
  action: string;
198
454
  itemId?: string;
199
455
  className?: string;
200
- style?: string;
201
456
  pressed?: boolean;
202
457
  accessibleLabel?: string;
203
458
  title?: string;
@@ -205,66 +460,91 @@ interface MenuItemProps {
205
460
  state?: string;
206
461
  disabled?: boolean;
207
462
  tabIndex?: number;
208
- rootAttributes?: MenuItemRootAttributes;
463
+ /** Render the row as an unanimated loading skeleton, disabling its action. */
464
+ placeholder?: boolean;
465
+ rootAttributes?: ListItemRootAttributes;
209
466
  }
210
- declare function MenuItem({ label, icon, trailing, selected, action, itemId, className, style, pressed, accessibleLabel, title, multiline, state, disabled, tabIndex, rootAttributes }: MenuItemProps): SafeHtml;
467
+ declare function ListItem({ label, description, icon, trailing, status, busy, density, divider, selected, action, itemId, className, pressed, accessibleLabel, title, multiline, state, disabled, tabIndex, placeholder, rootAttributes, }: ListItemProps): SafeHtml;
211
468
 
212
- export { MenuItem, type MenuItemProps };
469
+ export { ListItem, type ListItemProps };
213
470
  ```
214
471
 
215
- ## `@kerfjs/ui/page-header`
472
+ ## `@kerfjs/ui/list-inset-control`
216
473
 
217
474
  ```ts
218
- import { SafeHtml } from 'kerfjs';
475
+ import * as kerfjs from 'kerfjs';
476
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
219
477
 
220
- interface PageHeaderProps {
221
- title: string | SafeHtml;
222
- action?: SafeHtml;
478
+ interface ListInsetControlProps {
479
+ /** Control(s) that own their own border and padding (e.g. an input, a `wa-*`). */
480
+ children: KerfUiContent;
481
+ className?: string;
223
482
  }
224
- declare function PageHeader({ title, action }: PageHeaderProps): SafeHtml;
483
+ /**
484
+ * Insets a control into a pane/list content region: an 8px inline margin (so its
485
+ * edges line up with `.kui-content` items) and a stretch flex row with an 8px gap.
486
+ * Use it for controls that carry their own border and padding but no outer margin
487
+ * — the wrapper adds only the alignment margin and layout, not a second inset.
488
+ */
489
+ declare function ListInsetControl({ children, className, }: ListInsetControlProps): kerfjs.SafeHtml;
225
490
 
226
- export { PageHeader, type PageHeaderProps };
491
+ export { ListInsetControl, type ListInsetControlProps };
227
492
  ```
228
493
 
229
- ## `@kerfjs/ui/dialog-header`
494
+ ## `@kerfjs/ui/list-inset-text`
230
495
 
231
496
  ```ts
232
- import { SafeHtml } from 'kerfjs';
497
+ import * as kerfjs from 'kerfjs';
498
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
233
499
 
234
- interface DialogHeaderProps {
235
- title: string;
236
- titleId: string;
237
- summary?: string;
238
- summaryId?: string;
239
- icon?: SafeHtml;
240
- iconClassName?: string;
241
- actions?: SafeHtml;
242
- actionsLabel?: string;
500
+ interface ListInsetTextProps {
501
+ /** Text (or inline content) that carries no margin, border, or padding of its own. */
502
+ children: KerfUiContent | string;
503
+ /**
504
+ * Keep only the horizontal geometry (inline margin, left/right border, and
505
+ * left/right padding) and drop the vertical margin, border, and padding. Use it
506
+ * when the text edge must still align with bordered items but the line should not
507
+ * add its own vertical box space — tight text layout inside a content region.
508
+ */
509
+ horizontalOnly?: boolean;
510
+ className?: string;
243
511
  }
244
- declare function DialogHeader({ title, titleId, summary, summaryId, icon, iconClassName, actions, actionsLabel }: DialogHeaderProps): SafeHtml;
512
+ /**
513
+ * Gives bare text the content-item geometry — an 8px inline margin, a 1px
514
+ * transparent border, and 8px padding — so a plain string lines up with
515
+ * bordered `.kui-content` items (its text edge lands at the same 17px inset).
516
+ * Use it for text elements that have no margin, border, or padding of their own.
517
+ * Pass `horizontalOnly` to keep the horizontal inset but drop the vertical box
518
+ * space for tight text layout.
519
+ */
520
+ declare function ListInsetText({ children, horizontalOnly, className, }: ListInsetTextProps): kerfjs.SafeHtml;
245
521
 
246
- export { DialogHeader, type DialogHeaderProps };
522
+ export { ListInsetText, type ListInsetTextProps };
247
523
  ```
248
524
 
249
525
  ## `@kerfjs/ui/value-table`
250
526
 
251
527
  ```ts
528
+ import * as kerfjs from 'kerfjs';
252
529
  import { SafeHtml } from 'kerfjs';
530
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
253
531
 
254
532
  interface ValueTableRowProps {
255
533
  label: string | SafeHtml;
256
534
  value: string | SafeHtml;
257
535
  icon?: SafeHtml;
258
536
  className?: string;
537
+ /** Render the value as an unanimated loading skeleton, keeping the field label. */
538
+ placeholder?: boolean;
259
539
  }
260
- declare function ValueTableRow({ label, value, icon, className }: ValueTableRowProps): SafeHtml;
540
+ declare function ValueTableRow({ label, value, icon, className, placeholder, }: ValueTableRowProps): SafeHtml;
261
541
 
262
542
  interface ValueTableProps {
263
543
  label: string;
264
544
  className?: string;
265
- children: SafeHtml | readonly SafeHtml[];
545
+ children: KerfUiContent;
266
546
  }
267
- declare function ValueTable({ label, className, children }: ValueTableProps): SafeHtml;
547
+ declare function ValueTable({ label, className, children, }: ValueTableProps): kerfjs.SafeHtml;
268
548
 
269
549
  export { ValueTable, type ValueTableProps, ValueTableRow, type ValueTableRowProps };
270
550
  ```
@@ -273,6 +553,7 @@ export { ValueTable, type ValueTableProps, ValueTableRow, type ValueTableRowProp
273
553
 
274
554
  ```ts
275
555
  import { SafeHtml } from 'kerfjs';
556
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
276
557
 
277
558
  type AppTabRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
278
559
  'data-component'?: never;
@@ -282,48 +563,80 @@ type AppTabRootAttributes = Readonly<Record<`data-${string}`, string | undefined
282
563
  'data-tab-dragging'?: never;
283
564
  'data-tab-drop-position'?: never;
284
565
  }>;
566
+ type AppTabPresentation = 'pill' | 'segmented' | 'icon-only';
567
+ type AppTabSize = 'default' | 'compact';
285
568
  interface AppTabProps {
286
569
  id: string;
287
570
  name: string;
288
571
  selected?: boolean;
289
572
  closable?: boolean;
290
573
  draggable?: boolean;
291
- leading?: SafeHtml;
292
- trailing?: SafeHtml;
574
+ leading?: KerfUiContent;
575
+ trailing?: KerfUiContent;
576
+ /** Visual treatment within a TabBar. Icon-only tabs retain `name` as their accessible name. */
577
+ presentation?: AppTabPresentation;
578
+ /** Compact tabs use the 32px application-rail height. */
579
+ size?: AppTabSize;
580
+ /** Maximum visible label width in CSS pixels before ellipsis. */
581
+ labelMaxWidth?: number;
293
582
  /** Decorative dormant content for the close button. Must not contain interactive descendants. */
294
583
  closeIcon?: SafeHtml;
295
584
  selectAction?: string;
296
585
  closeAction?: string;
297
586
  className?: string;
587
+ /** Render as an unanimated loading skeleton, disabling select/close and dragging. */
588
+ placeholder?: boolean;
298
589
  rootAttributes?: AppTabRootAttributes;
299
590
  }
300
- declare function AppTab({ id, name, selected, closable, draggable, leading, trailing, closeIcon, selectAction, closeAction, className, rootAttributes }: AppTabProps): SafeHtml;
591
+ declare function AppTab({ id, name, selected, closable, draggable, leading, trailing, presentation, size, labelMaxWidth, closeIcon, selectAction, closeAction, className, placeholder, rootAttributes, }: AppTabProps): SafeHtml;
301
592
 
302
- export { AppTab, type AppTabProps };
593
+ export { AppTab, type AppTabPresentation, type AppTabProps, type AppTabSize };
303
594
  ```
304
595
 
305
596
  ## `@kerfjs/ui/tab-bar`
306
597
 
307
598
  ```ts
308
- import { SafeHtml } from 'kerfjs';
599
+ import * as kerfjs from 'kerfjs';
600
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
309
601
 
602
+ type TabActivation = 'automatic' | 'manual';
603
+ type TabBarAllocation = 'intrinsic' | 'fill';
604
+ type TabBarPresentation = 'rail' | 'segmented' | 'inspector';
605
+ type TabBarTrailingPlacement = 'separate' | 'adjacent';
310
606
  interface TabBarProps {
311
607
  id: string;
312
608
  label: string;
313
- children: SafeHtml | readonly SafeHtml[];
314
- leading?: SafeHtml;
315
- trailing?: SafeHtml;
609
+ children: KerfUiContent;
610
+ leading?: KerfUiContent;
611
+ trailing?: KerfUiContent;
316
612
  className?: string;
613
+ /**
614
+ * Keyboard activation mode for this strip, emitted as `data-tab-activation` for
615
+ * `wireTabBars` to read (overrides its `activation` option). `'automatic'` (default)
616
+ * selects on arrow / Home / End; `'manual'` moves roving focus only and the user
617
+ * selects with Enter / Space / click — use it when selecting a tab is a heavy action.
618
+ */
619
+ activation?: TabActivation;
620
+ /** How available strip width is allocated across child AppTabs. */
621
+ allocation?: TabBarAllocation;
622
+ /** Named strip chrome for application rails, segmented tabs, or inspectors. */
623
+ presentation?: TabBarPresentation;
624
+ /** Keep a trailing action beside the final tab or at the far edge of the bar. */
625
+ trailingPlacement?: TabBarTrailingPlacement;
317
626
  }
318
627
  /** Render a controlled tab strip. The application owns selection, order, and persistence. */
319
- declare function TabBar({ id, label, children, leading, trailing, className }: TabBarProps): SafeHtml;
628
+ declare function TabBar({ id, label, children, leading, trailing, className, activation, allocation, presentation, trailingPlacement, }: TabBarProps): kerfjs.SafeHtml;
320
629
 
321
- export { TabBar, type TabBarProps };
630
+ export { type TabActivation, TabBar, type TabBarAllocation, type TabBarPresentation, type TabBarProps, type TabBarTrailingPlacement };
322
631
  ```
323
632
 
324
633
  ## `@kerfjs/ui/wire-tab-bars`
325
634
 
326
635
  ```ts
636
+ import { TabActivation } from './tab-bar.js';
637
+ import 'kerfjs';
638
+ import './semantic-content-BbzjvSu9.js';
639
+
327
640
  type TabReorderSource = 'pointer' | 'keyboard';
328
641
  type TabDropPosition = 'before' | 'after';
329
642
  interface TabReorder {
@@ -333,23 +646,450 @@ interface TabReorder {
333
646
  position: TabDropPosition;
334
647
  source: TabReorderSource;
335
648
  }
649
+
336
650
  interface WireTabBarsOptions {
337
651
  onReorder: (change: TabReorder) => void;
652
+ /**
653
+ * How arrow / Home / End keys activate tabs (default `'automatic'`):
654
+ * - `'automatic'` moves roving focus **and** selects the focused tab (clicks it).
655
+ * - `'manual'` moves roving focus only; the user selects with Enter / Space / click
656
+ * (the ARIA Tabs manual-activation pattern). Use this when activation is a heavy or
657
+ * side-effecting action (e.g. a tab that loads a project) so arrowing through the
658
+ * strip doesn't trigger it on every tab.
659
+ *
660
+ * A per-bar `data-tab-activation="manual" | "automatic"` attribute (see the `TabBar`
661
+ * `activation` prop) overrides this option for that strip.
662
+ */
663
+ activation?: TabActivation;
338
664
  }
339
665
  declare function reorderTabs<T>(items: readonly T[], getId: (item: T) => string, sourceId: string, targetId: string, position: TabDropPosition): T[];
340
666
  /** Wire reordering and keyboard navigation while leaving controlled state in the application. */
341
- declare function wireTabBars(root: HTMLElement | Document, { onReorder }: WireTabBarsOptions): () => void;
667
+ declare function wireTabBars(root: HTMLElement | Document, { onReorder, activation }: WireTabBarsOptions): () => void;
668
+
669
+ export { TabActivation, type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
670
+ ```
671
+
672
+ ## `@kerfjs/ui/nav-stack`
673
+
674
+ ```ts
675
+ import * as kerfjs from 'kerfjs';
676
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
677
+
678
+ /**
679
+ * One entry in a {@link NavStack}. The app owns the stack as an array (usually a
680
+ * signal); `NavStack` renders it and `wireNavStack` animates the transitions.
681
+ */
682
+ interface NavStackView {
683
+ /** Stable identity for keyed reconcile and transition direction. */
684
+ key: string;
685
+ content: KerfUiContent;
686
+ /** Title shown in the top toolbar for this view. */
687
+ title?: string;
688
+ /** Trailing actions for this view's top toolbar. */
689
+ toolbar?: KerfUiContent;
690
+ /** Bottom toolbar for this view. Cross-fades with the top chrome on navigation. */
691
+ bottomToolbar?: KerfUiContent;
692
+ }
693
+ interface NavStackProps {
694
+ id: string;
695
+ /** Accessible name for the stack region. */
696
+ label: string;
697
+ /** The stack, root first; the last entry is the active top view. */
698
+ views: NavStackView[];
699
+ /** Accessible label for the back control (default "Back"). */
700
+ backLabel?: string;
701
+ /** Hide the top toolbar entirely (rare — a fully custom-chrome view). */
702
+ hideToolbar?: boolean;
703
+ /** Optional persistent bottom toolbar used when the active view does not provide one. */
704
+ bottomToolbar?: KerfUiContent;
705
+ className?: string;
706
+ }
707
+ /**
708
+ * A navigation stack (iOS-style push/pop). Renders every entry stacked, the last
709
+ * one active; `@kerfjs/ui/wire-nav-stack`'s `wireNavStack` slides the content and
710
+ * cross-fades the chrome across a change. A single-pane layout is a `NavStack`
711
+ * with one entry. See `docs/23-app-layouts.md` §3.1.
712
+ */
713
+ declare function NavStack({ id, label, views, backLabel, hideToolbar, bottomToolbar, className, }: NavStackProps): kerfjs.SafeHtml;
714
+
715
+ export { NavStack, type NavStackProps, type NavStackView };
716
+ ```
717
+
718
+ ## `@kerfjs/ui/wire-nav-stack`
719
+
720
+ ```ts
721
+ interface WireNavStackOptions {
722
+ /** Invoked when the back control is activated. The app pops its own stack. */
723
+ onBack?: () => void;
724
+ /** Transition duration in ms (default 200). Set 0 to disable animation. */
725
+ duration?: number;
726
+ }
727
+ /**
728
+ * Animate a `NavStack`'s push/pop transitions and wire its back control. The app
729
+ * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it
730
+ * changes; this helper slides the content and settles the chrome across each
731
+ * change, moves focus into every new top view, restores that view's last focused
732
+ * descendant on pop, and calls `onBack` when the back control is used. Mark a
733
+ * preferred initial target with `data-nav-focus`; otherwise the first focusable
734
+ * descendant (or the view itself) receives focus. Returns a disposer.
735
+ */
736
+ declare function wireNavStack(root: Element, options?: WireNavStackOptions): () => void;
737
+
738
+ export { type WireNavStackOptions, wireNavStack };
739
+ ```
740
+
741
+ ## `@kerfjs/ui/split-view`
742
+
743
+ ```ts
744
+ import * as kerfjs from 'kerfjs';
745
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
746
+
747
+ interface SplitViewResizable {
748
+ size: number;
749
+ min: number;
750
+ max: number;
751
+ }
752
+ interface SplitViewProps {
753
+ id: string;
754
+ label: string;
755
+ /** The list (primary) pane. */
756
+ list: KerfUiContent;
757
+ /** The detail (secondary) pane. */
758
+ detail: KerfUiContent;
759
+ /**
760
+ * Compact ("one pane at a time") classes — a handset or portrait tablet.
761
+ * Derive from `deviceClass().value.compact`. When true the split collapses to
762
+ * a `NavStack`: the list is the root and the detail is pushed over it.
763
+ */
764
+ compact?: boolean;
765
+ /** In compact mode, whether the detail is currently pushed over the list. */
766
+ detailActive?: boolean;
767
+ /** Title/label for the list (compact NavStack root + region label). */
768
+ listTitle?: string;
769
+ /** Title/label for the detail (compact NavStack pushed view + region label). */
770
+ detailTitle?: string;
771
+ /** Back label for the compact NavStack (default "Back"). */
772
+ backLabel?: string;
773
+ /** A resizable separator on roomy classes (min/max px). Omit for a fixed split. */
774
+ resizable?: SplitViewResizable;
775
+ className?: string;
776
+ }
777
+ /**
778
+ * A list-detail split. On roomy classes it shows both panes side
779
+ * by side with an optional resizable separator; on compact classes it collapses
780
+ * to a `NavStack` (list → detail). See `docs/23-app-layouts.md` §3.2. Compose the
781
+ * resizable wiring with `wireResizableRegions` and the compact back with
782
+ * `wireNavStack`.
783
+ */
784
+ declare function SplitView({ id, label, list, detail, compact, detailActive, listTitle, detailTitle, backLabel, resizable, className, }: SplitViewProps): kerfjs.SafeHtml;
785
+
786
+ export { SplitView, type SplitViewProps, type SplitViewResizable };
787
+ ```
788
+
789
+ ## `@kerfjs/ui/pane`
790
+
791
+ ```ts
792
+ import * as kerfjs from 'kerfjs';
793
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
794
+
795
+ /** Logical sides that can show a {@link Pane} separator. */
796
+ type PaneSeparatorSide = 'block-start' | 'block-end' | 'inline-start' | 'inline-end';
797
+ /** Semantic root elements supported by {@link Pane}. */
798
+ type PaneElement = 'article' | 'aside' | 'div' | 'main' | 'section';
799
+ /** Semantic elements supported by the scrolling content slot. */
800
+ type PaneContentElement = 'div' | 'main' | 'nav' | 'section';
801
+ type PaneRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
802
+ 'data-component'?: never;
803
+ 'data-separator-block-start'?: never;
804
+ 'data-separator-block-end'?: never;
805
+ 'data-separator-inline-start'?: never;
806
+ 'data-separator-inline-end'?: never;
807
+ }>;
808
+ interface PaneProps {
809
+ /** Optional fixed chrome above the scrolling content, arranged vertically. */
810
+ header?: KerfUiContent;
811
+ /** The pane's primary vertical, scrolling content stack. */
812
+ children?: KerfUiContent;
813
+ /** Optional fixed chrome below the scrolling content. */
814
+ footer?: KerfUiContent;
815
+ /** Root semantics. Defaults to `div`. */
816
+ element?: PaneElement;
817
+ /** Scrolling content semantics. Defaults to `div`. */
818
+ contentElement?: PaneContentElement;
819
+ /** Independent logical-edge separator lines. Defaults to none. */
820
+ separators?: readonly PaneSeparatorSide[];
821
+ id?: string;
822
+ /** Accessible name for a landmark root such as `aside` or `main`. */
823
+ label?: string;
824
+ /** Accessible name for a landmark scrolling slot such as `nav`. */
825
+ contentLabel?: string;
826
+ className?: string;
827
+ headerClassName?: string;
828
+ contentClassName?: string;
829
+ footerClassName?: string;
830
+ /** Safe `data-*` metadata; Pane-owned structural attributes remain protected. */
831
+ rootAttributes?: PaneRootAttributes;
832
+ }
833
+ /**
834
+ * An unpadded application column with optional fixed header/footer slots and one
835
+ * scrolling vertical content owner. Separator lines are independently opt-in on
836
+ * each logical edge, so the same component works as a sidebar, main area,
837
+ * inspector, or dialog column.
838
+ */
839
+ declare function Pane({ header, children, footer, element, contentElement, separators, id, label, contentLabel, className, headerClassName, contentClassName, footerClassName, rootAttributes, }: PaneProps): kerfjs.SafeHtml;
840
+
841
+ export { Pane, type PaneContentElement, type PaneElement, type PaneProps, type PaneSeparatorSide };
842
+ ```
843
+
844
+ ## `@kerfjs/ui/workbench`
845
+
846
+ ```ts
847
+ import { SafeHtml } from 'kerfjs';
848
+ import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
849
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
850
+
851
+ /** A collapsible Workbench panel — a side rail or the bottom drawer. */
852
+ interface WorkbenchPanel {
853
+ content: KerfUiContent;
854
+ /** Whether the panel is currently collapsed (the app owns this). */
855
+ collapsed?: boolean;
856
+ /** Rail width, or drawer height, in px. Overrides the CSS default. */
857
+ size?: number;
858
+ /** Accessible name for the panel region. */
859
+ label?: string;
860
+ separator?: ResizableRegionSeparator;
861
+ collapseMotion?: ResizableRegionCollapseMotion;
862
+ contentOverflow?: ResizableRegionContentOverflow;
863
+ presentation?: ResizableRegionPresentation;
864
+ /** Control shown in a safe-area-aware viewport corner while collapsed. */
865
+ restoreControl?: SafeHtml;
866
+ restorePosition?: ResizableRegionRestorePosition;
867
+ }
868
+ interface WorkbenchProps {
869
+ id: string;
870
+ label: string;
871
+ /** The central work area. */
872
+ main: KerfUiContent;
873
+ leftRail?: WorkbenchPanel;
874
+ rightRail?: WorkbenchPanel;
875
+ bottomDrawer?: WorkbenchPanel;
876
+ className?: string;
877
+ }
878
+ /**
879
+ * The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and
880
+ * bottom drawer around a central work area (any absent). Collapsing snaps the
881
+ * panel's track to zero in one reflow while its fixed-size content slides out via
882
+ * a composited transform — the instant-width / sliding-content technique, so the
883
+ * work area relayouts once, not per frame. Bottom-drawer content stays anchored
884
+ * to the shell's stable bottom edge throughout that transition. The app owns
885
+ * each `collapsed` flag; the collapse is pure CSS (no wire). See
886
+ * `docs/23-app-layouts.md` §3.3.
887
+ */
888
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, }: WorkbenchProps): SafeHtml;
889
+
890
+ export { Workbench, type WorkbenchPanel, type WorkbenchProps };
891
+ ```
892
+
893
+ ## `@kerfjs/ui/collapsible-panel`
894
+
895
+ ```ts
896
+ import { SafeHtml } from 'kerfjs';
897
+ import { LucideIcon } from './lucide-icon.js';
898
+ import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
899
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
900
+ import 'lucide';
901
+
902
+ /** Which edge a {@link CollapsiblePanel} docks to. */
903
+ type CollapsiblePanelSide = 'left' | 'right' | 'bottom';
904
+ /**
905
+ * The standard collapse/expand icon for a panel `side` and `collapsed` state,
906
+ * so every app's sidebars and drawers use one recognizable convention:
907
+ * `PanelLeft*` for a left rail, `PanelRight*` for a right rail, `PanelBottom*`
908
+ * for a bottom drawer — the `Close` glyph while open, the `Open` glyph while
909
+ * collapsed. Exposed so an app can render its own toggle affordance.
910
+ */
911
+ declare function collapsiblePanelToggleIcon(side: CollapsiblePanelSide, collapsed: boolean): {
912
+ icon: Parameters<typeof LucideIcon>[0]['icon'];
913
+ name: string;
914
+ };
915
+ interface CollapsiblePanelToggleProps {
916
+ /** The panel this toggle controls. */
917
+ side: CollapsiblePanelSide;
918
+ /** The panel's current collapsed state (drives the icon direction). */
919
+ collapsed: boolean;
920
+ /** `data-action` the button carries so `wireSidebar` can delegate its click. */
921
+ action: string;
922
+ /** The panel id the button targets (`data-tab-panel`-style: `data-collapsible-panel`). */
923
+ panelId?: string;
924
+ /** Accessible label; defaults to "Collapse"/"Expand". */
925
+ label?: string;
926
+ className?: string;
927
+ }
928
+ /**
929
+ * A standard collapse/expand toggle button for a {@link CollapsiblePanel}: the
930
+ * recognizable per-side icon (see {@link collapsiblePanelToggleIcon}) plus the
931
+ * `data-action` / `aria-expanded` `wireSidebar` reads. Placement is the app's —
932
+ * put it in the panel's own header (to collapse) and somewhere always-visible
933
+ * (to expand while collapsed).
934
+ */
935
+ declare function CollapsiblePanelToggle({ side, collapsed, action, panelId, label, className, }: CollapsiblePanelToggleProps): SafeHtml;
936
+ interface CollapsiblePanelProps {
937
+ /** A stable id for the panel — `wireSidebar` targets it and toggles reference it. */
938
+ id: string;
939
+ /** Which edge the panel docks to: a left/right rail or a bottom drawer. */
940
+ side: CollapsiblePanelSide;
941
+ /** Whether the panel is currently collapsed (the app owns this signal). */
942
+ collapsed?: boolean;
943
+ /** Rail width or drawer height in px. Overrides the CSS default. */
944
+ size?: number;
945
+ /** Accessible label for the panel region. */
946
+ label?: string;
947
+ /** Panel content. */
948
+ children?: KerfUiContent;
949
+ separator?: ResizableRegionSeparator;
950
+ collapseMotion?: ResizableRegionCollapseMotion;
951
+ contentOverflow?: ResizableRegionContentOverflow;
952
+ presentation?: ResizableRegionPresentation;
953
+ /** Control shown in a safe-area-aware viewport corner while collapsed. */
954
+ restoreControl?: SafeHtml;
955
+ restorePosition?: ResizableRegionRestorePosition;
956
+ className?: string;
957
+ }
958
+ /**
959
+ * A standalone collapsible side rail or bottom drawer, outside the full
960
+ * {@link Workbench} shell. It owns only the presentation: a fixed-size content
961
+ * area that stays laid out while the panel's track snaps to zero and the content
962
+ * slides out via `transform` (one reflow, composited — the same technique
963
+ * `Workbench` and the catalog sidebar use). Bottom-drawer content stays anchored
964
+ * to the panel's fixed bottom edge, so the track cannot move its layout origin
965
+ * underneath the transform transition. The app owns the `collapsed` signal;
966
+ * pair it with `wireSidebar` for the toggle, focus, compact-overlay, keyboard,
967
+ * and persistence semantics, and with `CollapsiblePanelToggle` for the standard
968
+ * affordance. See `docs/24-collapsible-panel.md`.
969
+ */
970
+ declare function CollapsiblePanel({ id, side, collapsed, size, label, children, separator, collapseMotion, contentOverflow, presentation, restoreControl, restorePosition, className, }: CollapsiblePanelProps): SafeHtml;
971
+
972
+ export { CollapsiblePanel, type CollapsiblePanelProps, type CollapsiblePanelSide, CollapsiblePanelToggle, type CollapsiblePanelToggleProps, collapsiblePanelToggleIcon };
973
+ ```
974
+
975
+ ## `@kerfjs/ui/wire-sidebar`
976
+
977
+ ```ts
978
+ import { Signal, ReadonlySignal } from 'kerfjs';
979
+ import { DeviceClass } from './device-class.js';
980
+
981
+ /** Minimal `localStorage`-shaped store, so the persistence hook is testable. */
982
+ interface SidebarStorage {
983
+ getItem(key: string): string | null;
984
+ setItem(key: string, value: string): void;
985
+ }
986
+ interface WireSidebarPanel {
987
+ /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */
988
+ id: string;
989
+ /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and
990
+ * writes it (toggle, Escape, backdrop, persistence). */
991
+ collapsed: Signal<boolean>;
992
+ /** `data-action` value the panel's toggle button(s) carry. */
993
+ toggleAction: string;
994
+ /** When set, the collapsed state is loaded from and saved to `storage` under
995
+ * this key (a persistence hook), so the panel remembers its state. */
996
+ storageKey?: string;
997
+ }
998
+ interface WireSidebarOptions {
999
+ panels: readonly WireSidebarPanel[];
1000
+ /**
1001
+ * When provided, the sidebar adopts a compact **overlay** presentation while
1002
+ * `deviceClass.compact` is true: an open panel floats over the content with a
1003
+ * dismissable backdrop, Escape and backdrop-click collapse it, and focus is
1004
+ * trapped within the open panel (the ARIA dialog pattern). Without it the panel
1005
+ * is always inline.
1006
+ */
1007
+ deviceClass?: ReadonlySignal<DeviceClass>;
1008
+ /** Compact devices either overlay the panels (default) or hide them in favor
1009
+ * of an application-owned responsive replacement. */
1010
+ compactPresentation?: 'overlay' | 'hidden';
1011
+ /** Collapse the other panels when one opens in compact overlay mode. */
1012
+ exclusiveCompact?: boolean;
1013
+ /** Persistence store (default `globalThis.localStorage`, if present). */
1014
+ storage?: SidebarStorage;
1015
+ }
1016
+ /**
1017
+ * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle
1018
+ * delegation with focus restore, focus-into on open, an optional compact overlay
1019
+ * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional
1020
+ * persistence hook. The app owns each `collapsed` signal and the layout; this wire
1021
+ * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.
1022
+ */
1023
+ declare function wireSidebar(root: HTMLElement, { panels, deviceClass, compactPresentation, exclusiveCompact, storage, }: WireSidebarOptions): () => void;
342
1024
 
343
- export { type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
1025
+ export { type SidebarStorage, type WireSidebarOptions, type WireSidebarPanel, wireSidebar };
1026
+ ```
1027
+
1028
+ ## `@kerfjs/ui/tab-scaffold`
1029
+
1030
+ ```ts
1031
+ import { SafeHtml } from 'kerfjs';
1032
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1033
+
1034
+ interface TabScaffoldTab<Id extends string = string> {
1035
+ id: Id;
1036
+ label: string;
1037
+ /** Decorative icon shown above the label in the bottom bar. */
1038
+ icon?: SafeHtml;
1039
+ /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */
1040
+ content: KerfUiContent;
1041
+ }
1042
+ interface TabScaffoldProps<Id extends string = string> {
1043
+ id: string;
1044
+ /** Accessible name for the tab bar. */
1045
+ label: string;
1046
+ tabs: readonly TabScaffoldTab<Id>[];
1047
+ /** The controlled active tab id (the app owns selection). */
1048
+ active: NoInfer<Id>;
1049
+ className?: string;
1050
+ }
1051
+ /**
1052
+ * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches
1053
+ * between major sections, each tab keeping its own content (usually a `NavStack`)
1054
+ * mounted so its stack and scroll survive a switch. Controlled — the app owns
1055
+ * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.
1056
+ * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of
1057
+ * a bottom bar. See `docs/23-app-layouts.md` §3.4.
1058
+ */
1059
+ declare function TabScaffold<Id extends string>({ id, label, tabs, active, className, }: TabScaffoldProps<Id>): SafeHtml;
1060
+
1061
+ export { TabScaffold, type TabScaffoldProps, type TabScaffoldTab };
1062
+ ```
1063
+
1064
+ ## `@kerfjs/ui/wire-tab-scaffold`
1065
+
1066
+ ```ts
1067
+ interface WireTabScaffoldOptions {
1068
+ /** Invoked with the selected tab id when a bottom-bar tab is activated. */
1069
+ onSelect: (tabId: string) => void;
1070
+ }
1071
+ /**
1072
+ * Wire a `TabScaffold`'s bottom tab bar: clicking a tab calls `onSelect` with its
1073
+ * id (the app then updates its controlled `active`). Returns a disposer.
1074
+ */
1075
+ declare function wireTabScaffold(root: Element, options: WireTabScaffoldOptions): () => void;
1076
+
1077
+ export { type WireTabScaffoldOptions, wireTabScaffold };
344
1078
  ```
345
1079
 
346
1080
  ## `@kerfjs/ui/resizable-region`
347
1081
 
348
1082
  ```ts
349
1083
  import { SafeHtml } from 'kerfjs';
1084
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
350
1085
 
351
1086
  type ResizableRegionAxis = 'horizontal' | 'vertical';
352
1087
  type ResizableRegionEdge = 'start' | 'end';
1088
+ type ResizableRegionSeparator = 'auto' | 'hidden';
1089
+ type ResizableRegionCollapseMotion = 'none' | 'slide' | 'fade-slide';
1090
+ type ResizableRegionContentOverflow = 'clip' | 'auto' | 'visible';
1091
+ type ResizableRegionPresentation = 'inline' | 'overlay' | 'hidden';
1092
+ type ResizableRegionRestorePosition = 'bottom-start' | 'bottom-end';
353
1093
  interface ResizableRegionProps {
354
1094
  id: string;
355
1095
  label: string;
@@ -360,15 +1100,27 @@ interface ResizableRegionProps {
360
1100
  edge?: ResizableRegionEdge;
361
1101
  collapsed?: boolean;
362
1102
  transitioning?: boolean;
1103
+ /** Whether the separator line is painted. The resize hit target remains available. */
1104
+ separator?: ResizableRegionSeparator;
1105
+ /** Keep the track change instant while optionally sliding the fixed-size content. */
1106
+ collapseMotion?: ResizableRegionCollapseMotion;
1107
+ /** Overflow policy for content such as an open popup inside a bottom drawer. */
1108
+ contentOverflow?: ResizableRegionContentOverflow;
1109
+ /** Inline layout, an edge overlay, or a responsive replacement that removes the region. */
1110
+ presentation?: ResizableRegionPresentation;
1111
+ /** Always-available control rendered while collapsed, outside the clipped region. */
1112
+ restoreControl?: SafeHtml;
1113
+ /** Safe-area-aware viewport corner for `restoreControl`. */
1114
+ restorePosition?: ResizableRegionRestorePosition;
363
1115
  /** Decorative dormant content for the separator handle. Must not contain interactive descendants. */
364
1116
  handleIcon?: SafeHtml;
365
- children: SafeHtml | SafeHtml[];
1117
+ children: KerfUiContent;
366
1118
  }
367
1119
  declare const clampRegionSize: (size: number, min: number, max: number) => number;
368
1120
  declare const resizeRegionFromPointer: (startSize: number, delta: number, edge: ResizableRegionEdge) => number;
369
- declare function ResizableRegion({ id, label, size, min, max, axis, edge, collapsed, transitioning, handleIcon, children }: ResizableRegionProps): SafeHtml;
1121
+ declare function ResizableRegion({ id, label, size, min, max, axis, edge, collapsed, transitioning, separator, collapseMotion, contentOverflow, presentation, restoreControl, restorePosition, handleIcon, children, }: ResizableRegionProps): SafeHtml;
370
1122
 
371
- export { ResizableRegion, type ResizableRegionAxis, type ResizableRegionEdge, type ResizableRegionProps, clampRegionSize, resizeRegionFromPointer };
1123
+ export { ResizableRegion, type ResizableRegionAxis, type ResizableRegionCollapseMotion, type ResizableRegionContentOverflow, type ResizableRegionEdge, type ResizableRegionPresentation, type ResizableRegionProps, type ResizableRegionRestorePosition, type ResizableRegionSeparator, clampRegionSize, resizeRegionFromPointer };
372
1124
  ```
373
1125
 
374
1126
  ## `@kerfjs/ui/wire-resizable-regions`
@@ -386,78 +1138,472 @@ interface WireResizableRegionsOptions {
386
1138
  onCommit: (change: ResizeCommit) => void;
387
1139
  }
388
1140
  /** Wire pointer and separator-keyboard behavior for every ResizableRegion below root. */
389
- declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPreview, onCommit }: WireResizableRegionsOptions): () => void;
1141
+ declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPreview, onCommit, }: WireResizableRegionsOptions): () => void;
390
1142
 
391
1143
  export { type ResizeCommit, type WireResizableRegionsOptions, wireResizableRegions };
392
1144
  ```
393
1145
 
394
- ## `@kerfjs/ui/segmented-control`
1146
+ ## `@kerfjs/ui/device-class`
1147
+
1148
+ ```ts
1149
+ import { ReadonlySignal } from 'kerfjs';
1150
+
1151
+ /**
1152
+ * Reactive device-class detection for `@kerfjs/ui` (see `docs/23-app-layouts.md`
1153
+ * §2). `deviceClass()` returns a `ReadonlySignal<DeviceClass>` describing the
1154
+ * current viewport as a size bucket × orientation × viewport-segment count, so a
1155
+ * layout can pick its presentation reactively instead of hand-wiring `matchMedia`.
1156
+ *
1157
+ * One shared viewport source backs every reader; the pure `classifyViewport`
1158
+ * core is DOM-free and directly unit-tested.
1159
+ */
1160
+ type DeviceSize = 'xs-mobile' | 'mobile' | 'tablet' | 'desktop' | 'xl-desktop';
1161
+ type DeviceOrientation = 'portrait' | 'landscape';
1162
+ /** Minimum widths (px) at which each larger bucket begins. `xs-mobile` is 0. */
1163
+ interface DeviceBreakpoints {
1164
+ mobile: number;
1165
+ tablet: number;
1166
+ desktop: number;
1167
+ 'xl-desktop': number;
1168
+ }
1169
+ interface DeviceClass {
1170
+ size: DeviceSize;
1171
+ orientation: DeviceOrientation;
1172
+ /** Horizontal viewport segments (foldables / dual-screen); 1 on ordinary devices. */
1173
+ segments: number;
1174
+ /** Vertical viewport segments; 1 on ordinary devices. */
1175
+ verticalSegments: number;
1176
+ /** Small phones — `xs-mobile` or `mobile`. */
1177
+ handset: boolean;
1178
+ /** "One pane at a time" — a handset or a portrait tablet. */
1179
+ compact: boolean;
1180
+ /** True when the current size is `size` or larger, e.g. `atLeast('tablet')`. */
1181
+ atLeast(size: DeviceSize): boolean;
1182
+ }
1183
+ /** A raw viewport snapshot, before breakpoints are applied. */
1184
+ interface Viewport {
1185
+ width: number;
1186
+ height: number;
1187
+ segments: number;
1188
+ verticalSegments: number;
1189
+ }
1190
+ interface DeviceClassOptions {
1191
+ /** Override any of the default bucket thresholds. */
1192
+ breakpoints?: Partial<DeviceBreakpoints>;
1193
+ /** The viewport assumed when there is no DOM (SSR). Defaults to 1024×768, one segment. */
1194
+ ssr?: Partial<Viewport>;
1195
+ }
1196
+ declare const DEFAULT_BREAKPOINTS: DeviceBreakpoints;
1197
+ /**
1198
+ * Classify a raw viewport into a {@link DeviceClass}. Pure and DOM-free — the
1199
+ * single source of truth for the bucketing rules.
1200
+ */
1201
+ declare function classifyViewport(width: number, orientation: DeviceOrientation, segments?: number, verticalSegments?: number, breakpoints?: DeviceBreakpoints): DeviceClass;
1202
+ /**
1203
+ * A reactive signal of the current {@link DeviceClass}. Reading it inside an
1204
+ * `effect`/`computed` re-runs when the viewport crosses a breakpoint, rotates,
1205
+ * or changes its segment count. Without a DOM it resolves to `options.ssr`
1206
+ * (default 1024×768, landscape, one segment).
1207
+ */
1208
+ declare function deviceClass(options?: DeviceClassOptions): ReadonlySignal<DeviceClass>;
1209
+
1210
+ export { DEFAULT_BREAKPOINTS, type DeviceBreakpoints, type DeviceClass, type DeviceClassOptions, type DeviceOrientation, type DeviceSize, type Viewport, classifyViewport, deviceClass };
1211
+ ```
1212
+
1213
+ ## `@kerfjs/ui/catalog`
395
1214
 
396
1215
  ```ts
397
1216
  import { SafeHtml } from 'kerfjs';
1217
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1218
+
1219
+ /** A reference link shown in the detail footer for the active entry. */
1220
+ interface CatalogResource {
1221
+ label: string;
1222
+ href: string;
1223
+ /** Optional monospace detail (e.g. a file path) shown after the label. */
1224
+ detail?: string;
1225
+ }
1226
+ /** A related entry offered in the detail footer's "Related entries" popup menu. */
1227
+ interface CatalogRelated {
1228
+ id: string;
1229
+ name: string;
1230
+ /** Group heading in the menu, e.g. "Uses" / "Used by". */
1231
+ group: string;
1232
+ }
1233
+ interface CatalogEntry {
1234
+ id: string;
1235
+ name: string;
1236
+ description?: string;
1237
+ /** Short metadata tags shown at the trailing edge of the sidebar row. */
1238
+ tags?: readonly string[];
1239
+ resources?: readonly CatalogResource[];
1240
+ related?: readonly CatalogRelated[];
1241
+ }
1242
+ interface CatalogSection {
1243
+ category: string;
1244
+ entries: readonly CatalogEntry[];
1245
+ }
1246
+ /**
1247
+ * A secondary group of sections shown below the primary sidebar sections with a
1248
+ * quieter "ecosystem" treatment (e.g. third-party components). Optionally
1249
+ * collapsible — the app owns `expanded` and toggles it from `wireCatalog`'s
1250
+ * `onToggleSecondary`.
1251
+ */
1252
+ interface CatalogSecondaryGroup {
1253
+ label: string;
1254
+ sections: readonly CatalogSection[];
1255
+ /** When true, the group's label is a disclosure toggle controlling `expanded`. */
1256
+ collapsible?: boolean;
1257
+ /** Whether the group is expanded (controlled). Ignored unless `collapsible`. */
1258
+ expanded?: boolean;
1259
+ }
1260
+ interface CatalogBrand {
1261
+ title: string;
1262
+ subtitle?: string;
1263
+ /** Logo image URL (rendered decorative). Omit for a text-only brand. */
1264
+ logoUrl?: string;
1265
+ }
1266
+ interface CatalogProps {
1267
+ brand: CatalogBrand;
1268
+ sections: readonly CatalogSection[];
1269
+ /** The controlled active entry id — the app owns this signal. */
1270
+ active: string;
1271
+ /** The rendered preview for the active entry; the app computes it from `active`. */
1272
+ content: KerfUiContent;
1273
+ /** Whether the sidebar is collapsed (controlled). */
1274
+ collapsed?: boolean;
1275
+ /** Current theme; when set, a theme toggle is shown that switches to the opposite. Omit to hide it. */
1276
+ theme?: 'light' | 'dark';
1277
+ /** Extra header controls placed before the theme toggle (each a `ToolbarControlGroup`). */
1278
+ headerActions?: KerfUiContent;
1279
+ /** A secondary "ecosystem" group of sections below the primary category groups. */
1280
+ secondarySections?: CatalogSecondaryGroup;
1281
+ /** Extra sidebar content below the category groups (and the secondary group). */
1282
+ sidebarFooter?: KerfUiContent;
1283
+ /** Status line content shown at the start of the detail footer. */
1284
+ status?: KerfUiContent;
1285
+ /**
1286
+ * Whether to highlight specimens' computed borders (or transparent outer
1287
+ * bounds) and non-zero margins. Pass a boolean (rather than omitting the
1288
+ * prop) when the active entry can switch between component and composition
1289
+ * previews; `wireCatalogGeometryOverlay` keeps the overlay synchronized.
1290
+ */
1291
+ geometryOverlay?: boolean;
1292
+ selectAction?: string;
1293
+ toggleSidebarAction?: string;
1294
+ toggleThemeAction?: string;
1295
+ /** Action fired by the secondary group's disclosure toggle (when collapsible). */
1296
+ toggleSecondaryAction?: string;
1297
+ className?: string;
1298
+ }
1299
+ /**
1300
+ * A reusable component-catalog shell: a collapsible category sidebar, a titled
1301
+ * detail stage that renders the active entry's preview, and a footer with
1302
+ * reference links and a related-entry popup menu. Built entirely from public
1303
+ * `@kerfjs/ui` primitives. Controlled and stateless — the app owns the `active`,
1304
+ * `collapsed`, and `theme` signals and computes `content` from `active` in its own
1305
+ * render; wire the sidebar/collapse/theme actions with `wireCatalog`.
1306
+ */
1307
+ declare function Catalog({ brand, sections, active, content, collapsed, theme, headerActions, secondarySections, sidebarFooter, status, geometryOverlay, selectAction, toggleSidebarAction, toggleThemeAction, toggleSecondaryAction, className, }: CatalogProps): SafeHtml;
1308
+ /**
1309
+ * How a {@link CatalogExample}'s content aligns its visible left edge with the
1310
+ * example's `ListHeader` label (which sits 16px in — 8px title + 8px label):
1311
+ * - `'glyph'` — a bare glyph/text specimen with no inline geometry insets the full 16px.
1312
+ * - `'inline-control'` — a control that already carries ~8px of its own inline padding insets 8px so its content lands on the same line.
1313
+ * - `'none'` — a content-item / composition that owns its geometry and already aligns; no inset (default).
1314
+ */
1315
+ type CatalogExampleAlign = 'glyph' | 'inline-control' | 'none';
1316
+ type CatalogExampleRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
1317
+ 'data-catalog-example'?: never;
1318
+ 'data-catalog-example-stack'?: never;
1319
+ 'data-catalog-example-label'?: never;
1320
+ 'data-catalog-example-note'?: never;
1321
+ 'data-align'?: never;
1322
+ }>;
1323
+ type CatalogExampleStackRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
1324
+ 'data-catalog-example'?: never;
1325
+ 'data-catalog-example-stack'?: never;
1326
+ 'data-catalog-example-label'?: never;
1327
+ 'data-catalog-example-note'?: never;
1328
+ 'data-align'?: never;
1329
+ }>;
1330
+ interface CatalogExampleProps {
1331
+ /** The example's label, shown as a `ListHeader` above the specimen. Omit for a bare specimen. */
1332
+ label?: string;
1333
+ /** Optional explanatory note between the label and the specimen. */
1334
+ note?: SafeHtml | string;
1335
+ /** Alignment inset for the specimen — see {@link CatalogExampleAlign}. Default `'none'`. */
1336
+ align?: CatalogExampleAlign;
1337
+ /** Safe authoring `data-*` metadata for the rendered section. Helper-owned structural attributes remain protected. */
1338
+ rootAttributes?: CatalogExampleRootAttributes;
1339
+ className?: string;
1340
+ children?: KerfUiContent;
1341
+ }
1342
+ /**
1343
+ * One labeled example in a catalog preview: a `ListHeader` label, an optional
1344
+ * note, and the specimen. `align` insets the specimen so its visible left edge
1345
+ * lines up with the label text, encoding the catalog's alignment rules as a
1346
+ * first-class prop instead of per-demo CSS. The inset is published as the
1347
+ * `--kui-catalog-example-align` custom property so a debug overlay can exclude it
1348
+ * from a specimen's measured margin.
1349
+ */
1350
+ declare function CatalogExample({ label, note, align, rootAttributes, className, children, }: CatalogExampleProps): SafeHtml;
1351
+ interface CatalogExampleStackProps {
1352
+ /** Accessible label for the stack region. */
1353
+ label?: string;
1354
+ /** Safe authoring `data-*` metadata for the rendered stack. Helper-owned structural attributes remain protected. */
1355
+ rootAttributes?: CatalogExampleStackRootAttributes;
1356
+ className?: string;
1357
+ children?: KerfUiContent;
1358
+ }
1359
+ /**
1360
+ * A vertically-stacked group of {@link CatalogExample}s with the catalog's
1361
+ * example rhythm. The semantic `section` becomes a named `region` when `label`
1362
+ * is supplied; an unlabeled stack remains an ordinary grouping.
1363
+ */
1364
+ declare function CatalogExampleStack({ label, rootAttributes, className, children, }: CatalogExampleStackProps): SafeHtml;
1365
+
1366
+ export { Catalog, type CatalogBrand, type CatalogEntry, CatalogExample, type CatalogExampleAlign, type CatalogExampleProps, CatalogExampleStack, type CatalogExampleStackProps, type CatalogProps, type CatalogRelated, type CatalogResource, type CatalogSecondaryGroup, type CatalogSection };
1367
+ ```
1368
+
1369
+ ## `@kerfjs/ui/catalog-resources`
1370
+
1371
+ ```ts
1372
+ import { CatalogResource } from './catalog.js';
1373
+ import 'kerfjs';
1374
+ import './semantic-content-BbzjvSu9.js';
1375
+
1376
+ /**
1377
+ * Standard resource labels for a Kerf catalog detail footer. Keep these labels
1378
+ * stable across catalogs so people and AI-generated integrations see the same
1379
+ * choices in the same vocabulary.
1380
+ */
1381
+ type CatalogResourceKind = 'demoSource' | 'componentSource' | 'designTemplate' | 'guidance' | 'integrationGuidance';
1382
+ type CatalogGuidanceKind = 'guidance' | 'integrationGuidance';
1383
+ interface CatalogResourceTarget {
1384
+ href: string;
1385
+ /** Optional monospace detail, normally the repository-relative source path. */
1386
+ detail?: string;
1387
+ }
1388
+ interface CatalogResourcesInput {
1389
+ /** Required source for the runnable demonstration. */
1390
+ demoSource: CatalogResourceTarget;
1391
+ /** Source for the production component; omit for recipes and integrations. */
1392
+ componentSource?: CatalogResourceTarget;
1393
+ /** Optional design-tool template associated with the component. */
1394
+ designTemplate?: CatalogResourceTarget;
1395
+ /** Required UI or integration guidance. */
1396
+ guidance: CatalogResourceTarget;
1397
+ /** Use `integrationGuidance` when the component implementation is upstream. */
1398
+ guidanceKind?: CatalogGuidanceKind;
1399
+ }
1400
+ /**
1401
+ * Build the standard catalog resource group in its canonical order: demo,
1402
+ * component, optional design template, then guidance.
1403
+ */
1404
+ declare function catalogResources(input: CatalogResourcesInput): CatalogResource[];
1405
+
1406
+ export { type CatalogGuidanceKind, type CatalogResourceKind, type CatalogResourceTarget, type CatalogResourcesInput, catalogResources };
1407
+ ```
1408
+
1409
+ ## `@kerfjs/ui/wire-catalog`
1410
+
1411
+ ```ts
1412
+ interface WireCatalogOptions {
1413
+ /** Invoked with the entry id when a sidebar item or a related-entry option is chosen. */
1414
+ onSelect: (id: string) => void;
1415
+ /** Invoked when the sidebar collapse/expand control is activated. */
1416
+ onToggleSidebar?: () => void;
1417
+ /** Invoked when the theme toggle is activated. */
1418
+ onToggleTheme?: () => void;
1419
+ /** Invoked when the secondary (ecosystem) group's disclosure toggle is activated. */
1420
+ onToggleSecondary?: () => void;
1421
+ /** When set, `?<urlParam>=<id>` is written on select via `history.replaceState`. */
1422
+ urlParam?: string;
1423
+ /**
1424
+ * Reveal the chosen sidebar row after selection. `true` uses desktop-safe
1425
+ * defaults; pass options to customize scroll alignment or the media guard.
1426
+ */
1427
+ revealSelection?: boolean | CatalogRevealOptions;
1428
+ selectAction?: string;
1429
+ toggleSidebarAction?: string;
1430
+ toggleThemeAction?: string;
1431
+ toggleSecondaryAction?: string;
1432
+ }
1433
+ interface CatalogRevealOptions {
1434
+ /** Scroll alignment within the sidebar. Default `'nearest'`. */
1435
+ block?: ScrollLogicalPosition;
1436
+ /** Cross-axis alignment. Default `'nearest'`. */
1437
+ inline?: ScrollLogicalPosition;
1438
+ /** Scroll behavior. Default `'auto'`. */
1439
+ behavior?: ScrollBehavior;
1440
+ /**
1441
+ * Only reveal when this media query matches. Defaults to the Catalog's
1442
+ * desktop layout; pass `false` to reveal at every viewport size.
1443
+ */
1444
+ media?: string | false;
1445
+ }
1446
+ /**
1447
+ * Reveal one Catalog sidebar entry after the controlled render settles without
1448
+ * moving focus. Returns a cancellation function for rapid selection changes.
1449
+ */
1450
+ declare function revealCatalogEntry(root: HTMLElement, id: string, { block, inline, behavior, media, }?: CatalogRevealOptions): () => void;
1451
+ /**
1452
+ * Keep a Catalog's opt-in geometry overlay synchronized with its preview.
1453
+ * Specimens receive computed border highlights (or a dashed bound when they
1454
+ * have no border and are transparent), while positive computed margins use
1455
+ * devtools-style orange bands. CSS/stylesheet-only changes are observed too.
1456
+ * Returns a disposer.
1457
+ */
1458
+ declare function wireCatalogGeometryOverlay(root: HTMLElement): () => void;
1459
+ /**
1460
+ * Wire a {@link Catalog}'s interactions with one delegated listener set: sidebar
1461
+ * item selection (and the related-entry popup menu), the sidebar collapse toggle, and
1462
+ * the theme toggle. The app owns the `active`/`collapsed`/`theme` signals and updates
1463
+ * them in the callbacks; optionally mirror the active id into the URL via `urlParam`.
1464
+ * Returns a disposer.
1465
+ */
1466
+ declare function wireCatalog(root: HTMLElement, { onSelect, onToggleSidebar, onToggleTheme, onToggleSecondary, urlParam, revealSelection, selectAction, toggleSidebarAction, toggleThemeAction, toggleSecondaryAction, }: WireCatalogOptions): () => void;
1467
+
1468
+ export { type CatalogRevealOptions, type WireCatalogOptions, revealCatalogEntry, wireCatalog, wireCatalogGeometryOverlay };
1469
+ ```
1470
+
1471
+ ## `@kerfjs/ui/segmented-control`
1472
+
1473
+ ```ts
1474
+ import * as kerfjs from 'kerfjs';
1475
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
398
1476
 
399
1477
  type SegmentedControlAppearance = 'filled' | 'outlined' | 'toolbar';
400
1478
  type SegmentedControlShape = 'rounded' | 'pill';
401
1479
  type SegmentedControlSize = 'small' | 'default';
402
1480
  type SegmentedControlLayout = 'content' | 'equal';
403
- interface SegmentedControlChoice {
404
- value: string;
1481
+ interface SegmentedControlChoice<Value extends string = string> {
1482
+ value: Value;
405
1483
  label: string;
406
- content?: SafeHtml;
1484
+ content?: KerfUiContent;
407
1485
  title?: string;
408
1486
  disabled?: boolean;
409
1487
  }
410
- interface SegmentedControlProps {
1488
+ interface SegmentedControlProps<Value extends string = string> {
411
1489
  id: string;
412
1490
  label: string;
413
- value: string;
414
- choices: readonly SegmentedControlChoice[];
1491
+ value: NoInfer<Value>;
1492
+ choices: readonly SegmentedControlChoice<Value>[];
415
1493
  action?: string;
416
1494
  appearance?: SegmentedControlAppearance;
417
1495
  shape?: SegmentedControlShape;
418
1496
  size?: SegmentedControlSize;
419
1497
  layout?: SegmentedControlLayout;
420
1498
  className?: string;
1499
+ /** Render as an unanimated loading skeleton, disabling every segment. */
1500
+ placeholder?: boolean;
421
1501
  }
422
- declare function SegmentedControl({ id, label, value, choices, action, appearance, shape, size, layout, className, }: SegmentedControlProps): SafeHtml;
1502
+ declare function SegmentedControl<Value extends string>({ id, label, value, choices, action, appearance, shape, size, layout, className, placeholder, }: SegmentedControlProps<Value>): kerfjs.SafeHtml;
423
1503
 
424
1504
  export { SegmentedControl, type SegmentedControlAppearance, type SegmentedControlChoice, type SegmentedControlLayout, type SegmentedControlProps, type SegmentedControlShape, type SegmentedControlSize };
425
1505
  ```
426
1506
 
1507
+ ## `@kerfjs/ui/select`
1508
+
1509
+ ```ts
1510
+ import { SafeHtml } from 'kerfjs';
1511
+ import { CssColor } from './css-values.js';
1512
+ import { LucideNode } from './lucide-icon.js';
1513
+ import 'lucide';
1514
+
1515
+ interface SelectChoice<Value extends string = string> {
1516
+ value: Value;
1517
+ label: string;
1518
+ icon?: LucideNode;
1519
+ iconName?: string;
1520
+ /** Typed semantic or application-owned color for the optional icon. */
1521
+ color?: CssColor;
1522
+ group?: string;
1523
+ separatorBefore?: boolean;
1524
+ }
1525
+ type SelectAccessibleName = {
1526
+ label: string;
1527
+ ariaLabel?: string;
1528
+ } | {
1529
+ label?: never;
1530
+ ariaLabel: string;
1531
+ };
1532
+ type SelectPresentation = 'form' | 'toolbar-borderless' | 'navigation';
1533
+ type SelectSize = 'default' | 'compact';
1534
+ type SelectSelectedPresentation = 'label' | 'icon-only';
1535
+ type SelectFocusRingOwner = 'select' | 'group';
1536
+ interface SelectBaseProps<Value extends string = string> {
1537
+ name: string;
1538
+ value: NoInfer<Value>;
1539
+ choices: readonly SelectChoice<Value>[];
1540
+ className?: string;
1541
+ /** Empty-value hint text shown in the closed control (the native select placeholder). */
1542
+ placeholderText?: string;
1543
+ /** Supporting text shown below the control and associated with its combobox. */
1544
+ hint?: string;
1545
+ disabled?: boolean;
1546
+ fitMenu?: boolean;
1547
+ renderSelected?: (choice: SelectChoice<Value>) => SafeHtml;
1548
+ /** Render as an unanimated loading skeleton: the label above a static, empty control box. */
1549
+ placeholder?: boolean;
1550
+ /** Form (default), borderless toolbar, or intrinsic navigation chrome. */
1551
+ presentation?: SelectPresentation;
1552
+ size?: SelectSize;
1553
+ /** Show only the selected choice icon while retaining the Select's accessible name. */
1554
+ selectedPresentation?: SelectSelectedPresentation;
1555
+ /** Let an enclosing ToolbarControlGroup paint the composed focus ring. */
1556
+ focusRingOwner?: SelectFocusRingOwner;
1557
+ /** Maximum closed-control label width in CSS pixels before ellipsis. */
1558
+ labelMaxWidth?: number;
1559
+ }
1560
+ type SelectProps<Value extends string = string> = SelectBaseProps<Value> & SelectAccessibleName;
1561
+ declare function Select<Value extends string>({ name, value, label, ariaLabel, choices, className, placeholderText, hint, disabled, fitMenu, renderSelected, placeholder, presentation, size, selectedPresentation, focusRingOwner, labelMaxWidth, }: SelectProps<Value>): SafeHtml;
1562
+
1563
+ export { Select, type SelectChoice, type SelectFocusRingOwner, type SelectPresentation, type SelectProps, type SelectSelectedPresentation, type SelectSize };
1564
+ ```
1565
+
427
1566
  ## `@kerfjs/ui/state-banner`
428
1567
 
429
1568
  ```ts
430
1569
  import { SafeHtml } from 'kerfjs';
1570
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
431
1571
 
432
- type StateBannerTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
1572
+ type StateBannerTone = 'neutral' | 'info' | 'pop' | 'success' | 'warning' | 'danger';
1573
+ type StateBannerUrgency = 'status' | 'alert';
433
1574
  interface StateBannerProps {
434
1575
  title: string;
435
1576
  detail?: string;
1577
+ /** Optional compact status or count shown beside the title. */
1578
+ badge?: string;
436
1579
  icon?: SafeHtml;
437
- action?: SafeHtml;
1580
+ action?: KerfUiContent;
438
1581
  tone?: StateBannerTone;
439
- urgency?: 'status' | 'alert';
1582
+ urgency?: StateBannerUrgency;
440
1583
  className?: string;
1584
+ /** Render the title, badge, and detail as unanimated loading skeletons, keeping the icon and tone. */
1585
+ placeholder?: boolean;
441
1586
  }
442
- declare function StateBanner({ title, detail, icon, action, tone, urgency, className }: StateBannerProps): SafeHtml;
1587
+ declare function StateBanner({ title, detail, badge, icon, action, tone, urgency, className, placeholder, }: StateBannerProps): SafeHtml;
443
1588
 
444
- export { StateBanner, type StateBannerProps, type StateBannerTone };
1589
+ export { StateBanner, type StateBannerProps, type StateBannerTone, type StateBannerUrgency };
445
1590
  ```
446
1591
 
447
1592
  ## `@kerfjs/ui/empty-state`
448
1593
 
449
1594
  ```ts
450
1595
  import { SafeHtml } from 'kerfjs';
1596
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
451
1597
 
452
1598
  interface EmptyStateProps {
453
1599
  title: string;
454
1600
  detail?: string;
455
1601
  icon?: SafeHtml;
456
- action?: SafeHtml;
1602
+ action?: KerfUiContent;
457
1603
  busy?: boolean;
458
1604
  className?: string;
459
1605
  }
460
- declare function EmptyState({ title, detail, icon, action, busy, className }: EmptyStateProps): SafeHtml;
1606
+ declare function EmptyState({ title, detail, icon, action, busy, className, }: EmptyStateProps): SafeHtml;
461
1607
 
462
1608
  export { EmptyState, type EmptyStateProps };
463
1609
  ```
@@ -477,10 +1623,66 @@ declare function LoadingSpinner({ className, label }: LoadingSpinnerProps): kerf
477
1623
  export { LoadingSpinner, type LoadingSpinnerProps };
478
1624
  ```
479
1625
 
1626
+ ## `@kerfjs/ui/skeleton`
1627
+
1628
+ ```ts
1629
+ import * as kerfjs from 'kerfjs';
1630
+ import { CssSize, CssLength } from './css-values.js';
1631
+
1632
+ interface SkeletonProps {
1633
+ /** Typed width or intrinsic sizing keyword. Defaults to filling its slot. */
1634
+ width?: CssSize;
1635
+ /** Typed height or intrinsic sizing keyword. Defaults to a single text line. */
1636
+ height?: CssSize;
1637
+ /** Typed corner-radius override. Defaults to the small radius token. */
1638
+ radius?: CssLength;
1639
+ /** Render this many stacked lines (the last one shorter), for multi-line text. */
1640
+ lines?: number;
1641
+ /** Accessible label. Omit to keep the block decorative (`aria-hidden`). */
1642
+ label?: string;
1643
+ className?: string;
1644
+ }
1645
+ /**
1646
+ * A subtle, deliberately **unanimated** loading placeholder block. Use it for a
1647
+ * value slot whose content is not yet known, on its own or via a component's
1648
+ * `placeholder` prop. Decorative by default (`aria-hidden`); pass `label` to
1649
+ * announce it. Sizes to its slot unless `width`/`height` are given.
1650
+ */
1651
+ declare function Skeleton({ width, height, radius, lines, label, className, }: SkeletonProps): kerfjs.SafeHtml;
1652
+
1653
+ export { Skeleton, type SkeletonProps };
1654
+ ```
1655
+
1656
+ ## `@kerfjs/ui/sunken-panel`
1657
+
1658
+ ```ts
1659
+ import * as kerfjs from 'kerfjs';
1660
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1661
+
1662
+ type SunkenPanelShape = 'rounded' | 'square';
1663
+ interface SunkenPanelProps {
1664
+ children?: KerfUiContent;
1665
+ /** Optional accessible landmark name for a distinct application region. */
1666
+ ariaLabel?: string;
1667
+ /** Corner shape: a rounded rectangle (default) or square corners. */
1668
+ shape?: SunkenPanelShape;
1669
+ className?: string;
1670
+ }
1671
+ /**
1672
+ * A lowered application surface with one compact inset and a vertical content
1673
+ * stack. The panel owns its background and padding; children own their own
1674
+ * borders and internal geometry.
1675
+ */
1676
+ declare function SunkenPanel({ children, ariaLabel, shape, className, }: SunkenPanelProps): kerfjs.SafeHtml;
1677
+
1678
+ export { SunkenPanel, type SunkenPanelProps, type SunkenPanelShape };
1679
+ ```
1680
+
480
1681
  ## `@kerfjs/ui/token-search-field`
481
1682
 
482
1683
  ```ts
483
- import { SafeHtml } from 'kerfjs';
1684
+ import * as kerfjs from 'kerfjs';
1685
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
484
1686
 
485
1687
  interface TokenSearchToken {
486
1688
  value: string;
@@ -488,7 +1690,15 @@ interface TokenSearchToken {
488
1690
  offset?: number;
489
1691
  accessibleLabel?: string;
490
1692
  }
491
- interface TokenSearchFieldProps {
1693
+ type TokenSearchEditorAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
1694
+ 'data-component'?: never;
1695
+ 'data-key'?: never;
1696
+ 'data-morph-skip'?: never;
1697
+ 'data-token-search-editor'?: never;
1698
+ 'data-token-count'?: never;
1699
+ 'data-placeholder'?: never;
1700
+ }>;
1701
+ interface TokenSearchFieldBaseProps {
492
1702
  id: string;
493
1703
  label: string;
494
1704
  query?: string;
@@ -497,48 +1707,156 @@ interface TokenSearchFieldProps {
497
1707
  tokenPlaceholder?: string;
498
1708
  disabled?: boolean;
499
1709
  autofocus?: boolean;
500
- /** Allow an empty field to render as one iconic action. */
501
- collapsible?: boolean;
502
- /** Keep an empty collapsible field open while the application owns focus. */
503
- expanded?: boolean;
504
- expandAction?: string;
505
- expandLabel?: string;
506
- leading?: SafeHtml;
507
- trailing?: SafeHtml;
1710
+ leading?: KerfUiContent;
1711
+ trailing?: KerfUiContent;
508
1712
  editAction?: string;
509
1713
  removeAction?: string;
510
1714
  clearAction?: string;
511
1715
  clearLabel?: string;
512
1716
  className?: string;
513
- editorAttributes?: Readonly<Record<`data-${string}`, string>>;
1717
+ editorAttributes?: TokenSearchEditorAttributes;
514
1718
  }
1719
+ type TokenSearchCollapsibleProps = {
1720
+ /** Allow an empty field to render as one iconic action. */
1721
+ collapsible: true;
1722
+ /** Keep an empty collapsible field open while the application owns focus. */
1723
+ expanded?: boolean;
1724
+ expandAction?: string;
1725
+ expandLabel?: string;
1726
+ } | {
1727
+ collapsible?: false;
1728
+ expanded?: never;
1729
+ expandAction?: never;
1730
+ expandLabel?: never;
1731
+ };
1732
+ type TokenSearchFieldProps = TokenSearchFieldBaseProps & TokenSearchCollapsibleProps;
515
1733
  interface TokenSearchFieldValue {
516
1734
  query: string;
517
1735
  tokens: TokenSearchToken[];
518
1736
  }
519
- declare function TokenSearchField({ id, label, query, tokens, placeholder, tokenPlaceholder, disabled, autofocus, collapsible, expanded, expandAction, expandLabel, leading, trailing, editAction, removeAction, clearAction, clearLabel, className, editorAttributes, }: TokenSearchFieldProps): SafeHtml;
1737
+ declare function TokenSearchField({ id, label, query, tokens, placeholder, tokenPlaceholder, disabled, autofocus, collapsible, expanded, expandAction, expandLabel, leading, trailing, editAction, removeAction, clearAction, clearLabel, className, editorAttributes, }: TokenSearchFieldProps): kerfjs.SafeHtml;
520
1738
  /** Read editable text and ordered token offsets from a rendered TokenSearchField editor. */
521
1739
  declare function readTokenSearchField(editor: HTMLElement, knownTokens?: readonly TokenSearchToken[]): TokenSearchFieldValue;
522
1740
  /** Focus an editor and place its caret at a text offset, skipping atomic token chips. */
523
1741
  declare function placeTokenSearchCaret(editor: HTMLElement, offset?: number): void;
524
1742
 
525
- export { TokenSearchField, type TokenSearchFieldProps, type TokenSearchFieldValue, type TokenSearchToken, placeTokenSearchCaret, readTokenSearchField };
1743
+ export { type TokenSearchEditorAttributes, TokenSearchField, type TokenSearchFieldProps, type TokenSearchFieldValue, type TokenSearchToken, placeTokenSearchCaret, readTokenSearchField };
526
1744
  ```
527
1745
 
528
1746
  ## `@kerfjs/ui/wire-token-search-fields`
529
1747
 
530
1748
  ```ts
1749
+ import { Signal } from 'kerfjs';
1750
+
531
1751
  interface TokenSearchSubmit {
532
1752
  id: string;
533
1753
  editor: HTMLElement;
534
1754
  }
1755
+ /** Reported to {@link WireTokenSearchFieldsOptions.onEdit} on every editor input. */
1756
+ interface TokenSearchEdit extends TokenSearchSubmit {
1757
+ /**
1758
+ * The `InputEvent` that mutated the editor — read `event.inputType` / `event.data`
1759
+ * to gate commit behavior (e.g. only parse a chip on whitespace-terminated input)
1760
+ * without keeping a separate `input` listener.
1761
+ */
1762
+ event: InputEvent;
1763
+ }
1764
+ /** Reported when adjacent-token keyboard deletion asks the app to drop a chip. */
1765
+ interface TokenSearchTokenRemoval {
1766
+ id: string;
1767
+ /** The `data-token-value` of the token the app should remove from its state. */
1768
+ value: string;
1769
+ editor: HTMLElement;
1770
+ /** `'backward'` = the token before the caret (Backspace); `'forward'` = after (Delete). */
1771
+ direction: 'backward' | 'forward';
1772
+ }
1773
+ /**
1774
+ * Opt-in keyboard behavior for the atomic token chips. Off unless `keyboard` is
1775
+ * set; each piece defaults on once opted in. The helper never mutates app state:
1776
+ * a removal is reported through {@link TokenSearchKeyboardOptions.onRemoveToken}
1777
+ * for the caller to apply, while caret movement past a chip is a pure ephemeral
1778
+ * mechanic the helper performs itself.
1779
+ */
1780
+ interface TokenSearchKeyboardBaseOptions {
1781
+ /**
1782
+ * From a collapsed caret with no selection, Backspace removes the token
1783
+ * immediately before it and Delete the token immediately after — reported via
1784
+ * `onRemoveToken` — instead of deleting a character. Default: true.
1785
+ */
1786
+ /**
1787
+ * ArrowRight moves the caret past a trailing atomic token so text typed next
1788
+ * lands after the chip. Default: true.
1789
+ */
1790
+ moveCaretPastToken?: boolean;
1791
+ }
1792
+ type TokenSearchKeyboardOptions = TokenSearchKeyboardBaseOptions & ({
1793
+ removeAdjacentToken?: true;
1794
+ /** Apply the reported removal to your controlled state, then re-render. */
1795
+ onRemoveToken: (removal: TokenSearchTokenRemoval) => void;
1796
+ } | {
1797
+ removeAdjacentToken: false;
1798
+ onRemoveToken?: never;
1799
+ });
1800
+ /**
1801
+ * Managed collapsible behavior for the iconic TokenSearchField. Every piece is on
1802
+ * by default; disable a specific one to own it in the app. Provide `signals` to
1803
+ * drive app-owned `expanded` signals per field id instead of helper-created ones.
1804
+ */
1805
+ interface TokenSearchCollapsibleOptions {
1806
+ /** Expand the field and focus its editor when the iconic trigger is activated. Default: true. */
1807
+ expandOnActivate?: boolean;
1808
+ /** Collapse the field when focus leaves it while it is empty. Default: true. */
1809
+ collapseOnEmptyBlur?: boolean;
1810
+ /** Collapse an empty field on Escape and restore focus to its trigger. Default: true. */
1811
+ collapseOnEscape?: boolean;
1812
+ /** Focus the editor on expand/controlled clear and the trigger on Escape-collapse. Default: true. */
1813
+ manageFocus?: boolean;
1814
+ /**
1815
+ * Keep an empty field expanded when focus moves to a caller-owned surface
1816
+ * rendered outside the field — a suggestions dropdown, date picker, or help
1817
+ * popover shown beside it. Return true for any focus target that must NOT
1818
+ * trigger collapse-on-empty-blur. An element carrying `data-token-search-keep-open`
1819
+ * (or any node inside one) is always exempt, so this predicate is only needed
1820
+ * for surfaces you cannot mark declaratively.
1821
+ */
1822
+ keepOpenOn?: (target: Node | null) => boolean;
1823
+ /** App-owned `expanded` signals keyed by field id; adopted instead of helper-created. */
1824
+ signals?: Readonly<Record<string, Signal<boolean>>>;
1825
+ }
535
1826
  interface WireTokenSearchFieldsOptions {
536
- onSubmit: (submission: TokenSearchSubmit) => void;
1827
+ onSubmit?: (submission: TokenSearchSubmit) => void;
1828
+ /** Fired on every editor `input`, after the browser mutates it, so a caller can drop its own `input` listener. */
1829
+ onEdit?: (edit: TokenSearchEdit) => void;
1830
+ /** Managed collapsible transient behavior. `true`/omitted = on with defaults; `false` = fully off. */
1831
+ collapsible?: boolean | TokenSearchCollapsibleOptions;
1832
+ /** Opt-in atomic-chip keyboard behavior (off by default). */
1833
+ keyboard?: false | TokenSearchKeyboardOptions;
1834
+ }
1835
+ /**
1836
+ * The value returned from {@link wireTokenSearchFields}: call it (or `dispose()`) to
1837
+ * tear down. When collapsible behavior is managed, it also exposes the transient
1838
+ * `expanded` state per field id so the app can read it in render, hand in its own
1839
+ * signal, or drive it imperatively.
1840
+ */
1841
+ interface TokenSearchFieldsHandle {
1842
+ (): void;
1843
+ dispose(): void;
1844
+ /** The managed `expanded` signal for a field id (adopted or helper-created); undefined when unmanaged. */
1845
+ expanded(id: string): Signal<boolean> | undefined;
1846
+ /** Expand the field (and, when focus is managed, focus its editor). */
1847
+ open(id: string): void;
1848
+ /** Collapse the field (and, when focus is managed, restore focus to its trigger). */
1849
+ close(id: string): void;
537
1850
  }
538
- /** Keep TokenSearchField wrapping, submit Enter, and preserve its caret across controlled token deletion. */
539
- declare function wireTokenSearchFields(root: HTMLElement, { onSubmit }: WireTokenSearchFieldsOptions): () => void;
1851
+ /**
1852
+ * Wire every TokenSearchField under `root`: submit on Enter, preserve the caret across
1853
+ * controlled token deletion, and (by default) manage the collapsible field's transient
1854
+ * expand/collapse/focus. Returns a {@link TokenSearchFieldsHandle} — a disposer that also
1855
+ * exposes the managed `expanded` state per field id.
1856
+ */
1857
+ declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard, }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
540
1858
 
541
- export { type TokenSearchSubmit, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
1859
+ export { type TokenSearchCollapsibleOptions, type TokenSearchEdit, type TokenSearchFieldsHandle, type TokenSearchKeyboardOptions, type TokenSearchSubmit, type TokenSearchTokenRemoval, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
542
1860
  ```
543
1861
 
544
1862
  ## `kerfjs/actions`