@kerfjs/ui 4.4.1 → 4.5.0-beta.1

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 (499) hide show
  1. package/README.md +425 -42
  2. package/ai/application-ui-diagnostic-ids-v1.json +92 -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 +102 -0
  12. package/ai/compile-time-contracts-v1.schema.json +49 -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 +319 -0
  17. package/ai/component-catalog-v2.d.ts +67 -0
  18. package/ai/component-catalog-v2.json +4675 -0
  19. package/ai/component-catalog-v2.schema.json +246 -0
  20. package/ai/component-catalog.json +5826 -0
  21. package/ai/component-catalog.schema.json +95 -0
  22. package/ai/public-api-signatures-v1.md +1719 -0
  23. package/ai/skill.md +129 -21
  24. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  25. package/analyzer/cli.mjs +46 -0
  26. package/analyzer/index.d.mts +45 -0
  27. package/analyzer/index.mjs +737 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +14 -2
  30. package/dist/app-tab.js +3 -1
  31. package/dist/browser/app-tab.js +3 -2
  32. package/dist/browser/disclosure-arrow.js +4 -0
  33. package/dist/browser/empty-state.js +3 -3
  34. package/dist/browser/floating-toolbar.js +3 -0
  35. package/dist/browser/list-action-row.js +4 -0
  36. package/dist/browser/list-header.js +6 -0
  37. package/dist/browser/list-inset-control.js +3 -0
  38. package/dist/browser/list-inset-text.js +3 -0
  39. package/dist/browser/list-item.js +4 -0
  40. package/dist/browser/list.js +3 -0
  41. package/dist/browser/loading-spinner.js +2 -2
  42. package/dist/browser/lucide-icon.js +2 -2
  43. package/dist/browser/pane.js +3 -0
  44. package/dist/browser/panel-header.js +7 -0
  45. package/dist/browser/resizable-region.js +2 -2
  46. package/dist/browser/segmented-control.js +3 -2
  47. package/dist/browser/select.js +4 -3
  48. package/dist/browser/skeleton.js +3 -0
  49. package/dist/browser/state-banner.js +3 -2
  50. package/dist/browser/sunken-panel.js +3 -0
  51. package/dist/browser/tab-bar.js +2 -2
  52. package/dist/browser/token-search-field.js +4 -0
  53. package/dist/browser/toolbar-control-group.js +2 -2
  54. package/dist/browser/toolbar-text.js +3 -2
  55. package/dist/browser/toolbar.js +2 -2
  56. package/dist/browser/value-table.js +3 -2
  57. package/dist/catalog-resources.d.ts +34 -0
  58. package/dist/catalog-resources.js +22 -0
  59. package/dist/catalog-resources.js.map +1 -0
  60. package/dist/catalog.d.ts +150 -0
  61. package/dist/catalog.js +377 -0
  62. package/dist/catalog.js.map +1 -0
  63. package/dist/chunk-5VTB2BKP.js +35 -0
  64. package/dist/chunk-5VTB2BKP.js.map +1 -0
  65. package/dist/chunk-6WTETUZ6.js +21 -0
  66. package/dist/chunk-6WTETUZ6.js.map +1 -0
  67. package/dist/chunk-7KEUJIIC.js +218 -0
  68. package/dist/chunk-7KEUJIIC.js.map +1 -0
  69. package/dist/chunk-7XRDUG4Y.js +58 -0
  70. package/dist/chunk-7XRDUG4Y.js.map +1 -0
  71. package/dist/chunk-B3A3IVO5.js +83 -0
  72. package/dist/chunk-B3A3IVO5.js.map +1 -0
  73. package/dist/chunk-DPDBVKNA.js +113 -0
  74. package/dist/chunk-DPDBVKNA.js.map +1 -0
  75. package/dist/chunk-FEAMR7S4.js +68 -0
  76. package/dist/chunk-FEAMR7S4.js.map +1 -0
  77. package/dist/chunk-FEY65TBV.js +15 -0
  78. package/dist/chunk-FEY65TBV.js.map +1 -0
  79. package/dist/{chunk-TRNBHPNW.js → chunk-IJBSZ4NX.js} +11 -8
  80. package/dist/chunk-IJBSZ4NX.js.map +1 -0
  81. package/dist/chunk-J5BFYY7Q.js +35 -0
  82. package/dist/chunk-J5BFYY7Q.js.map +1 -0
  83. package/dist/chunk-JRKSK2HX.js +79 -0
  84. package/dist/chunk-JRKSK2HX.js.map +1 -0
  85. package/dist/chunk-KWWR5VMS.js +45 -0
  86. package/dist/chunk-KWWR5VMS.js.map +1 -0
  87. package/dist/chunk-LPOMRC4C.js +25 -0
  88. package/dist/chunk-LPOMRC4C.js.map +1 -0
  89. package/dist/chunk-M7JBER5U.js +41 -0
  90. package/dist/chunk-M7JBER5U.js.map +1 -0
  91. package/dist/chunk-NENBUYX5.js +79 -0
  92. package/dist/chunk-NENBUYX5.js.map +1 -0
  93. package/dist/chunk-NWSEP5RT.js +117 -0
  94. package/dist/chunk-NWSEP5RT.js.map +1 -0
  95. package/dist/chunk-OJWWHFH3.js +426 -0
  96. package/dist/chunk-OJWWHFH3.js.map +1 -0
  97. package/dist/chunk-PSKN344N.js +114 -0
  98. package/dist/chunk-PSKN344N.js.map +1 -0
  99. package/dist/chunk-PX3YSS5C.js +32 -0
  100. package/dist/chunk-PX3YSS5C.js.map +1 -0
  101. package/dist/chunk-QO55FN2Y.js +20 -0
  102. package/dist/chunk-QO55FN2Y.js.map +1 -0
  103. package/dist/chunk-R7ZWUN64.js +38 -0
  104. package/dist/chunk-R7ZWUN64.js.map +1 -0
  105. package/dist/chunk-RKVQEH4J.js +61 -0
  106. package/dist/chunk-RKVQEH4J.js.map +1 -0
  107. package/dist/chunk-SRSJO5QE.js +40 -0
  108. package/dist/chunk-SRSJO5QE.js.map +1 -0
  109. package/dist/chunk-SYDMYBPG.js +31 -0
  110. package/dist/chunk-SYDMYBPG.js.map +1 -0
  111. package/dist/chunk-TB6DY7H5.js +25 -0
  112. package/dist/chunk-TB6DY7H5.js.map +1 -0
  113. package/dist/chunk-UFZKLBCF.js +108 -0
  114. package/dist/chunk-UFZKLBCF.js.map +1 -0
  115. package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
  116. package/dist/chunk-UKKSMZYT.js.map +1 -0
  117. package/dist/chunk-W5L2JSBY.js +40 -0
  118. package/dist/chunk-W5L2JSBY.js.map +1 -0
  119. package/dist/chunk-Y45PPBDV.js +105 -0
  120. package/dist/chunk-Y45PPBDV.js.map +1 -0
  121. package/dist/chunk-Y6LYKFXY.js +43 -0
  122. package/dist/chunk-Y6LYKFXY.js.map +1 -0
  123. package/dist/collapsible-panel.d.ts +66 -0
  124. package/dist/collapsible-panel.js +63 -0
  125. package/dist/collapsible-panel.js.map +1 -0
  126. package/dist/device-class.d.ts +62 -0
  127. package/dist/device-class.js +103 -0
  128. package/dist/device-class.js.map +1 -0
  129. package/dist/disclosure-arrow.d.ts +14 -0
  130. package/dist/disclosure-arrow.js +4 -0
  131. package/dist/disclosure-arrow.js.map +1 -0
  132. package/dist/divider-sides-267FA7sY.d.ts +4 -0
  133. package/dist/empty-state.d.ts +1 -1
  134. package/dist/empty-state.js +1 -1
  135. package/dist/floating-toolbar.d.ts +28 -0
  136. package/dist/floating-toolbar.js +3 -0
  137. package/dist/floating-toolbar.js.map +1 -0
  138. package/dist/index.d.ts +20 -9
  139. package/dist/index.js +29 -18
  140. package/dist/list-action-row.d.ts +52 -0
  141. package/dist/list-action-row.js +5 -0
  142. package/dist/list-action-row.js.map +1 -0
  143. package/dist/list-header.d.ts +64 -0
  144. package/dist/list-header.js +7 -0
  145. package/dist/list-header.js.map +1 -0
  146. package/dist/list-inset-control.d.ts +16 -0
  147. package/dist/list-inset-control.js +3 -0
  148. package/dist/list-inset-control.js.map +1 -0
  149. package/dist/list-inset-text.d.ts +25 -0
  150. package/dist/list-inset-text.js +3 -0
  151. package/dist/list-inset-text.js.map +1 -0
  152. package/dist/list-item.d.ts +33 -0
  153. package/dist/list-item.js +5 -0
  154. package/dist/list-item.js.map +1 -0
  155. package/dist/list.d.ts +19 -0
  156. package/dist/list.js +3 -0
  157. package/dist/list.js.map +1 -0
  158. package/dist/loading-spinner.js +1 -1
  159. package/dist/nav-stack.d.ts +38 -0
  160. package/dist/nav-stack.js +5 -0
  161. package/dist/nav-stack.js.map +1 -0
  162. package/dist/pane.d.ts +49 -0
  163. package/dist/pane.js +4 -0
  164. package/dist/pane.js.map +1 -0
  165. package/dist/panel-header.d.ts +42 -0
  166. package/dist/panel-header.js +7 -0
  167. package/dist/panel-header.js.map +1 -0
  168. package/dist/resizable-region.d.ts +3 -1
  169. package/dist/resizable-region.js +1 -1
  170. package/dist/segmented-control.d.ts +8 -6
  171. package/dist/segmented-control.js +2 -1
  172. package/dist/select-register.d.ts +5 -1
  173. package/dist/select-register.js +3 -0
  174. package/dist/select-register.js.map +1 -1
  175. package/dist/select.d.ts +15 -6
  176. package/dist/select.js +2 -1
  177. package/dist/skeleton.d.ts +24 -0
  178. package/dist/skeleton.js +3 -0
  179. package/dist/skeleton.js.map +1 -0
  180. package/dist/split-view.d.ts +42 -0
  181. package/dist/split-view.js +81 -0
  182. package/dist/split-view.js.map +1 -0
  183. package/dist/state-banner.d.ts +8 -3
  184. package/dist/state-banner.js +2 -1
  185. package/dist/styles/app-tab.css +152 -0
  186. package/dist/styles/catalog.css +575 -0
  187. package/dist/styles/collapsible-panel.css +146 -0
  188. package/dist/styles/disclosure-arrow.css +23 -0
  189. package/dist/styles/empty-state.css +47 -0
  190. package/dist/styles/floating-toolbar.css +38 -0
  191. package/dist/styles/foundation.css +362 -0
  192. package/dist/styles/layout.css +55 -0
  193. package/dist/styles/list-action-row.css +160 -0
  194. package/dist/styles/list-header.css +160 -0
  195. package/dist/styles/list-inset-control.css +7 -0
  196. package/dist/styles/list-inset-text.css +14 -0
  197. package/dist/styles/list-item.css +123 -0
  198. package/dist/styles/list.css +43 -0
  199. package/dist/styles/loading-spinner.css +21 -0
  200. package/dist/styles/lucide-icon.css +6 -0
  201. package/dist/styles/nav-stack.css +111 -0
  202. package/dist/styles/pane.css +62 -0
  203. package/dist/styles/panel-header.css +64 -0
  204. package/dist/styles/resizable-region.css +111 -0
  205. package/{src → dist/styles}/segmented-control.css +58 -31
  206. package/dist/styles/select.css +140 -0
  207. package/dist/styles/skeleton.css +31 -0
  208. package/dist/styles/split-view.css +35 -0
  209. package/dist/styles/state-banner.css +198 -0
  210. package/dist/styles/styles.css +24 -0
  211. package/dist/styles/sunken-panel.css +22 -0
  212. package/dist/styles/tab-bar.css +45 -0
  213. package/dist/styles/tab-scaffold.css +84 -0
  214. package/dist/styles/token-search-field.css +289 -0
  215. package/dist/styles/toolbar-control-group.css +225 -0
  216. package/dist/styles/toolbar-text.css +92 -0
  217. package/dist/styles/toolbar.css +97 -0
  218. package/dist/styles/value-table.css +75 -0
  219. package/dist/styles/webawesome.css +399 -0
  220. package/dist/styles/workbench.css +94 -0
  221. package/dist/sunken-panel.d.ts +19 -0
  222. package/dist/sunken-panel.js +3 -0
  223. package/dist/sunken-panel.js.map +1 -0
  224. package/dist/tab-bar.d.ts +10 -2
  225. package/dist/tab-bar.js +1 -1
  226. package/dist/tab-scaffold.d.ts +30 -0
  227. package/dist/tab-scaffold.js +51 -0
  228. package/dist/tab-scaffold.js.map +1 -0
  229. package/dist/token-search-field.d.ts +59 -0
  230. package/dist/token-search-field.js +4 -0
  231. package/dist/token-search-field.js.map +1 -0
  232. package/dist/toolbar-control-group.d.ts +11 -5
  233. package/dist/toolbar-control-group.js +1 -1
  234. package/dist/toolbar-text.d.ts +31 -4
  235. package/dist/toolbar-text.js +2 -1
  236. package/dist/toolbar.d.ts +5 -3
  237. package/dist/toolbar.js +1 -1
  238. package/dist/value-table.d.ts +13 -3
  239. package/dist/value-table.js +2 -1
  240. package/dist/webawesome.d.ts +100 -0
  241. package/dist/webawesome.js +3 -0
  242. package/dist/webawesome.js.map +1 -0
  243. package/dist/wire-catalog.d.ts +57 -0
  244. package/dist/wire-catalog.js +302 -0
  245. package/dist/wire-catalog.js.map +1 -0
  246. package/dist/wire-nav-stack.d.ts +15 -0
  247. package/dist/wire-nav-stack.js +103 -0
  248. package/dist/wire-nav-stack.js.map +1 -0
  249. package/dist/wire-resizable-regions.d.ts +1 -1
  250. package/dist/wire-resizable-regions.js +1 -1
  251. package/dist/wire-sidebar.d.ts +43 -0
  252. package/dist/wire-sidebar.js +128 -0
  253. package/dist/wire-sidebar.js.map +1 -0
  254. package/dist/wire-tab-bars.d.ts +18 -2
  255. package/dist/wire-tab-bars.js +1 -1
  256. package/dist/wire-tab-scaffold.d.ts +11 -0
  257. package/dist/wire-tab-scaffold.js +21 -0
  258. package/dist/wire-tab-scaffold.js.map +1 -0
  259. package/dist/wire-token-search-fields.d.ts +111 -0
  260. package/dist/wire-token-search-fields.js +5 -0
  261. package/dist/wire-token-search-fields.js.map +1 -0
  262. package/dist/workbench.d.ts +33 -0
  263. package/dist/workbench.js +64 -0
  264. package/dist/workbench.js.map +1 -0
  265. package/docs/accessibility.md +124 -4
  266. package/docs/app-layouts.md +61 -0
  267. package/docs/catalog.md +364 -0
  268. package/docs/collapsible-panel.md +129 -0
  269. package/docs/component-contract.md +312 -5
  270. package/docs/component-selection.md +219 -0
  271. package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
  272. package/docs/design/templates/empty-state/filtered.svg +1 -0
  273. package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
  274. package/docs/design/templates/empty-state/with-action.svg +1 -0
  275. package/docs/design/templates/empty-state-dark.svg +1 -0
  276. package/docs/design/templates/empty-state.svg +1 -0
  277. package/docs/design/templates/list/compact-dark.svg +1 -0
  278. package/docs/design/templates/list/compact.svg +1 -0
  279. package/docs/design/templates/list/stack-dark.svg +1 -0
  280. package/docs/design/templates/list/stack.svg +1 -0
  281. package/docs/design/templates/list-action-row/default-dark.svg +1 -0
  282. package/docs/design/templates/list-action-row/default.svg +1 -0
  283. package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
  284. package/docs/design/templates/list-action-row/selected.svg +1 -0
  285. package/docs/design/templates/list-action-row-dark.svg +1 -0
  286. package/docs/design/templates/list-action-row.svg +1 -0
  287. package/docs/design/templates/list-dark.svg +1 -0
  288. package/docs/design/templates/list-header/action-dark.svg +1 -0
  289. package/docs/design/templates/list-header/action.svg +1 -0
  290. package/docs/design/templates/list-header/count-dark.svg +1 -0
  291. package/docs/design/templates/list-header/count.svg +1 -0
  292. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
  293. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
  294. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
  295. package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
  296. package/docs/design/templates/list-header-dark.svg +1 -0
  297. package/docs/design/templates/list-header.svg +1 -0
  298. package/docs/design/templates/list-item/default-dark.svg +1 -0
  299. package/docs/design/templates/list-item/default.svg +1 -0
  300. package/docs/design/templates/list-item/multiline-dark.svg +1 -0
  301. package/docs/design/templates/list-item/multiline.svg +1 -0
  302. package/docs/design/templates/list-item/selected-dark.svg +1 -0
  303. package/docs/design/templates/list-item/selected.svg +1 -0
  304. package/docs/design/templates/list-item/trailing-dark.svg +1 -0
  305. package/docs/design/templates/list-item/trailing.svg +1 -0
  306. package/docs/design/templates/list-item-dark.svg +1 -0
  307. package/docs/design/templates/list-item.svg +1 -0
  308. package/docs/design/templates/list.svg +1 -0
  309. package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
  310. package/docs/design/templates/panel-header/icon-actions.svg +1 -0
  311. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
  312. package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
  313. package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
  314. package/docs/design/templates/panel-header/no-icon.svg +1 -0
  315. package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
  316. package/docs/design/templates/panel-header/page-heading.svg +1 -0
  317. package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
  318. package/docs/design/templates/panel-header/title-only.svg +1 -0
  319. package/docs/design/templates/panel-header-dark.svg +1 -0
  320. package/docs/design/templates/panel-header.svg +1 -0
  321. package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
  322. package/docs/design/templates/segmented-control/equal.svg +1 -0
  323. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
  324. package/docs/design/templates/segmented-control/pill-small.svg +1 -0
  325. package/docs/design/templates/segmented-control-dark.svg +1 -0
  326. package/docs/design/templates/segmented-control.svg +1 -0
  327. package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
  328. package/docs/design/templates/skeleton/avatar.svg +1 -0
  329. package/docs/design/templates/skeleton/block-dark.svg +1 -0
  330. package/docs/design/templates/skeleton/block.svg +1 -0
  331. package/docs/design/templates/skeleton/lines-dark.svg +1 -0
  332. package/docs/design/templates/skeleton/lines.svg +1 -0
  333. package/docs/design/templates/skeleton-dark.svg +1 -0
  334. package/docs/design/templates/skeleton.svg +1 -0
  335. package/docs/design/templates/state-banner/danger-dark.svg +1 -0
  336. package/docs/design/templates/state-banner/danger.svg +1 -0
  337. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
  338. package/docs/design/templates/state-banner/info-badge.svg +1 -0
  339. package/docs/design/templates/state-banner/info-dark.svg +1 -0
  340. package/docs/design/templates/state-banner/info.svg +1 -0
  341. package/docs/design/templates/state-banner/success-dark.svg +1 -0
  342. package/docs/design/templates/state-banner/success.svg +1 -0
  343. package/docs/design/templates/state-banner/warning-dark.svg +1 -0
  344. package/docs/design/templates/state-banner/warning.svg +1 -0
  345. package/docs/design/templates/state-banner-dark.svg +1 -0
  346. package/docs/design/templates/state-banner.svg +1 -0
  347. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
  348. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
  349. package/docs/design/templates/tab-bar-dark.svg +1 -0
  350. package/docs/design/templates/tab-bar.svg +1 -0
  351. package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
  352. package/docs/design/templates/token-search-field/empty.svg +1 -0
  353. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
  354. package/docs/design/templates/token-search-field/tokens.svg +1 -0
  355. package/docs/design/templates/token-search-field-dark.svg +1 -0
  356. package/docs/design/templates/token-search-field.svg +1 -0
  357. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
  358. package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
  359. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  360. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  361. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  362. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  363. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  364. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  365. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  366. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  367. package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
  368. package/docs/design/templates/toolbar-control-group.svg +1 -0
  369. package/docs/design/templates/toolbar-dark.svg +1 -0
  370. package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
  371. package/docs/design/templates/toolbar-text/default.svg +1 -0
  372. package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
  373. package/docs/design/templates/toolbar-text/large.svg +1 -0
  374. package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
  375. package/docs/design/templates/toolbar-text/small.svg +1 -0
  376. package/docs/design/templates/toolbar-text-dark.svg +1 -0
  377. package/docs/design/templates/toolbar-text.svg +1 -0
  378. package/docs/design/templates/toolbar.svg +1 -0
  379. package/docs/design/templates/value-table/details-dark.svg +1 -0
  380. package/docs/design/templates/value-table/details.svg +1 -0
  381. package/docs/design/templates/value-table-dark.svg +1 -0
  382. package/docs/design/templates/value-table.svg +1 -0
  383. package/docs/design/templates.md +130 -0
  384. package/docs/design-philosophy.md +59 -1
  385. package/docs/device-class.md +54 -0
  386. package/docs/examples/application-ui-profile.json +60 -0
  387. package/docs/examples/command-palette-adapter.tsx +120 -0
  388. package/docs/examples/component-catalog-extension-v2.json +81 -0
  389. package/docs/examples/component-catalog-extension.json +45 -0
  390. package/docs/layout.md +150 -0
  391. package/docs/nav-stack.md +56 -0
  392. package/docs/recipes.md +167 -0
  393. package/docs/split-view.md +49 -0
  394. package/docs/sunken-panel.md +53 -0
  395. package/docs/tab-scaffold.md +50 -0
  396. package/docs/type-contracts.md +102 -0
  397. package/docs/ui-analyzer.md +86 -0
  398. package/docs/ui-doctor.md +84 -0
  399. package/docs/ui-evaluator.md +122 -0
  400. package/docs/ux-demo.md +23 -9
  401. package/docs/webawesome-theme.md +87 -7
  402. package/docs/workbench.md +73 -0
  403. package/doctor/cli.mjs +115 -0
  404. package/doctor/config.schema.json +68 -0
  405. package/doctor/eslint-diagnostics.d.mts +8 -0
  406. package/doctor/eslint-diagnostics.mjs +7 -0
  407. package/doctor/index.d.mts +116 -0
  408. package/doctor/index.mjs +1257 -0
  409. package/doctor/report.schema.json +118 -0
  410. package/evaluator/cli.mjs +89 -0
  411. package/evaluator/index.d.mts +90 -0
  412. package/evaluator/index.mjs +1138 -0
  413. package/evaluator/report.schema.json +152 -0
  414. package/llms.txt +197 -14
  415. package/package.json +228 -46
  416. package/traversal-exclusions.mjs +27 -0
  417. package/ux-demo/recipes/app-shell.tsx +228 -0
  418. package/ux-demo/recipes/collapsible-sidebar.tsx +223 -0
  419. package/ux-demo/recipes/compact-toolbar.tsx +145 -0
  420. package/ux-demo/recipes/composer-form.tsx +151 -0
  421. package/ux-demo/recipes/list-detail-dialog.tsx +181 -0
  422. package/ux-demo/recipes/list-workspace-states.tsx +183 -0
  423. package/ux-demo/recipes/loaders.ts +36 -0
  424. package/ux-demo/recipes/loading-inspector.tsx +164 -0
  425. package/ux-demo/recipes/mount-recipe.ts +64 -0
  426. package/ux-demo/recipes/navigation-sidebar.tsx +137 -0
  427. package/ux-demo/recipes/navigation-stack.tsx +118 -0
  428. package/ux-demo/recipes/recipes.css +448 -0
  429. package/ux-demo/recipes/types.ts +19 -0
  430. package/ux-demo/recipes/workspace-header.tsx +67 -0
  431. package/dist/browser/dialog-header.js +0 -3
  432. package/dist/browser/menu-header.js +0 -3
  433. package/dist/browser/menu-item.js +0 -3
  434. package/dist/browser/page-header.js +0 -3
  435. package/dist/chunk-2PES33HS.js +0 -13
  436. package/dist/chunk-2PES33HS.js.map +0 -1
  437. package/dist/chunk-3O7ZEUW2.js +0 -27
  438. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  439. package/dist/chunk-4HHCYSGT.js +0 -79
  440. package/dist/chunk-4HHCYSGT.js.map +0 -1
  441. package/dist/chunk-55DATV3A.js +0 -10
  442. package/dist/chunk-55DATV3A.js.map +0 -1
  443. package/dist/chunk-723YNJBH.js +0 -17
  444. package/dist/chunk-723YNJBH.js.map +0 -1
  445. package/dist/chunk-DAFFDO33.js +0 -14
  446. package/dist/chunk-DAFFDO33.js.map +0 -1
  447. package/dist/chunk-GY5WH7TO.js +0 -10
  448. package/dist/chunk-GY5WH7TO.js.map +0 -1
  449. package/dist/chunk-MBR3TYNL.js +0 -14
  450. package/dist/chunk-MBR3TYNL.js.map +0 -1
  451. package/dist/chunk-MFKMHK5W.js +0 -10
  452. package/dist/chunk-MFKMHK5W.js.map +0 -1
  453. package/dist/chunk-MQIZAD4A.js.map +0 -1
  454. package/dist/chunk-NHXX4JI5.js +0 -28
  455. package/dist/chunk-NHXX4JI5.js.map +0 -1
  456. package/dist/chunk-OPCJRCYO.js +0 -19
  457. package/dist/chunk-OPCJRCYO.js.map +0 -1
  458. package/dist/chunk-T5XNMVOI.js +0 -24
  459. package/dist/chunk-T5XNMVOI.js.map +0 -1
  460. package/dist/chunk-TRNBHPNW.js.map +0 -1
  461. package/dist/chunk-VHW5YNIB.js +0 -17
  462. package/dist/chunk-VHW5YNIB.js.map +0 -1
  463. package/dist/chunk-VKH2JLDZ.js +0 -10
  464. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  465. package/dist/chunk-WVPYVDXY.js +0 -15
  466. package/dist/chunk-WVPYVDXY.js.map +0 -1
  467. package/dist/chunk-WY4CAEPG.js +0 -14
  468. package/dist/chunk-WY4CAEPG.js.map +0 -1
  469. package/dist/dialog-header.d.ts +0 -14
  470. package/dist/dialog-header.js +0 -3
  471. package/dist/dialog-header.js.map +0 -1
  472. package/dist/menu-header.d.ts +0 -15
  473. package/dist/menu-header.js +0 -3
  474. package/dist/menu-header.js.map +0 -1
  475. package/dist/menu-item.d.ts +0 -22
  476. package/dist/menu-item.js +0 -3
  477. package/dist/menu-item.js.map +0 -1
  478. package/dist/page-header.d.ts +0 -9
  479. package/dist/page-header.js +0 -3
  480. package/dist/page-header.js.map +0 -1
  481. package/src/app-tab.css +0 -20
  482. package/src/dialog-header.css +0 -9
  483. package/src/empty-state.css +0 -6
  484. package/src/foundation.css +0 -106
  485. package/src/loading-spinner.css +0 -4
  486. package/src/lucide-icon.css +0 -1
  487. package/src/menu-header.css +0 -13
  488. package/src/menu-item.css +0 -12
  489. package/src/page-header.css +0 -3
  490. package/src/resizable-region.css +0 -19
  491. package/src/select.css +0 -18
  492. package/src/state-banner.css +0 -31
  493. package/src/styles.css +0 -18
  494. package/src/tab-bar.css +0 -4
  495. package/src/toolbar-control-group.css +0 -25
  496. package/src/toolbar-text.css +0 -4
  497. package/src/toolbar.css +0 -9
  498. package/src/value-table.css +0 -5
  499. package/src/webawesome.css +0 -178
@@ -0,0 +1,128 @@
1
+ import { effect, delegate } from 'kerfjs';
2
+
3
+ // src/wire-sidebar.ts
4
+ var FOCUSABLE = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
5
+ function focusables(panel) {
6
+ return [...panel.querySelectorAll(FOCUSABLE)];
7
+ }
8
+ function defaultStorage() {
9
+ try {
10
+ return globalThis.localStorage ?? void 0;
11
+ } catch {
12
+ return void 0;
13
+ }
14
+ }
15
+ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() }) {
16
+ const ownerDocument = root.ownerDocument;
17
+ const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));
18
+ const returnFocus = /* @__PURE__ */ new Map();
19
+ const disposers = [];
20
+ for (const panel of panels) {
21
+ if (!panel.storageKey || !storage) continue;
22
+ const stored = storage.getItem(panel.storageKey);
23
+ if (stored === "true" || stored === "false")
24
+ panel.collapsed.value = stored === "true";
25
+ disposers.push(
26
+ effect(() => {
27
+ storage.setItem(panel.storageKey, String(panel.collapsed.value));
28
+ })
29
+ );
30
+ }
31
+ const panelElement = (id) => root.querySelector(`[data-collapsible-panel="${id}"]`);
32
+ const collapse = (panel) => {
33
+ if (panel.collapsed.value) return;
34
+ panel.collapsed.value = true;
35
+ };
36
+ disposers.push(
37
+ delegate(root, "click", "[data-action]", (_event, element) => {
38
+ const trigger = element;
39
+ const panel = byAction.get(trigger.dataset.action ?? "");
40
+ if (!panel) return;
41
+ returnFocus.set(panel.id, trigger);
42
+ panel.collapsed.value = !panel.collapsed.value;
43
+ })
44
+ );
45
+ for (const panel of panels) {
46
+ let previous = panel.collapsed.value;
47
+ disposers.push(
48
+ effect(() => {
49
+ const collapsed = panel.collapsed.value;
50
+ if (collapsed === previous) return;
51
+ previous = collapsed;
52
+ const element = panelElement(panel.id);
53
+ if (!collapsed && element) {
54
+ (focusables(element)[0] ?? element).focus();
55
+ } else if (collapsed) {
56
+ returnFocus.get(panel.id)?.focus();
57
+ }
58
+ })
59
+ );
60
+ }
61
+ if (deviceClass) {
62
+ let backdrop;
63
+ const openPanel = () => panels.find((panel) => !panel.collapsed.value);
64
+ const removeBackdrop = () => {
65
+ backdrop?.remove();
66
+ backdrop = void 0;
67
+ };
68
+ disposers.push(
69
+ effect(() => {
70
+ const compact = deviceClass.value.compact;
71
+ const open = compact ? openPanel() : void 0;
72
+ root.dataset.collapsibleOverlay = String(compact);
73
+ if (compact && open) {
74
+ if (!backdrop) {
75
+ backdrop = ownerDocument.createElement("button");
76
+ backdrop.type = "button";
77
+ backdrop.className = "kui-collapsible-panel__backdrop";
78
+ backdrop.setAttribute("aria-label", "Close");
79
+ backdrop.addEventListener("click", () => collapse(open));
80
+ const element = panelElement(open.id);
81
+ element?.parentElement?.insertBefore(backdrop, element);
82
+ }
83
+ } else {
84
+ removeBackdrop();
85
+ if (!compact) delete root.dataset.collapsibleOverlay;
86
+ }
87
+ })
88
+ );
89
+ const onKeydown = (event) => {
90
+ if (!deviceClass.value.compact) return;
91
+ const open = openPanel();
92
+ if (!open) return;
93
+ if (event.key === "Escape") {
94
+ event.preventDefault();
95
+ collapse(open);
96
+ return;
97
+ }
98
+ if (event.key !== "Tab") return;
99
+ const element = panelElement(open.id);
100
+ if (!element) return;
101
+ const items = focusables(element);
102
+ if (items.length === 0) return;
103
+ const first = items[0];
104
+ const last = items[items.length - 1];
105
+ const active = ownerDocument.activeElement;
106
+ if (event.shiftKey && active === first) {
107
+ event.preventDefault();
108
+ last.focus();
109
+ } else if (!event.shiftKey && active === last) {
110
+ event.preventDefault();
111
+ first.focus();
112
+ }
113
+ };
114
+ ownerDocument.addEventListener("keydown", onKeydown);
115
+ disposers.push(
116
+ () => ownerDocument.removeEventListener("keydown", onKeydown)
117
+ );
118
+ disposers.push(removeBackdrop);
119
+ }
120
+ return () => {
121
+ for (const dispose of disposers.splice(0)) dispose();
122
+ delete root.dataset.collapsibleOverlay;
123
+ };
124
+ }
125
+
126
+ export { wireSidebar };
127
+
128
+ //# sourceMappingURL=wire-sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the collapsed state is loaded from and saved to `storage` under\n * this key (a persistence hook), so the panel remembers its state. */\n storageKey?: string;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.\n */\nexport function wireSidebar(\n root: HTMLElement,\n { panels, deviceClass, storage = defaultStorage() }: WireSidebarOptions,\n): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n\n // Persistence: seed from storage, then mirror on change.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n const stored = storage.getItem(panel.storageKey);\n if (stored === 'true' || stored === 'false')\n panel.collapsed.value = stored === 'true';\n disposers.push(\n effect(() => {\n storage.setItem(panel.storageKey!, String(panel.collapsed.value));\n }),\n );\n }\n\n const panelElement = (id: string): HTMLElement | null =>\n root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(\n delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n panel.collapsed.value = !panel.collapsed.value;\n }),\n );\n\n // Focus: move into the panel when it opens; restore to the trigger when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n if (!collapsed && element) {\n (focusables(element)[0] ?? element).focus();\n } else if (collapsed) {\n returnFocus.get(panel.id)?.focus();\n }\n }),\n );\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n const openPanel = (): WireSidebarPanel | undefined =>\n panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n };\n disposers.push(\n effect(() => {\n const compact = deviceClass.value.compact;\n const open = compact ? openPanel() : undefined;\n root.dataset.collapsibleOverlay = String(compact);\n if (compact && open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n backdrop.addEventListener('click', () => collapse(open));\n const element = panelElement(open.id);\n element?.parentElement?.insertBefore(backdrop, element);\n }\n } else {\n removeBackdrop();\n if (!compact) delete root.dataset.collapsibleOverlay;\n }\n }),\n );\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact) return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() =>\n ownerDocument.removeEventListener('keydown', onKeydown),\n );\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n delete root.dataset.collapsibleOverlay;\n };\n}\n"]}
@@ -1,3 +1,6 @@
1
+ import { TabActivation } from './tab-bar.js';
2
+ import 'kerfjs';
3
+
1
4
  type TabReorderSource = 'pointer' | 'keyboard';
2
5
  type TabDropPosition = 'before' | 'after';
3
6
  interface TabReorder {
@@ -7,11 +10,24 @@ interface TabReorder {
7
10
  position: TabDropPosition;
8
11
  source: TabReorderSource;
9
12
  }
13
+
10
14
  interface WireTabBarsOptions {
11
15
  onReorder: (change: TabReorder) => void;
16
+ /**
17
+ * How arrow / Home / End keys activate tabs (default `'automatic'`):
18
+ * - `'automatic'` moves roving focus **and** selects the focused tab (clicks it).
19
+ * - `'manual'` moves roving focus only; the user selects with Enter / Space / click
20
+ * (the ARIA Tabs manual-activation pattern). Use this when activation is a heavy or
21
+ * side-effecting action (e.g. a tab that loads a project) so arrowing through the
22
+ * strip doesn't trigger it on every tab.
23
+ *
24
+ * A per-bar `data-tab-activation="manual" | "automatic"` attribute (see the `TabBar`
25
+ * `activation` prop) overrides this option for that strip.
26
+ */
27
+ activation?: TabActivation;
12
28
  }
13
29
  declare function reorderTabs<T>(items: readonly T[], getId: (item: T) => string, sourceId: string, targetId: string, position: TabDropPosition): T[];
14
30
  /** Wire reordering and keyboard navigation while leaving controlled state in the application. */
15
- declare function wireTabBars(root: HTMLElement | Document, { onReorder }: WireTabBarsOptions): () => void;
31
+ declare function wireTabBars(root: HTMLElement | Document, { onReorder, activation }: WireTabBarsOptions): () => void;
16
32
 
17
- export { type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
33
+ export { TabActivation, type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
@@ -1,3 +1,3 @@
1
- export { reorderTabs, wireTabBars } from './chunk-MQIZAD4A.js';
1
+ export { reorderTabs, wireTabBars } from './chunk-UKKSMZYT.js';
2
2
 
3
3
  //# sourceMappingURL=wire-tab-bars.js.map
@@ -0,0 +1,11 @@
1
+ interface WireTabScaffoldOptions {
2
+ /** Invoked with the selected tab id when a bottom-bar tab is activated. */
3
+ onSelect: (tabId: string) => void;
4
+ }
5
+ /**
6
+ * Wire a `TabScaffold`'s bottom tab bar: clicking a tab calls `onSelect` with its
7
+ * id (the app then updates its controlled `active`). Returns a disposer.
8
+ */
9
+ declare function wireTabScaffold(root: Element, options: WireTabScaffoldOptions): () => void;
10
+
11
+ export { type WireTabScaffoldOptions, wireTabScaffold };
@@ -0,0 +1,21 @@
1
+ import { delegate } from 'kerfjs';
2
+
3
+ // src/wire-tab-scaffold.ts
4
+ function wireTabScaffold(root, options) {
5
+ const section = root.matches('[data-component="tab-scaffold"]') ? root : root.querySelector('[data-component="tab-scaffold"]');
6
+ if (!(section instanceof HTMLElement)) return () => {
7
+ };
8
+ return delegate(
9
+ section,
10
+ "click",
11
+ "[data-tab-scaffold-tab]",
12
+ (_event, target) => {
13
+ const tabId = target.getAttribute("data-tab-scaffold-tab");
14
+ if (tabId) options.onSelect(tabId);
15
+ }
16
+ );
17
+ }
18
+
19
+ export { wireTabScaffold };
20
+
21
+ //# sourceMappingURL=wire-tab-scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wire-tab-scaffold.ts"],"names":[],"mappings":";;;AAWO,SAAS,eAAA,CACd,MACA,OAAA,EACY;AACZ,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,iCAAiC,IAC1D,IAAA,GACA,IAAA,CAAK,cAAc,iCAAiC,CAAA;AACxD,EAAA,IAAI,EAAE,OAAA,YAAmB,WAAA,CAAA,EAAc,OAAO,MAAM;AAAA,EAAC,CAAA;AACrD,EAAA,OAAO,QAAA;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,yBAAA;AAAA,IACA,CAAC,QAAQ,MAAA,KAAW;AAClB,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,uBAAuB,CAAA;AACzD,MAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AAAA,IACnC;AAAA,GACF;AACF","file":"wire-tab-scaffold.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireTabScaffoldOptions {\n /** Invoked with the selected tab id when a bottom-bar tab is activated. */\n onSelect: (tabId: string) => void;\n}\n\n/**\n * Wire a `TabScaffold`'s bottom tab bar: clicking a tab calls `onSelect` with its\n * id (the app then updates its controlled `active`). Returns a disposer.\n */\nexport function wireTabScaffold(\n root: Element,\n options: WireTabScaffoldOptions,\n): () => void {\n const section = root.matches('[data-component=\"tab-scaffold\"]')\n ? root\n : root.querySelector('[data-component=\"tab-scaffold\"]');\n if (!(section instanceof HTMLElement)) return () => {};\n return delegate(\n section,\n 'click',\n '[data-tab-scaffold-tab]',\n (_event, target) => {\n const tabId = target.getAttribute('data-tab-scaffold-tab');\n if (tabId) options.onSelect(tabId);\n },\n );\n}\n"]}
@@ -0,0 +1,111 @@
1
+ import { Signal } from 'kerfjs';
2
+
3
+ interface TokenSearchSubmit {
4
+ id: string;
5
+ editor: HTMLElement;
6
+ }
7
+ /** Reported to {@link WireTokenSearchFieldsOptions.onEdit} on every editor input. */
8
+ interface TokenSearchEdit extends TokenSearchSubmit {
9
+ /**
10
+ * The `InputEvent` that mutated the editor — read `event.inputType` / `event.data`
11
+ * to gate commit behavior (e.g. only parse a chip on whitespace-terminated input)
12
+ * without keeping a separate `input` listener.
13
+ */
14
+ event: InputEvent;
15
+ }
16
+ /** Reported when adjacent-token keyboard deletion asks the app to drop a chip. */
17
+ interface TokenSearchTokenRemoval {
18
+ id: string;
19
+ /** The `data-token-value` of the token the app should remove from its state. */
20
+ value: string;
21
+ editor: HTMLElement;
22
+ /** `'backward'` = the token before the caret (Backspace); `'forward'` = after (Delete). */
23
+ direction: 'backward' | 'forward';
24
+ }
25
+ /**
26
+ * Opt-in keyboard behavior for the atomic token chips. Off unless `keyboard` is
27
+ * set; each piece defaults on once opted in. The helper never mutates app state:
28
+ * a removal is reported through {@link TokenSearchKeyboardOptions.onRemoveToken}
29
+ * for the caller to apply, while caret movement past a chip is a pure ephemeral
30
+ * mechanic the helper performs itself.
31
+ */
32
+ interface TokenSearchKeyboardBaseOptions {
33
+ /**
34
+ * From a collapsed caret with no selection, Backspace removes the token
35
+ * immediately before it and Delete the token immediately after — reported via
36
+ * `onRemoveToken` — instead of deleting a character. Default: true.
37
+ */
38
+ /**
39
+ * ArrowRight moves the caret past a trailing atomic token so text typed next
40
+ * lands after the chip. Default: true.
41
+ */
42
+ moveCaretPastToken?: boolean;
43
+ }
44
+ type TokenSearchKeyboardOptions = TokenSearchKeyboardBaseOptions & ({
45
+ removeAdjacentToken?: true;
46
+ /** Apply the reported removal to your controlled state, then re-render. */
47
+ onRemoveToken: (removal: TokenSearchTokenRemoval) => void;
48
+ } | {
49
+ removeAdjacentToken: false;
50
+ onRemoveToken?: never;
51
+ });
52
+ /**
53
+ * Managed collapsible behavior for the iconic TokenSearchField. Every piece is on
54
+ * by default; disable a specific one to own it in the app. Provide `signals` to
55
+ * drive app-owned `expanded` signals per field id instead of helper-created ones.
56
+ */
57
+ interface TokenSearchCollapsibleOptions {
58
+ /** Expand the field and focus its editor when the iconic trigger is activated. Default: true. */
59
+ expandOnActivate?: boolean;
60
+ /** Collapse the field when focus leaves it while it is empty. Default: true. */
61
+ collapseOnEmptyBlur?: boolean;
62
+ /** Collapse an empty field on Escape and restore focus to its trigger. Default: true. */
63
+ collapseOnEscape?: boolean;
64
+ /** Focus the editor on expand/controlled clear and the trigger on Escape-collapse. Default: true. */
65
+ manageFocus?: boolean;
66
+ /**
67
+ * Keep an empty field expanded when focus moves to a caller-owned surface
68
+ * rendered outside the field — a suggestions dropdown, date picker, or help
69
+ * popover shown beside it. Return true for any focus target that must NOT
70
+ * trigger collapse-on-empty-blur. An element carrying `data-token-search-keep-open`
71
+ * (or any node inside one) is always exempt, so this predicate is only needed
72
+ * for surfaces you cannot mark declaratively.
73
+ */
74
+ keepOpenOn?: (target: Node | null) => boolean;
75
+ /** App-owned `expanded` signals keyed by field id; adopted instead of helper-created. */
76
+ signals?: Readonly<Record<string, Signal<boolean>>>;
77
+ }
78
+ interface WireTokenSearchFieldsOptions {
79
+ onSubmit?: (submission: TokenSearchSubmit) => void;
80
+ /** Fired on every editor `input`, after the browser mutates it, so a caller can drop its own `input` listener. */
81
+ onEdit?: (edit: TokenSearchEdit) => void;
82
+ /** Managed collapsible transient behavior. `true`/omitted = on with defaults; `false` = fully off. */
83
+ collapsible?: boolean | TokenSearchCollapsibleOptions;
84
+ /** Opt-in atomic-chip keyboard behavior (off by default). */
85
+ keyboard?: false | TokenSearchKeyboardOptions;
86
+ }
87
+ /**
88
+ * The value returned from {@link wireTokenSearchFields}: call it (or `dispose()`) to
89
+ * tear down. When collapsible behavior is managed, it also exposes the transient
90
+ * `expanded` state per field id so the app can read it in render, hand in its own
91
+ * signal, or drive it imperatively.
92
+ */
93
+ interface TokenSearchFieldsHandle {
94
+ (): void;
95
+ dispose(): void;
96
+ /** The managed `expanded` signal for a field id (adopted or helper-created); undefined when unmanaged. */
97
+ expanded(id: string): Signal<boolean> | undefined;
98
+ /** Expand the field (and, when focus is managed, focus its editor). */
99
+ open(id: string): void;
100
+ /** Collapse the field (and, when focus is managed, restore focus to its trigger). */
101
+ close(id: string): void;
102
+ }
103
+ /**
104
+ * Wire every TokenSearchField under `root`: submit on Enter, preserve the caret across
105
+ * controlled token deletion, and (by default) manage the collapsible field's transient
106
+ * expand/collapse/focus. Returns a {@link TokenSearchFieldsHandle} — a disposer that also
107
+ * exposes the managed `expanded` state per field id.
108
+ */
109
+ declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard, }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
110
+
111
+ export { type TokenSearchCollapsibleOptions, type TokenSearchEdit, type TokenSearchFieldsHandle, type TokenSearchKeyboardOptions, type TokenSearchSubmit, type TokenSearchTokenRemoval, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
@@ -0,0 +1,5 @@
1
+ export { wireTokenSearchFields } from './chunk-OJWWHFH3.js';
2
+
3
+
4
+
5
+ //# sourceMappingURL=wire-token-search-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"wire-token-search-fields.js"}
@@ -0,0 +1,33 @@
1
+ import { SafeHtml } from 'kerfjs';
2
+
3
+ /** A collapsible Workbench panel — a side rail or the bottom drawer. */
4
+ interface WorkbenchPanel {
5
+ content: SafeHtml;
6
+ /** Whether the panel is currently collapsed (the app owns this). */
7
+ collapsed?: boolean;
8
+ /** Rail width, or drawer height, in px. Overrides the CSS default. */
9
+ size?: number;
10
+ /** Accessible name for the panel region. */
11
+ label?: string;
12
+ }
13
+ interface WorkbenchProps {
14
+ id: string;
15
+ label: string;
16
+ /** The central work area. */
17
+ main: SafeHtml;
18
+ leftRail?: WorkbenchPanel;
19
+ rightRail?: WorkbenchPanel;
20
+ bottomDrawer?: WorkbenchPanel;
21
+ className?: string;
22
+ }
23
+ /**
24
+ * The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and
25
+ * bottom drawer around a central work area (any absent). Collapsing snaps the
26
+ * panel's track to zero in one reflow while its fixed-size content slides out via
27
+ * a composited transform — the instant-width / sliding-content technique, so the
28
+ * work area relayouts once, not per frame. The app owns each `collapsed` flag;
29
+ * the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.
30
+ */
31
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, }: WorkbenchProps): SafeHtml;
32
+
33
+ export { Workbench, type WorkbenchPanel, type WorkbenchProps };
@@ -0,0 +1,64 @@
1
+ import { jsxs, jsx } from 'kerfjs/jsx-runtime';
2
+
3
+ // src/workbench.tsx
4
+ function Workbench({
5
+ id,
6
+ label,
7
+ main,
8
+ leftRail,
9
+ rightRail,
10
+ bottomDrawer,
11
+ className = ""
12
+ }) {
13
+ return /* @__PURE__ */ jsxs(
14
+ "section",
15
+ {
16
+ class: `kui-workbench ${className}`.trim(),
17
+ id,
18
+ "data-component": "workbench",
19
+ "aria-label": label,
20
+ children: [
21
+ leftRail && /* @__PURE__ */ jsx(
22
+ "aside",
23
+ {
24
+ class: "kui-workbench__rail kui-workbench__rail--left",
25
+ "data-workbench-rail": "left",
26
+ "data-collapsed": String(leftRail.collapsed ?? false),
27
+ "aria-label": leftRail.label || void 0,
28
+ style: leftRail.size ? `--kui-workbench-rail-width: ${leftRail.size}px` : void 0,
29
+ children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: leftRail.content })
30
+ }
31
+ ),
32
+ /* @__PURE__ */ jsxs("div", { class: "kui-workbench__center", children: [
33
+ /* @__PURE__ */ jsx("div", { class: "kui-workbench__main", "data-workbench-main": true, children: main }),
34
+ bottomDrawer && /* @__PURE__ */ jsx(
35
+ "section",
36
+ {
37
+ class: "kui-workbench__drawer",
38
+ "data-workbench-drawer": true,
39
+ "data-collapsed": String(bottomDrawer.collapsed ?? false),
40
+ "aria-label": bottomDrawer.label || void 0,
41
+ style: bottomDrawer.size ? `--kui-workbench-drawer-height: ${bottomDrawer.size}px` : void 0,
42
+ children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: bottomDrawer.content })
43
+ }
44
+ )
45
+ ] }),
46
+ rightRail && /* @__PURE__ */ jsx(
47
+ "aside",
48
+ {
49
+ class: "kui-workbench__rail kui-workbench__rail--right",
50
+ "data-workbench-rail": "right",
51
+ "data-collapsed": String(rightRail.collapsed ?? false),
52
+ "aria-label": rightRail.label || void 0,
53
+ style: rightRail.size ? `--kui-workbench-rail-width: ${rightRail.size}px` : void 0,
54
+ children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: rightRail.content })
55
+ }
56
+ )
57
+ ]
58
+ }
59
+ );
60
+ }
61
+
62
+ export { Workbench };
63
+
64
+ //# sourceMappingURL=workbench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workbench.tsx"],"names":[],"mappings":";;;AAgCO,SAAS,SAAA,CAAU;AAAA,EACxB,EAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA,GAAY;AACd,CAAA,EAAmB;AACjB,EAAA,uBACE,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,cAAA,EAAiB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACzC,EAAA;AAAA,MACA,gBAAA,EAAe,WAAA;AAAA,MACf,YAAA,EAAY,KAAA;AAAA,MAEX,QAAA,EAAA;AAAA,QAAA,QAAA,oBACC,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,+CAAA;AAAA,YACN,qBAAA,EAAoB,MAAA;AAAA,YACpB,gBAAA,EAAgB,MAAA,CAAO,QAAA,CAAS,SAAA,IAAa,KAAK,CAAA;AAAA,YAClD,YAAA,EAAY,SAAS,KAAA,IAAS,MAAA;AAAA,YAC9B,OACE,QAAA,CAAS,IAAA,GACL,CAAA,4BAAA,EAA+B,QAAA,CAAS,IAAI,CAAA,EAAA,CAAA,GAC5C,MAAA;AAAA,YAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,8BAAA,EAAgC,mBAAS,OAAA,EAAQ;AAAA;AAAA,SAC9D;AAAA,wBAEF,IAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,uBAAA,EACT,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,qBAAA,EAAsB,qBAAA,EAAmB,MACjD,QAAA,EAAA,IAAA,EACH,CAAA;AAAA,UACC,YAAA,oBACC,GAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,KAAA,EAAM,uBAAA;AAAA,cACN,uBAAA,EAAqB,IAAA;AAAA,cACrB,gBAAA,EAAgB,MAAA,CAAO,YAAA,CAAa,SAAA,IAAa,KAAK,CAAA;AAAA,cACtD,YAAA,EAAY,aAAa,KAAA,IAAS,MAAA;AAAA,cAClC,OACE,YAAA,CAAa,IAAA,GACT,CAAA,+BAAA,EAAkC,YAAA,CAAa,IAAI,CAAA,EAAA,CAAA,GACnD,MAAA;AAAA,cAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,8BAAA,EACR,uBAAa,OAAA,EAChB;AAAA;AAAA;AACF,SAAA,EAEJ,CAAA;AAAA,QACC,SAAA,oBACC,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,gDAAA;AAAA,YACN,qBAAA,EAAoB,OAAA;AAAA,YACpB,gBAAA,EAAgB,MAAA,CAAO,SAAA,CAAU,SAAA,IAAa,KAAK,CAAA;AAAA,YACnD,YAAA,EAAY,UAAU,KAAA,IAAS,MAAA;AAAA,YAC/B,OACE,SAAA,CAAU,IAAA,GACN,CAAA,4BAAA,EAA+B,SAAA,CAAU,IAAI,CAAA,EAAA,CAAA,GAC7C,MAAA;AAAA,YAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,8BAAA,EAAgC,oBAAU,OAAA,EAAQ;AAAA;AAAA;AAC/D;AAAA;AAAA,GAEJ;AAEJ","file":"workbench.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\n/** A collapsible Workbench panel — a side rail or the bottom drawer. */\nexport interface WorkbenchPanel {\n content: SafeHtml;\n /** Whether the panel is currently collapsed (the app owns this). */\n collapsed?: boolean;\n /** Rail width, or drawer height, in px. Overrides the CSS default. */\n size?: number;\n /** Accessible name for the panel region. */\n label?: string;\n}\n\nexport interface WorkbenchProps {\n id: string;\n label: string;\n /** The central work area. */\n main: SafeHtml;\n leftRail?: WorkbenchPanel;\n rightRail?: WorkbenchPanel;\n bottomDrawer?: WorkbenchPanel;\n className?: string;\n}\n\n/**\n * The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and\n * bottom drawer around a central work area (any absent). Collapsing snaps the\n * panel's track to zero in one reflow while its fixed-size content slides out via\n * a composited transform — the instant-width / sliding-content technique, so the\n * work area relayouts once, not per frame. The app owns each `collapsed` flag;\n * the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.\n */\nexport function Workbench({\n id,\n label,\n main,\n leftRail,\n rightRail,\n bottomDrawer,\n className = '',\n}: WorkbenchProps) {\n return (\n <section\n class={`kui-workbench ${className}`.trim()}\n id={id}\n data-component=\"workbench\"\n aria-label={label}\n >\n {leftRail && (\n <aside\n class=\"kui-workbench__rail kui-workbench__rail--left\"\n data-workbench-rail=\"left\"\n data-collapsed={String(leftRail.collapsed ?? false)}\n aria-label={leftRail.label || undefined}\n style={\n leftRail.size\n ? `--kui-workbench-rail-width: ${leftRail.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">{leftRail.content}</div>\n </aside>\n )}\n <div class=\"kui-workbench__center\">\n <div class=\"kui-workbench__main\" data-workbench-main>\n {main}\n </div>\n {bottomDrawer && (\n <section\n class=\"kui-workbench__drawer\"\n data-workbench-drawer\n data-collapsed={String(bottomDrawer.collapsed ?? false)}\n aria-label={bottomDrawer.label || undefined}\n style={\n bottomDrawer.size\n ? `--kui-workbench-drawer-height: ${bottomDrawer.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">\n {bottomDrawer.content}\n </div>\n </section>\n )}\n </div>\n {rightRail && (\n <aside\n class=\"kui-workbench__rail kui-workbench__rail--right\"\n data-workbench-rail=\"right\"\n data-collapsed={String(rightRail.collapsed ?? false)}\n aria-label={rightRail.label || undefined}\n style={\n rightRail.size\n ? `--kui-workbench-rail-width: ${rightRail.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">{rightRail.content}</div>\n </aside>\n )}\n </section>\n );\n}\n"]}
@@ -6,11 +6,32 @@ These are normative contracts for package components and consuming applications.
6
6
 
7
7
  - All controls have accessible names and visible `:focus-visible` treatment.
8
8
  - Decorative `LucideIcon` output is `aria-hidden`; pass `label` only when the SVG itself conveys meaning.
9
- - `MenuItem` uses native button Enter/Space behavior. `selected` maps to `aria-current="page"`; `pressed` maps to `aria-pressed`. It deliberately does not emit `role="menuitem"` in isolation—a true ARIA menu must own arrow, Home/End, Escape, and focus behavior as one widget.
9
+ - `ListItem` uses native button Enter/Space behavior. `selected` maps to `aria-current="page"`; `pressed` maps to `aria-pressed`. Selected rows keep the normal foreground over their brand-tinted fill so text retains WCAG AA contrast in light and dark themes. Its leading icon visual is 18px while the row retains its 44px minimum target; multiline icons align to the first text line. Its `trailing` content is dormant and must not contain controls. Its `rootAttributes` slot accepts only application `data-*` metadata and deliberately cannot emit `role="menuitem"` in isolation—a true ARIA menu must own arrow, Home/End, Escape, and focus behavior as one widget.
10
+ - `ListHeader` owns action naming, disabled state, and disclosure `aria-expanded`. Toggle mode supplies one decorative 18px `DisclosureArrow` when `actionIcon` is omitted; it mirrors `expanded` while the native button's accessible name stays stable. The application must update that controlled state and reveal or hide real content. A valid `count` is visually rendered in an `aria-hidden` neutral pill while the required localized `countLabel` becomes part of the owning heading or disclosure button's accessible name; zero remains a real count. Its narrow `triggerAttributes` slot may describe a native popover or controlled-content relationship with `popoverTarget`, `popoverTargetAction`, `aria-controls`, and `aria-haspopup`; the application owns the target surface and its focus/dismissal behavior.
11
+ - Dispose `wireTabBars` and `wireTokenSearchFields` with their owning view. Disposal cancels queued controlled-render focus restoration, so a torn-down view cannot reclaim focus from its successor.
12
+ - In any `.kui-pane`, menu rows, `ListHeader` actions, and toolbar groups keep a minimum 44px target in both dimensions. A `ListHeader` fills the available inline width and keeps that action at the logical end, including in RTL. Its action visual defaults to 18px through `--kui-list-header-action-icon-size`; do not reduce the target to the visible icon.
10
13
  - `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
11
- - `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure.
14
+ - `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
12
15
  - `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
13
- - `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone.
16
+ - `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone. Its decorative option icons and value-dependent selected content remain present after controlled rerenders, so the visible choice does not silently lose its non-text cue.
17
+ - `DisclosureArrow` is an 18px root-scaled decorative visual by default, not an interaction target. Its owning native control supplies a stable accessible name, pointer and keyboard interaction, and `aria-expanded`; multiple arrows keep independently controlled state. Consumers may override `--kui-disclosure-arrow-size` without changing that ownership. Configured directions animate over the shortest path; a 180-degree closed-to-open tie uses counterclockwise rotation.
18
+ - `List` is layout-only and adds no `list` role. Children own their native or ARIA semantics. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
19
+
20
+ ## ListActionRow
21
+
22
+ `ListActionRow` renders a noninteractive visual root containing primary and
23
+ trailing native buttons as siblings. The primary comes first in DOM and Tab
24
+ order and alone receives `aria-current` or `aria-pressed`; the trailing button
25
+ has its own required accessible name and disabled state. Both controls carry
26
+ the row's item id for application delegation, while the root deliberately has
27
+ no action or role. `label`, `icon`, and `trailingActionIcon` are dormant visual
28
+ content and must not contain buttons, links, interactive roles, or other
29
+ controls. A trailing click, double click, context menu, Enter, or Space
30
+ must never activate the primary control. Selected rows keep the normal
31
+ foreground over their brand-tinted fill so text retains WCAG AA contrast in
32
+ light and dark themes. The application owns controlled
33
+ selection and the lifecycle, focus, and dismissal policy of any related
34
+ popover or context menu.
14
35
 
15
36
  ## ResizableRegion
16
37
 
@@ -22,12 +43,43 @@ The handle exposes separator role, orientation, name, minimum, maximum, and curr
22
43
  - primary-pointer drag with clamping;
23
44
  - preview callbacks while dragging and one commit callback on release.
24
45
 
25
- The application owns persistence and collapsed/expanded policy. Keep the last expanded size outside the component and restore it when reopening.
46
+ The application owns persistence and collapsed/expanded policy. Keep the last expanded size outside the component and restore it when reopening. An optional `handleIcon` replaces only decorative dormant content; it must not contain controls or interactive roles because the separator remains the sole focus and interaction owner.
47
+
48
+ ## PanelHeader
49
+
50
+ `PanelHeader` is a plain `Toolbar` heading. Its leading zone holds an optional
51
+ icon (a normal bordered `ToolbarControlGroup`) and the title as extra-large
52
+ `ToolbarText`, and the app's trailing controls go straight into the trailing
53
+ zone. By default the title carries no native heading role, so for a **dialog or
54
+ panel** the application connects `titleId` and an optional `summaryId` to the
55
+ owning host through `aria-labelledby` and `aria-describedby`. For a **page or
56
+ view** title, pass `headingLevel` (usually `1`): the title then exposes
57
+ `role="heading"` with a matching `aria-level`, giving the view a heading landmark
58
+ so screen-reader heading navigation and "main heading" semantics work — the same
59
+ role/level pair `ToolbarText` exposes when it is given `headingLevel` directly.
60
+ Keep the levels meaningful and non-skipping within a view. Pass the trailing
61
+ controls as a labeled `ToolbarControlGroup` when that group needs an accessible
62
+ name.
63
+
64
+ ## FloatingToolbar
65
+
66
+ `FloatingToolbar` is a `role="toolbar"` region with a **required** accessible
67
+ `label`; it floats over the content of its nearest positioned ancestor and is
68
+ forced to a dark color scheme, but it is **not** in the top layer, so it never
69
+ covers dialogs, popovers, or other overlays. Its children are the app's controls
70
+ (normally `ToolbarControlGroup`s), which keep their own names, focus, and
71
+ keyboard behavior; the application owns their actions, the toolbar's visibility,
72
+ and — via `position` and `--kui-floating-toolbar-inset` — where it sits.
26
73
 
27
74
  ## Tabs
28
75
 
29
76
  `AppTab` renders one controlled tab. `TabBar` supplies the containing tab list,
30
77
  fixed leading/trailing regions, and a horizontally scrollable strip.
78
+ `AppTab.rootAttributes` accepts application `data-*` metadata only; runtime
79
+ filtering rejects roles plus case variants of component- or wiring-owned
80
+ action, identity, selection, drag, drop, and component attributes. An optional
81
+ `closeIcon` is decorative dormant content inside the already named close button
82
+ and must not contain interactive descendants.
31
83
  `wireTabBars()` adds Left/Right wrapping, Home/End, Delete/Backspace close
32
84
  activation, same-bar pointer reordering, `Alt+Shift+ArrowLeft/ArrowRight`
33
85
  reordering, focus restoration, scroll-into-view, and pointer-proximity
@@ -35,6 +87,16 @@ autoscroll at either horizontal edge. Edge autoscroll is direct manipulation,
35
87
  stops on drop/drag end/disposal, and does not change keyboard behavior. It
36
88
  returns a disposer.
37
89
 
90
+ Arrow / Home / End follow the ARIA Tabs **automatic-activation** pattern by
91
+ default: they move roving focus and select the focused tab. If selection synchronously
92
+ replaces the strip, focus returns to the replacement with the same bar and tab IDs. Pass
93
+ `activation: 'manual'` (or set `data-tab-activation="manual"` on a strip via the
94
+ `TabBar` `activation` prop, which overrides the option per bar) for
95
+ **manual activation**: arrow keys move roving focus only and the user selects
96
+ with Enter / Space (native on the tab button) or click. Use manual activation
97
+ when selecting a tab is a heavy or side-effecting action — e.g. a tab that loads
98
+ a project — so arrowing through the strip does not trigger it on every tab.
99
+
38
100
  The application owns the ordered tab array, selection, panels, close policy,
39
101
  routing, and persistence. On `onReorder`, synchronously render the reported
40
102
  order so the helper can restore focus to the moved tab. Pair tabs with
@@ -51,6 +113,64 @@ re-renders. Keep labels unique and meaningful even when `content` shows only an
51
113
  icon. Use tabs—not a segmented control—when choices switch page regions that
52
114
  need `tab`/`tabpanel` semantics.
53
115
 
116
+ ## TokenSearchField
117
+
118
+ `TokenSearchField` exposes the editable surface as a named `searchbox`. Each
119
+ chip is atomic (`contenteditable="false"`) and contains separately named edit
120
+ and remove buttons; the clear action is also named. Disabled fields publish
121
+ `aria-disabled` and stop editing without hiding the current expression. The
122
+ application owns query parsing and must announce result-count or loading
123
+ changes separately when that feedback is useful. Use `readTokenSearchField()`
124
+ to ignore the chip buttons' visible text when reading browser-edited content,
125
+ and `placeTokenSearchCaret()` to restore a text caret without landing inside a
126
+ chip. Call `wireTokenSearchFields()` once at a stable root so Enter submits
127
+ without inserting a contenteditable line break and keyboard chip deletion
128
+ restores focus plus the text-relative caret after controlled rendering replaces
129
+ the editor; the editor still wraps text visually at its inline edge. Editable text is DOM-owned between token changes; a clear handler empties
130
+ the editor's `textContent` before updating application state. Leading and
131
+ trailing controls share the first text line's fixed vertical center and remain
132
+ there as the editor wraps. In `collapsible` mode, the closed state is one named
133
+ iconic search button and the open state is the same named searchbox, whether
134
+ the field stands alone or is composed inside `ToolbarControlGroup`.
135
+ Applications set `expanded` while the field is focused, move focus from the
136
+ trigger into the revealed editor, and clear `expanded` only after focus leaves
137
+ the complete component. Text or tokens keep the field expanded even when that
138
+ transient state is false. Width animation is disabled under reduced motion.
139
+
140
+ When the field is composed with a caller-owned surface rendered outside it — a
141
+ suggestions listbox, date picker, or help popover — mark that surface (or wrap
142
+ it) with `data-token-search-keep-open`, or pass `collapsible.keepOpenOn(target)`,
143
+ so focus moving into it does not collapse an empty field. `wireTokenSearchFields`
144
+ can also, opt-in via `keyboard`, own atomic-chip editing keys: from a collapsed
145
+ caret with no selection, Backspace removes the token before the caret and Delete
146
+ the token after it (reported through `onRemoveToken` for the app to apply to its
147
+ controlled state), and ArrowRight moves the caret past a trailing chip so typed
148
+ text lands after it. An optional `onEdit({ id, editor, event })` — with the
149
+ originating `InputEvent` so a caller can gate on `inputType`/`data` — fires on every editor
150
+ `input`, letting a caller drop its own `input` listener; the application still
151
+ owns query parsing and result-count/loading announcements.
152
+
153
+ Managed clear captures the action before application handlers run, keeps the adopted
154
+ expanded signal open during editor replacement, and focuses the current editor after
155
+ rendering so typing can continue. The app still owns clearing query/tokens and emptying
156
+ DOM-owned text. This focus step respects `manageFocus: false`, disposal, removed fields,
157
+ and focus deliberately moved to another control; no app-level reopen callback is needed.
158
+
159
+ ## CollapsiblePanel / sidebar
160
+
161
+ `CollapsiblePanel` is a labeled `aside` region that is `aria-hidden` while
162
+ collapsed; its content slides via transform (disabled under reduced motion).
163
+ `CollapsiblePanelToggle` is a named button carrying `aria-expanded` and the
164
+ standard per-side collapse/expand glyph (`PanelLeft*` / `PanelRight*` /
165
+ `PanelBottom*`); place a collapse toggle inside the panel and an expand toggle in
166
+ an always-visible location so it is reachable while collapsed. `wireSidebar()`
167
+ moves focus into the panel when it opens and restores it to the trigger when it
168
+ closes. When a `deviceClass()` reports `compact`, an open panel becomes an overlay
169
+ with a dismissable backdrop, Escape and backdrop-click collapse it, and Tab is
170
+ trapped within it (the ARIA dialog pattern). The application owns each `collapsed`
171
+ signal, the panels, their sizes, and content; the wire may persist the collapsed
172
+ state per panel.
173
+
54
174
  ## Verification matrix
55
175
 
56
176
  For each changed component, inspect default, hover, focus, disabled, selected/pressed, busy/error, long-content, wide, narrow, light, dark, increased-contrast, reduced-motion, keyboard-only, and 200%-zoom states where applicable. DOM order must match reading and focus order, with no clipping or unreachable action.