@kitn.ai/ui 0.18.2 → 0.20.0

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 (258) hide show
  1. package/LICENSE +6 -0
  2. package/NOTICE +54 -0
  3. package/dist/components/audio-visualizer/audio-visualizer.voice-fixture.d.ts +59 -0
  4. package/dist/components/audio-visualizer/aurora.glsl.d.ts +114 -0
  5. package/dist/components/audio-visualizer/index.d.ts +155 -0
  6. package/dist/components/audio-visualizer/shader-canvas.d.ts +130 -0
  7. package/dist/components/audio-visualizer/sizes.d.ts +25 -0
  8. package/dist/components/audio-visualizer/variant-aurora.d.ts +89 -0
  9. package/dist/components/audio-visualizer/variant-bar.d.ts +46 -0
  10. package/dist/components/audio-visualizer/variant-custom.d.ts +73 -0
  11. package/dist/components/audio-visualizer/variant-grid.d.ts +22 -0
  12. package/dist/components/audio-visualizer/variant-radial.d.ts +13 -0
  13. package/dist/components/audio-visualizer/variant-wave.d.ts +11 -0
  14. package/dist/components/audio-visualizer/wave.glsl.d.ts +16 -0
  15. package/dist/components/thread.d.ts +54 -0
  16. package/dist/create-tween-hvpmSuwW.js +7 -0
  17. package/dist/custom-elements.json +477 -0
  18. package/dist/elements/agent-card.js +1 -1
  19. package/dist/elements/artifact.js +1 -1
  20. package/dist/elements/attachments.js +1 -1
  21. package/dist/elements/audio-visualizer.d.ts +1 -0
  22. package/dist/elements/audio-visualizer.js +1 -0
  23. package/dist/elements/autoloader.js +1 -1
  24. package/dist/elements/avatar.js +1 -1
  25. package/dist/elements/badge.js +1 -1
  26. package/dist/elements/button.js +1 -1
  27. package/dist/elements/card.js +1 -1
  28. package/dist/elements/cards.js +1 -1
  29. package/dist/elements/chain-of-thought.js +1 -1
  30. package/dist/elements/chat-scope-picker.js +1 -1
  31. package/dist/elements/chat-workspace.js +1 -1
  32. package/dist/elements/chat.js +1 -1
  33. package/dist/elements/checkpoint.js +1 -1
  34. package/dist/elements/choice.js +1 -1
  35. package/dist/elements/chunks/{Icon-C2LTALdM.js → Icon-Tj58ES_d.js} +1 -1
  36. package/dist/elements/chunks/action-icons-ns1Fnedj.js +1 -0
  37. package/dist/elements/chunks/arrow-left-DQocUFvy.js +1 -0
  38. package/dist/elements/chunks/{attachments-DLJcWCDV.js → attachments-mCB5F1Fq.js} +1 -1
  39. package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +1 -0
  40. package/dist/elements/chunks/{badge-BTNZTODL.js → badge-B4Zuajl2.js} +1 -1
  41. package/dist/elements/chunks/{button-DBDZebGr.js → button-ZWNif3--.js} +1 -1
  42. package/dist/elements/chunks/{card-YfCyI4fH.js → card-BkHbp4BA.js} +1 -1
  43. package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +1 -0
  44. package/dist/elements/chunks/check-B9-gn6SS.js +1 -0
  45. package/dist/elements/chunks/chevron-down-BNsxc2hk.js +1 -0
  46. package/dist/elements/chunks/chevron-right-Hv00StBO.js +1 -0
  47. package/dist/elements/chunks/circle-0e3KJTDr.js +1 -0
  48. package/dist/elements/chunks/circle-check-BNAS0JEy.js +1 -0
  49. package/dist/elements/chunks/{circle-x-pDtzlghQ.js → circle-x-B7faJFlY.js} +1 -1
  50. package/dist/elements/chunks/{code-block-DZ6mz6vs.js → code-block-BFUHn6Va.js} +1 -1
  51. package/dist/elements/chunks/collapsible-DM1Ckd4e.js +1 -0
  52. package/dist/elements/chunks/{composer-BZyPU6gD.js → composer-BguS0QL_.js} +7 -3
  53. package/dist/elements/chunks/{context-Cx6eQA_G.js → context-Cb7k4F-v.js} +1 -1
  54. package/dist/elements/chunks/{conversation-list-C4xTQSpT.js → conversation-list-CvoAkWhN.js} +1 -1
  55. package/dist/elements/chunks/create-tween-oVvAIG8r.js +7 -0
  56. package/dist/elements/chunks/{default-input-BZcwyKcP.js → default-input-Cqqilg9t.js} +1 -1
  57. package/dist/elements/chunks/define-D1din7Ld.js +1 -0
  58. package/dist/elements/chunks/{disclosure-BcBs--Jf.js → disclosure-CIZ5c8nO.js} +1 -1
  59. package/dist/elements/chunks/{download-BDQwcFav.js → download-Dsbg3ste.js} +1 -1
  60. package/dist/elements/chunks/{dropdown-wgs8xCJZ.js → dropdown-BDd1-Zgi.js} +1 -1
  61. package/dist/elements/chunks/{ellipsis-CUiEJ6R1.js → ellipsis-Dw6qrss-.js} +1 -1
  62. package/dist/elements/chunks/{external-link-DJUmC7g2.js → external-link-Vt74zAoL.js} +1 -1
  63. package/dist/elements/chunks/{file-text-DOYfYvrh.js → file-text-CZrZF6_v.js} +1 -1
  64. package/dist/elements/chunks/{file-tree-DQzNQrbr.js → file-tree-DF4okKyo.js} +1 -1
  65. package/dist/elements/chunks/{folder-DBDUBANz.js → folder-glP5rha4.js} +1 -1
  66. package/dist/elements/chunks/hover-card-Cmm6Q53m.js +1 -0
  67. package/dist/elements/chunks/icon-BLb8beoi.js +1 -0
  68. package/dist/elements/chunks/{info-DBQl-m6e.js → info-CIsv92JK.js} +1 -1
  69. package/dist/elements/chunks/{input-DZD67EvW.js → input-Dds1kFyt.js} +1 -1
  70. package/dist/elements/chunks/{kbd-DbyVrlnU.js → kbd-De_xbgi9.js} +1 -1
  71. package/dist/elements/chunks/{link-vko7AttA.js → link-i4r_k7z4.js} +1 -1
  72. package/dist/elements/chunks/{loader-Cg6rO8tQ.js → loader-BmQH4s3V.js} +1 -1
  73. package/dist/elements/chunks/{markdown-n3Xbj3PR.js → markdown-Zi21WhUz.js} +7 -7
  74. package/dist/elements/chunks/{message-CgKytiSB.js → message-BqyXDN3-.js} +1 -1
  75. package/dist/elements/chunks/{message-feedback-Ds3-US0e.js → message-feedback-Djr3z6Yb.js} +1 -1
  76. package/dist/elements/chunks/message-square-vFAgbDaX.js +1 -0
  77. package/dist/elements/chunks/{minimize-2-3kCdE0q3.js → minimize-2--ifeCGrl.js} +1 -1
  78. package/dist/elements/chunks/{model-switcher-BMtXGwLU.js → model-switcher-B94YAKsM.js} +1 -1
  79. package/dist/elements/chunks/{overlay-B1-MBI6h.js → overlay-kD8dwRv_.js} +1 -1
  80. package/dist/elements/chunks/{paperclip-DDEfmkHc.js → paperclip-q21dYOSK.js} +1 -1
  81. package/dist/elements/chunks/{progress-bar-Dwbiyv3s.js → progress-bar-BrAIIaaJ.js} +1 -1
  82. package/dist/elements/chunks/{prompt-suggestion-9lvgFcEa.js → prompt-suggestion-Bjn2K-fR.js} +1 -1
  83. package/dist/elements/chunks/{reasoning-DzHWsdAJ.js → reasoning-DTpCGtnY.js} +1 -1
  84. package/dist/elements/chunks/resizable-Dp4JExql.js +1 -0
  85. package/dist/elements/chunks/{rotate-cw-jpisOdOV.js → rotate-cw-CFWWkpj2.js} +1 -1
  86. package/dist/elements/chunks/{scroll-area-g5Ze3K6f.js → scroll-area-Bb0kHH1j.js} +1 -1
  87. package/dist/elements/chunks/scroll-button-B0cH5fXf.js +1 -0
  88. package/dist/elements/chunks/{separator-Bmjc5XyX.js → separator-9kRgr1X9.js} +1 -1
  89. package/dist/elements/chunks/{settings-Cz1bMJP9.js → settings-B8lmec4D.js} +1 -1
  90. package/dist/elements/chunks/{settings-group-9imY3kRA.js → settings-group-I0hEuXiN.js} +1 -1
  91. package/dist/elements/chunks/{share-BdenF0wQ.js → share-C3lTiYGt.js} +1 -1
  92. package/dist/elements/chunks/skeleton-DSXW5Yya.js +1 -0
  93. package/dist/elements/chunks/{slots-DkZ-Cmha.js → slots--V19HwUb.js} +1 -1
  94. package/dist/elements/chunks/{star-DY6z1UUH.js → star-CSJtGxgv.js} +1 -1
  95. package/dist/elements/chunks/{store-th4VyJXM.js → store-CHyW95TP.js} +1 -1
  96. package/dist/elements/chunks/{text-shimmer-CEi-tlXr.js → text-shimmer-Cn9TmNpI.js} +1 -1
  97. package/dist/elements/chunks/textarea-CUwqAX0G.js +1 -0
  98. package/dist/elements/chunks/{thumbs-up-BR33KLfA.js → thumbs-up-PNVVThEF.js} +1 -1
  99. package/dist/elements/chunks/{toast-store-BEMda0fv.js → toast-store-DKbQn3Wn.js} +1 -1
  100. package/dist/elements/chunks/{tool-TMued3WO.js → tool-DjTRpIpl.js} +1 -1
  101. package/dist/elements/chunks/tooltip-CgBZCVKp.js +1 -0
  102. package/dist/elements/chunks/{triangle-alert-B07fX3Q8.js → triangle-alert-DsHTHCPm.js} +1 -1
  103. package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +1 -0
  104. package/dist/elements/chunks/variant-aurora-Dc4nQUia.js +218 -0
  105. package/dist/elements/chunks/variant-custom-y_3kVQhS.js +1 -0
  106. package/dist/elements/chunks/variant-wave-D5OR1-9Z.js +116 -0
  107. package/dist/elements/chunks/{video-MKp3LmNT.js → video-B7vmenoC.js} +1 -1
  108. package/dist/elements/chunks/x-GRUeeQFT.js +1 -0
  109. package/dist/elements/coachmark.js +1 -1
  110. package/dist/elements/code-block.js +1 -1
  111. package/dist/elements/command.js +1 -1
  112. package/dist/elements/compare.js +1 -1
  113. package/dist/elements/composer.js +1 -1
  114. package/dist/elements/confirm-card.js +1 -1
  115. package/dist/elements/context-meter.js +1 -1
  116. package/dist/elements/conversation-list.js +1 -1
  117. package/dist/elements/define.d.ts +11 -0
  118. package/dist/elements/dialog.js +1 -1
  119. package/dist/elements/editable-label.js +1 -1
  120. package/dist/elements/embed.js +1 -1
  121. package/dist/elements/empty.js +1 -1
  122. package/dist/elements/feedback-bar.js +1 -1
  123. package/dist/elements/file-tree.js +1 -1
  124. package/dist/elements/file-upload.js +1 -1
  125. package/dist/elements/form.js +1 -1
  126. package/dist/elements/hover-card.js +1 -1
  127. package/dist/elements/icon.js +1 -1
  128. package/dist/elements/image.js +1 -1
  129. package/dist/elements/input.js +1 -1
  130. package/dist/elements/kbd.js +1 -1
  131. package/dist/elements/link-preview.js +1 -1
  132. package/dist/elements/loader.js +1 -1
  133. package/dist/elements/markdown.js +1 -1
  134. package/dist/elements/menu.js +1 -1
  135. package/dist/elements/message-skills.js +1 -1
  136. package/dist/elements/message.js +1 -1
  137. package/dist/elements/model-switcher.js +1 -1
  138. package/dist/elements/nav.js +1 -1
  139. package/dist/elements/notice.js +1 -1
  140. package/dist/elements/pane-group.js +1 -1
  141. package/dist/elements/pane.js +1 -1
  142. package/dist/elements/popover.js +1 -1
  143. package/dist/elements/progress-bar.js +1 -1
  144. package/dist/elements/prompt-dock.js +1 -1
  145. package/dist/elements/prompt-input.js +1 -1
  146. package/dist/elements/prompt-suggestions.js +1 -1
  147. package/dist/elements/reasoning.js +1 -1
  148. package/dist/elements/remote.js +1 -1
  149. package/dist/elements/resizable.js +1 -1
  150. package/dist/elements/response-stream.js +1 -1
  151. package/dist/elements/screen.js +1 -1
  152. package/dist/elements/scroll-area.js +1 -1
  153. package/dist/elements/scroll-button.js +1 -1
  154. package/dist/elements/search.js +1 -1
  155. package/dist/elements/segmented.js +1 -1
  156. package/dist/elements/separator.js +1 -1
  157. package/dist/elements/setting-item.js +1 -1
  158. package/dist/elements/settings-group.js +1 -1
  159. package/dist/elements/skeleton.js +1 -1
  160. package/dist/elements/slots.d.ts +15 -0
  161. package/dist/elements/source.js +1 -1
  162. package/dist/elements/status.js +1 -1
  163. package/dist/elements/switch.js +1 -1
  164. package/dist/elements/tabs.js +1 -1
  165. package/dist/elements/tasks.js +1 -1
  166. package/dist/elements/text-shimmer.js +1 -1
  167. package/dist/elements/thinking-bar.js +1 -1
  168. package/dist/elements/thread.d.ts +1 -0
  169. package/dist/elements/thread.js +1 -0
  170. package/dist/elements/toast.js +1 -1
  171. package/dist/elements/tool.js +1 -1
  172. package/dist/elements/tooltip.js +1 -1
  173. package/dist/elements/voice-input.js +1 -1
  174. package/dist/elements/voice-output.js +1 -1
  175. package/dist/elements.d.ts +62 -0
  176. package/dist/index.js +3411 -3328
  177. package/dist/kai.es.js +1 -1
  178. package/dist/llms/llms-full.txt +70 -4
  179. package/dist/llms/llms.txt +3 -3
  180. package/dist/primitives/audio-bands.d.ts +106 -0
  181. package/dist/primitives/create-tween.d.ts +33 -0
  182. package/dist/primitives/use-audio-analysis.d.ts +149 -0
  183. package/dist/primitives/use-sequencer.d.ts +13 -0
  184. package/dist/primitives/use-voice-recorder.d.ts +1 -0
  185. package/dist/primitives/visualizer-sequences.d.ts +39 -0
  186. package/dist/react/index.d.ts +105 -0
  187. package/dist/react/use-voice-input.d.ts +15 -0
  188. package/dist/react.js +269 -240
  189. package/dist/register-impl-DQyejdfb.js +145 -0
  190. package/dist/test-utils/fake-clock.d.ts +45 -0
  191. package/dist/ui/resizable.d.ts +9 -3
  192. package/dist/variant-aurora-DVtGqVH6.js +218 -0
  193. package/dist/variant-custom-Dif-IV4u.js +1 -0
  194. package/dist/variant-wave-DbfNdvSi.js +116 -0
  195. package/frameworks/react/index.tsx +74 -1
  196. package/frameworks/react/use-voice-input.tsx +59 -0
  197. package/llms-full.txt +70 -4
  198. package/llms.txt +3 -3
  199. package/package.json +32 -7
  200. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +257 -0
  201. package/src/components/audio-visualizer/aurora.glsl.ts +330 -0
  202. package/src/components/audio-visualizer/index.tsx +442 -0
  203. package/src/components/audio-visualizer/shader-canvas.tsx +846 -0
  204. package/src/components/audio-visualizer/sizes.ts +54 -0
  205. package/src/components/audio-visualizer/variant-aurora.tsx +299 -0
  206. package/src/components/audio-visualizer/variant-bar.tsx +143 -0
  207. package/src/components/audio-visualizer/variant-custom.tsx +338 -0
  208. package/src/components/audio-visualizer/variant-grid.tsx +170 -0
  209. package/src/components/audio-visualizer/variant-radial.tsx +158 -0
  210. package/src/components/audio-visualizer/variant-wave.tsx +125 -0
  211. package/src/components/audio-visualizer/wave.glsl.ts +130 -0
  212. package/src/components/component-meta.json +34 -32
  213. package/src/components/composer.tsx +5 -0
  214. package/src/components/thread.tsx +177 -0
  215. package/src/elements/audio-visualizer.tsx +183 -0
  216. package/src/elements/chat-workspace.tsx +1 -1
  217. package/src/elements/compiled.css +1 -1
  218. package/src/elements/define.tsx +12 -1
  219. package/src/elements/element-manifest.json +8 -0
  220. package/src/elements/element-meta.json +287 -0
  221. package/src/elements/element-types.d.ts +62 -0
  222. package/src/elements/icon-names.json +2 -0
  223. package/src/elements/menu.tsx +7 -4
  224. package/src/elements/register-impl.ts +3 -0
  225. package/src/elements/resizable.tsx +78 -2
  226. package/src/elements/slots.ts +42 -0
  227. package/src/elements/thread.tsx +105 -0
  228. package/src/primitives/audio-bands.ts +174 -0
  229. package/src/primitives/create-tween.ts +214 -0
  230. package/src/primitives/use-audio-analysis.ts +321 -0
  231. package/src/primitives/use-sequencer.ts +53 -0
  232. package/src/primitives/use-voice-recorder.ts +16 -4
  233. package/src/primitives/visualizer-sequences.ts +350 -0
  234. package/src/test-utils/fake-clock.ts +88 -0
  235. package/src/ui/action-icons.ts +2 -1
  236. package/src/ui/icon.tsx +4 -0
  237. package/src/ui/overlay.tsx +8 -1
  238. package/src/ui/resizable.tsx +47 -9
  239. package/dist/elements/chunks/action-icons-Dtl8Ox5D.js +0 -1
  240. package/dist/elements/chunks/arrow-left-GbBceGBE.js +0 -1
  241. package/dist/elements/chunks/chat-thread-Dz2qbzvI.js +0 -1
  242. package/dist/elements/chunks/check-fhmRksq_.js +0 -1
  243. package/dist/elements/chunks/chevron-down-DcRg1aRU.js +0 -1
  244. package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +0 -1
  245. package/dist/elements/chunks/circle-B4fTe9v6.js +0 -1
  246. package/dist/elements/chunks/circle-check-CJV-3Rem.js +0 -1
  247. package/dist/elements/chunks/collapsible-CWUAShMC.js +0 -1
  248. package/dist/elements/chunks/define-CNSKFZCd.js +0 -1
  249. package/dist/elements/chunks/hover-card-BQGdGAQ3.js +0 -1
  250. package/dist/elements/chunks/icon-CAyRqVrg.js +0 -1
  251. package/dist/elements/chunks/message-square-MmRkvc-M.js +0 -1
  252. package/dist/elements/chunks/resizable-Bq_eHxUm.js +0 -1
  253. package/dist/elements/chunks/skeleton-Pjc5jiaY.js +0 -1
  254. package/dist/elements/chunks/textarea-CboigarR.js +0 -1
  255. package/dist/elements/chunks/tooltip-Cft_Rxiw.js +0 -1
  256. package/dist/elements/chunks/use-card-resolution-M36zVciB.js +0 -1
  257. package/dist/elements/chunks/x-Ckwicf6g.js +0 -1
  258. package/dist/register-impl-DcjZDELj.js +0 -141
@@ -78,6 +78,17 @@ export interface WebComponentContext<E = Record<string, unknown>> {
78
78
  * convention). Methods are attached when the facade renders (on element upgrade).
79
79
  */
80
80
  expose: (methods: Record<string, (...args: never[]) => unknown>) => void;
81
+ /**
82
+ * Whether the element should currently render its dark-mode look --
83
+ * exactly the SAME resolved value that already drives the `.dark` class
84
+ * every facade's content sits inside (not a second computation of the
85
+ * `theme='light'|'dark'|'auto'` rule; see `createDarkMode` above). Most
86
+ * facades never need this directly, since the injected kit CSS already
87
+ * flips its custom properties under `.dark`. It exists for content that
88
+ * cannot read CSS at all -- e.g. a WebGL shader baking a colour choice
89
+ * into a GLSL uniform, which is why `kai-audio-visualizer` reads it.
90
+ */
91
+ dark: () => boolean;
81
92
  }
82
93
 
83
94
  /** camelCase prop name → kebab-case attribute (`hoverCard` → `hover-card`). */
@@ -223,7 +234,7 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
223
234
  <div classList={{ dark: isDark() }} style={{ display: 'contents', color: 'var(--color-foreground)' }}>
224
235
  <div ref={portalNode} />
225
236
  <ChatConfig portalMount={portalNode}>
226
- {Facade(props as unknown as P, { element, dispatch, flag, expose })}
237
+ {Facade(props as unknown as P, { element, dispatch, flag, expose, dark: isDark })}
227
238
  </ChatConfig>
228
239
  </div>
229
240
  </>
@@ -4,6 +4,7 @@
4
4
  "kai-prompt-input": "prompt-input",
5
5
  "kai-chat": "chat",
6
6
  "kai-workspace": "chat-workspace",
7
+ "kai-thread": "thread",
7
8
  "kai-thinking-bar": "thinking-bar",
8
9
  "kai-model-switcher": "model-switcher",
9
10
  "kai-attachments": "attachments",
@@ -18,6 +19,7 @@
18
19
  "kai-suggestions": "prompt-suggestions",
19
20
  "kai-file-upload": "file-upload",
20
21
  "kai-voice-input": "voice-input",
22
+ "kai-audio-visualizer": "audio-visualizer",
21
23
  "kai-loader": "loader",
22
24
  "kai-text-shimmer": "text-shimmer",
23
25
  "kai-image": "image",
@@ -91,6 +93,9 @@
91
93
  "chat-workspace": [
92
94
  "kai-workspace"
93
95
  ],
96
+ "thread": [
97
+ "kai-thread"
98
+ ],
94
99
  "thinking-bar": [
95
100
  "kai-thinking-bar"
96
101
  ],
@@ -133,6 +138,9 @@
133
138
  "voice-input": [
134
139
  "kai-voice-input"
135
140
  ],
141
+ "audio-visualizer": [
142
+ "kai-audio-visualizer"
143
+ ],
136
144
  "loader": [
137
145
  "kai-loader"
138
146
  ],
@@ -456,6 +456,176 @@
456
456
  }
457
457
  ]
458
458
  },
459
+ {
460
+ "tag": "kai-audio-visualizer",
461
+ "className": "KaiAudioVisualizerElement",
462
+ "displayName": "AudioVisualizer",
463
+ "props": [
464
+ {
465
+ "name": "variant",
466
+ "type": "undefined | string",
467
+ "optional": true,
468
+ "scalar": true,
469
+ "description": "Look to render: `bar` (default), `grid`, `radial`, `wave`, `aurora`, `custom`. `aura` is accepted as a LiveKit-markup alias for `aurora`. Attribute: `variant`.",
470
+ "default": "'bar'",
471
+ "displayType": "undefined | string"
472
+ },
473
+ {
474
+ "name": "state",
475
+ "type": "undefined | string",
476
+ "optional": true,
477
+ "scalar": true,
478
+ "description": "`idle` (default), `connecting`, `listening`, `thinking`, `speaking`, `disconnected` (connection down: the dead, flat look). LiveKit's room-lifecycle state names are accepted as aliases. Attribute: `state`.",
479
+ "default": "'idle'",
480
+ "displayType": "undefined | string"
481
+ },
482
+ {
483
+ "name": "size",
484
+ "type": "undefined | string",
485
+ "optional": true,
486
+ "scalar": true,
487
+ "description": "`icon` | `sm` | `md` (default) | `lg` | `xl`. Attribute: `size`.",
488
+ "default": "'md'",
489
+ "displayType": "undefined | string"
490
+ },
491
+ {
492
+ "name": "barCount",
493
+ "type": "undefined | number",
494
+ "optional": true,
495
+ "scalar": true,
496
+ "description": "Bars to draw. Bar and radial only. Attribute: `bar-count`.",
497
+ "displayType": "undefined | number"
498
+ },
499
+ {
500
+ "name": "count",
501
+ "type": "undefined | number",
502
+ "optional": true,
503
+ "scalar": true,
504
+ "description": "Grid only: rows and columns of the (always square) grid. Attribute: `count`.",
505
+ "displayType": "undefined | number"
506
+ },
507
+ {
508
+ "name": "radius",
509
+ "type": "undefined | number",
510
+ "optional": true,
511
+ "scalar": true,
512
+ "description": "Radial only: ring distance from center, in px. Attribute: `radius`.",
513
+ "displayType": "undefined | number"
514
+ },
515
+ {
516
+ "name": "spread",
517
+ "type": "undefined | number",
518
+ "optional": true,
519
+ "scalar": true,
520
+ "description": "Grid only: ring distance for the connecting animation, in cells. Attribute: `spread`.",
521
+ "displayType": "undefined | number"
522
+ },
523
+ {
524
+ "name": "interval",
525
+ "type": "undefined | number",
526
+ "optional": true,
527
+ "scalar": true,
528
+ "description": "Grid only: ms between scripted frames. Attribute: `interval`.",
529
+ "displayType": "undefined | number"
530
+ },
531
+ {
532
+ "name": "color",
533
+ "type": "undefined | string",
534
+ "optional": true,
535
+ "scalar": true,
536
+ "description": "CSS color for the geometry, overriding the inherited `currentColor`. Attribute: `color`.",
537
+ "displayType": "undefined | string"
538
+ },
539
+ {
540
+ "name": "complexity",
541
+ "type": "undefined | number",
542
+ "optional": true,
543
+ "scalar": true,
544
+ "description": "Shader variants only: pattern density, 0..1. Attribute: `complexity`.",
545
+ "displayType": "undefined | number"
546
+ },
547
+ {
548
+ "name": "label",
549
+ "type": "undefined | string",
550
+ "optional": true,
551
+ "scalar": true,
552
+ "description": "Setting this makes the element an announced image (`role=\"img\"`) instead of decorative (`aria-hidden`). Attribute: `label`.",
553
+ "displayType": "undefined | string"
554
+ },
555
+ {
556
+ "name": "stream",
557
+ "type": "undefined | MediaStream",
558
+ "optional": true,
559
+ "scalar": false,
560
+ "description": "Live microphone or WebRTC audio to analyze. JS property only.",
561
+ "displayType": "undefined | MediaStream"
562
+ },
563
+ {
564
+ "name": "audioElement",
565
+ "type": "undefined | HTMLMediaElement",
566
+ "optional": true,
567
+ "scalar": false,
568
+ "description": "An `<audio>` or `<video>` element to tap for its audio. JS property only.",
569
+ "displayType": "undefined | HTMLMediaElement"
570
+ },
571
+ {
572
+ "name": "bands",
573
+ "type": "undefined | number[]",
574
+ "optional": true,
575
+ "scalar": false,
576
+ "description": "Pre-computed levels, 0..1. Set this and no AudioContext is ever built, which is what keeps headless/SSR rendering and browser-speech-synthesis playback (which exposes no audio node) free of Web Audio entirely. JS property only. A new array reference is required for each update; mutating the existing array in place will not re-render.",
577
+ "displayType": "undefined | number[]"
578
+ },
579
+ {
580
+ "name": "shader",
581
+ "type": "undefined | { fragment: string; uniforms?: undefined | Record<string, { type: \"1f\" | \"1i\" | \"1fv\" | \"2f\" | \"3f\" | \"3fv\" | \"4f\" | \"4fv\" | \"Matrix2fv\" | \"Matrix3fv\" | \"Matrix4fv\"; value: number | number[] }> }",
582
+ "optional": true,
583
+ "scalar": false,
584
+ "description": "Custom fragment shader for `variant=\"custom\"`. JS property only.",
585
+ "displayType": "undefined | { fragment: string; uniforms?: undefined | Record<string, { type: \"1f\" | \"1i\" | \"1fv\" | \"2f\" | \"3f\" | \"3fv\" | \"4f\" | \"4fv\" | \"Matrix2fv\" | \"Matrix3fv\" | \"Matrix4fv\"; value: number | number[] }> }"
586
+ },
587
+ {
588
+ "name": "animateWhenNotVisible",
589
+ "type": "undefined | false | true",
590
+ "optional": true,
591
+ "scalar": true,
592
+ "description": "Shader variants only: keep animating while scrolled off screen. Off by default, which stops drawing and releases the WebGL context until the element comes back (browsers ration contexts to roughly 16 a page). Does not override `prefers-reduced-motion`. Attribute: `animate-when-not-visible`.",
593
+ "displayType": "undefined | false | true"
594
+ }
595
+ ],
596
+ "events": [],
597
+ "methods": [],
598
+ "composedFrom": [
599
+ {
600
+ "name": "AudioVisualizer",
601
+ "group": "Components",
602
+ "storyId": "solid-advanced-elements-audiovisualizer--docs"
603
+ }
604
+ ],
605
+ "tokens": [],
606
+ "parts": [
607
+ {
608
+ "name": "bar",
609
+ "doc": "A single bar in the `bar` variant, or a single spoke in the `radial` variant. Also carries `data-kai-index` and `data-kai-highlighted` (\"true\"/\"false\") for use inside the shadow root; to style the lit state from OUTSIDE, combine with the `highlighted` part below rather than an attribute selector.",
610
+ "recipe": "kai-audio-visualizer::part(bar) { border-radius: 2px }\nkai-audio-visualizer::part(bar highlighted) { background: var(--brand) }"
611
+ },
612
+ {
613
+ "name": "cell",
614
+ "doc": "A single dot in the `grid` variant. Also carries `data-kai-index` and `data-kai-highlighted` (\"true\"/\"false\") for use inside the shadow root; to style the lit state from OUTSIDE, combine with the `highlighted` part below rather than an attribute selector.",
615
+ "recipe": "kai-audio-visualizer::part(cell) { border-radius: 9999px }\nkai-audio-visualizer::part(cell highlighted) { background: var(--brand) }"
616
+ },
617
+ {
618
+ "name": "highlighted",
619
+ "doc": "A second part TOKEN present on a `bar` or `cell` exactly when the sequencer or live audio has it lit, not a standalone styleable element. Combine it in the same `::part()` argument: `::part(bar highlighted)` or `::part(cell highlighted)`. This is the external equivalent of the internal `data-kai-highlighted=\"true\"` attribute, which a `::part()` selector cannot reach (an attribute selector cannot follow a pseudo-element).",
620
+ "recipe": "kai-audio-visualizer::part(bar highlighted) { background: var(--brand) }\nkai-audio-visualizer::part(cell highlighted) { background: var(--brand) }"
621
+ },
622
+ {
623
+ "name": "canvas",
624
+ "doc": "The WebGL canvas backing the `wave` and `aurora` variants. Restyle its size or radius, or layer a mask/filter, from outside.",
625
+ "recipe": "kai-audio-visualizer::part(canvas) { border-radius: 0.75rem }"
626
+ }
627
+ ]
628
+ },
459
629
  {
460
630
  "tag": "kai-avatar",
461
631
  "className": "KaiAvatarElement",
@@ -5200,6 +5370,15 @@
5200
5370
  "description": "Which item index is maximized (null = none). Declarative source of truth.",
5201
5371
  "default": "null",
5202
5372
  "displayType": "undefined | null | number"
5373
+ },
5374
+ {
5375
+ "name": "handle",
5376
+ "type": "undefined | \"none\" | \"line\" | \"grip\"",
5377
+ "optional": true,
5378
+ "scalar": true,
5379
+ "description": "Divider affordance drawn inside each draggable handle's 8px grab zone: - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag. - `grip` — a dotted grip handle. - `none` — no visible divider, just the invisible hit-area. The full grab zone and keyboard/ARIA behavior are identical for all three.",
5380
+ "default": "'line'",
5381
+ "displayType": "undefined | \"none\" | \"line\" | \"grip\""
5203
5382
  }
5204
5383
  ],
5205
5384
  "events": [
@@ -6768,6 +6947,114 @@
6768
6947
  ],
6769
6948
  "tokens": []
6770
6949
  },
6950
+ {
6951
+ "tag": "kai-thread",
6952
+ "className": "KaiThreadElement",
6953
+ "displayName": "Thread",
6954
+ "props": [
6955
+ {
6956
+ "name": "messages",
6957
+ "type": "undefined | { id: string; role: \"user\" | \"assistant\"; content: string; reasoning?: undefined | { text: string; label?: undefined | string }; tools?: undefined | { type: string; state: \"input-streaming\" | \"input-available\" | \"output-available\" | \"output-error\"; input?: undefined | Record<string, unknown>; output?: undefined | Record<string, unknown>; toolCallId?: undefined | string; errorText?: undefined | string }[]; attachments?: undefined | { id: string; type: \"file\" | \"source-document\"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[]; actions?: undefined | (\"copy\" | \"like\" | \"dislike\" | \"regenerate\" | \"edit\" | { id: string; label: string; icon?: undefined | string; tooltip?: undefined | string })[]; avatar?: undefined | { src?: undefined | string; fallback?: undefined | string; alt?: undefined | string }; feedback?: undefined | \"like\" | \"dislike\" }[]",
6958
+ "optional": true,
6959
+ "scalar": false,
6960
+ "description": "The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions/avatar. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not).",
6961
+ "displayType": "undefined | { id: string; role: \"user\" | \"assistant\"; content: string; reasoning?: undefined | { text: string; label?: undefined | string }; tools?: undefined | { type: string; state: \"input-streaming\" | \"input-available\" | \"output-available\" | \"output-error\"; input?: undefined | Record<string, unknown>; output?: undefined | Record<string, unknown>; toolCallId?: undefined | string; errorText?: undefined | string }[]; attachments?: undefined | { id: string; type: \"file\" | \"source-document\"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[]; actions?: undefined | (\"copy\" | \"like\" | \"dislike\" | \"regenerate\" | \"edit\" | { id: string; label: string; icon?: undefined | string; tooltip?: undefined | string })[]; avatar?: undefined | { src?: undefined | string; fallback?: undefined | string; alt?: undefined | string }; feedback?: undefined | \"like\" | \"dislike\" }[]"
6962
+ },
6963
+ {
6964
+ "name": "loading",
6965
+ "type": "undefined | false | true",
6966
+ "optional": true,
6967
+ "scalar": true,
6968
+ "description": "Show a typing indicator on the pending assistant turn — set while awaiting the assistant's reply.",
6969
+ "default": "false",
6970
+ "displayType": "undefined | false | true"
6971
+ },
6972
+ {
6973
+ "name": "proseSize",
6974
+ "type": "undefined | \"sm\" | \"lg\" | \"xs\" | \"base\"",
6975
+ "optional": true,
6976
+ "scalar": true,
6977
+ "description": "Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`.",
6978
+ "default": "'sm'",
6979
+ "displayType": "undefined | \"sm\" | \"lg\" | \"xs\" | \"base\""
6980
+ },
6981
+ {
6982
+ "name": "codeTheme",
6983
+ "type": "undefined | string",
6984
+ "optional": true,
6985
+ "scalar": true,
6986
+ "description": "Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`).",
6987
+ "default": "'github-dark-dimmed'",
6988
+ "displayType": "undefined | string"
6989
+ },
6990
+ {
6991
+ "name": "codeHighlight",
6992
+ "type": "undefined | false | true",
6993
+ "optional": true,
6994
+ "scalar": true,
6995
+ "description": "Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true.",
6996
+ "default": "true",
6997
+ "displayType": "undefined | false | true"
6998
+ },
6999
+ {
7000
+ "name": "actionsReveal",
7001
+ "type": "undefined | \"always\" | \"hover\"",
7002
+ "optional": true,
7003
+ "scalar": true,
7004
+ "description": "Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`).",
7005
+ "default": "'always'",
7006
+ "displayType": "undefined | \"always\" | \"hover\""
7007
+ },
7008
+ {
7009
+ "name": "scrollButton",
7010
+ "type": "undefined | false | true",
7011
+ "optional": true,
7012
+ "scalar": true,
7013
+ "description": "Show the scroll-to-bottom button inside the scroll area. Default true.",
7014
+ "default": "true",
7015
+ "displayType": "undefined | false | true"
7016
+ },
7017
+ {
7018
+ "name": "class",
7019
+ "type": "undefined | string",
7020
+ "optional": true,
7021
+ "scalar": true,
7022
+ "description": "Extra classes applied to the thread's inner root.",
7023
+ "displayType": "undefined | string"
7024
+ }
7025
+ ],
7026
+ "events": [
7027
+ {
7028
+ "name": "kai-message-action",
7029
+ "detail": "{ messageId: string; action: string; state?: undefined | \"on\" | \"off\" }",
7030
+ "description": "A message's action button was clicked. `action` is the built-in name (`copy` / `like` / `dislike` / `regenerate` / `edit`) or a custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear.",
7031
+ "displayDetail": "{ messageId: string; action: string; state?: undefined | \"on\" | \"off\" }"
7032
+ }
7033
+ ],
7034
+ "methods": [
7035
+ {
7036
+ "name": "scrollToBottom",
7037
+ "params": "behavior?: ScrollBehavior",
7038
+ "returns": "void",
7039
+ "description": "Scroll the message list to the bottom (default `'smooth'`)."
7040
+ }
7041
+ ],
7042
+ "composedFrom": [
7043
+ {
7044
+ "name": "Thread",
7045
+ "group": "Components",
7046
+ "storyId": "solid-advanced-elements-thread--docs"
7047
+ }
7048
+ ],
7049
+ "tokens": [],
7050
+ "slots": [
7051
+ {
7052
+ "name": "empty",
7053
+ "mode": "replace",
7054
+ "doc": "Custom zero-state rendered in the message area while the thread is empty; replaces the built-in default."
7055
+ }
7056
+ ]
7057
+ },
6771
7058
  {
6772
7059
  "tag": "kai-toast-region",
6773
7060
  "className": "KaiToastRegionElement",
@@ -164,6 +164,43 @@ export interface KaiAttachmentsElement extends HTMLElement {
164
164
  emptyText?: string;
165
165
  }
166
166
 
167
+ export interface KaiAudioVisualizerElement extends HTMLElement {
168
+ /** Color mode (`auto` follows prefers-color-scheme). */
169
+ theme?: 'light' | 'dark' | 'auto';
170
+ /** Look to render: `bar` (default), `grid`, `radial`, `wave`, `aurora`, `custom`. `aura` is accepted as a LiveKit-markup alias for `aurora`. Attribute: `variant`. */
171
+ variant?: string;
172
+ /** `idle` (default), `connecting`, `listening`, `thinking`, `speaking`, `disconnected` (connection down: the dead, flat look). LiveKit's room-lifecycle state names are accepted as aliases. Attribute: `state`. */
173
+ state?: string;
174
+ /** `icon` | `sm` | `md` (default) | `lg` | `xl`. Attribute: `size`. */
175
+ size?: string;
176
+ /** Bars to draw. Bar and radial only. Attribute: `bar-count`. */
177
+ barCount?: number;
178
+ /** Grid only: rows and columns of the (always square) grid. Attribute: `count`. */
179
+ count?: number;
180
+ /** Radial only: ring distance from center, in px. Attribute: `radius`. */
181
+ radius?: number;
182
+ /** Grid only: ring distance for the connecting animation, in cells. Attribute: `spread`. */
183
+ spread?: number;
184
+ /** Grid only: ms between scripted frames. Attribute: `interval`. */
185
+ interval?: number;
186
+ /** CSS color for the geometry, overriding the inherited `currentColor`. Attribute: `color`. */
187
+ color?: string;
188
+ /** Shader variants only: pattern density, 0..1. Attribute: `complexity`. */
189
+ complexity?: number;
190
+ /** Setting this makes the element an announced image (`role="img"`) instead of decorative (`aria-hidden`). Attribute: `label`. */
191
+ label?: string;
192
+ /** Live microphone or WebRTC audio to analyze. JS property only. */
193
+ stream?: MediaStream;
194
+ /** An `<audio>` or `<video>` element to tap for its audio. JS property only. */
195
+ audioElement?: HTMLMediaElement;
196
+ /** Pre-computed levels, 0..1. Set this and no AudioContext is ever built, which is what keeps headless/SSR rendering and browser-speech-synthesis playback (which exposes no audio node) free of Web Audio entirely. JS property only. A new array reference is required for each update; mutating the existing array in place will not re-render. */
197
+ bands?: number[];
198
+ /** Custom fragment shader for `variant="custom"`. JS property only. */
199
+ shader?: { fragment: string; uniforms?: Record<string, { type: "1f" | "1i" | "1fv" | "2f" | "3f" | "3fv" | "4f" | "4fv" | "Matrix2fv" | "Matrix3fv" | "Matrix4fv"; value: number | number[] }> };
200
+ /** Shader variants only: keep animating while scrolled off screen. Off by default, which stops drawing and releases the WebGL context until the element comes back (browsers ration contexts to roughly 16 a page). Does not override `prefers-reduced-motion`. Attribute: `animate-when-not-visible`. */
201
+ animateWhenNotVisible?: boolean;
202
+ }
203
+
167
204
  export interface KaiAvatarElement extends HTMLElement {
168
205
  /** Color mode (`auto` follows prefers-color-scheme). */
169
206
  theme?: 'light' | 'dark' | 'auto';
@@ -923,6 +960,8 @@ export interface KaiResizableElement extends HTMLElement {
923
960
  orientation?: "vertical" | "horizontal";
924
961
  /** Which item index is maximized (null = none). Declarative source of truth. */
925
962
  maximizedIndex?: null | number;
963
+ /** Divider affordance drawn inside each draggable handle's 8px grab zone: - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag. - `grip` — a dotted grip handle. - `none` — no visible divider, just the invisible hit-area. The full grab zone and keyboard/ARIA behavior are identical for all three. */
964
+ handle?: "none" | "line" | "grip";
926
965
  }
927
966
 
928
967
  export interface KaiResizableItemElement extends HTMLElement {
@@ -1213,6 +1252,27 @@ export interface KaiThinkingBarElement extends HTMLElement {
1213
1252
  stopLabel?: string;
1214
1253
  }
1215
1254
 
1255
+ export interface KaiThreadElement extends HTMLElement {
1256
+ /** Color mode (`auto` follows prefers-color-scheme). */
1257
+ theme?: 'light' | 'dark' | 'auto';
1258
+ /** The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions/avatar. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not). */
1259
+ messages?: { id: string; role: "user" | "assistant"; content: string; reasoning?: { text: string; label?: string }; tools?: { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; output?: Record<string, unknown>; toolCallId?: string; errorText?: string }[]; attachments?: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }[]; actions?: ("copy" | "like" | "dislike" | "regenerate" | "edit" | { id: string; label: string; icon?: string; tooltip?: string })[]; avatar?: { src?: string; fallback?: string; alt?: string }; feedback?: "like" | "dislike" }[];
1260
+ /** Show a typing indicator on the pending assistant turn — set while awaiting the assistant's reply. */
1261
+ loading?: boolean;
1262
+ /** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`. */
1263
+ proseSize?: "sm" | "lg" | "xs" | "base";
1264
+ /** Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). */
1265
+ codeTheme?: string;
1266
+ /** Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. */
1267
+ codeHighlight?: boolean;
1268
+ /** Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`). */
1269
+ actionsReveal?: "always" | "hover";
1270
+ /** Show the scroll-to-bottom button inside the scroll area. Default true. */
1271
+ scrollButton?: boolean;
1272
+ /** Extra classes applied to the thread's inner root. */
1273
+ class?: string;
1274
+ }
1275
+
1216
1276
  export interface KaiToastRegionElement extends HTMLElement {
1217
1277
  /** Color mode (`auto` follows prefers-color-scheme). */
1218
1278
  theme?: 'light' | 'dark' | 'auto';
@@ -1343,6 +1403,7 @@ declare global {
1343
1403
  'kai-agent-card': KaiAgentCardElement;
1344
1404
  'kai-artifact': KaiArtifactElement;
1345
1405
  'kai-attachments': KaiAttachmentsElement;
1406
+ 'kai-audio-visualizer': KaiAudioVisualizerElement;
1346
1407
  'kai-avatar': KaiAvatarElement;
1347
1408
  'kai-badge': KaiBadgeElement;
1348
1409
  'kai-button': KaiButtonElement;
@@ -1412,6 +1473,7 @@ declare global {
1412
1473
  'kai-tasks': KaiTasksElement;
1413
1474
  'kai-text-shimmer': KaiTextShimmerElement;
1414
1475
  'kai-thinking-bar': KaiThinkingBarElement;
1476
+ 'kai-thread': KaiThreadElement;
1415
1477
  'kai-toast-region': KaiToastRegionElement;
1416
1478
  'kai-tool': KaiToolElement;
1417
1479
  'kai-tooltip': KaiTooltipElement;
@@ -30,6 +30,7 @@
30
30
  "message-square",
31
31
  "mic",
32
32
  "monitor",
33
+ "moon",
33
34
  "more-horizontal",
34
35
  "panel-left",
35
36
  "paperclip",
@@ -43,6 +44,7 @@
43
44
  "smile",
44
45
  "sparkles",
45
46
  "square-pen",
47
+ "sun",
46
48
  "workflow",
47
49
  "x"
48
50
  ]
@@ -79,12 +79,15 @@ interface Events {
79
79
  /**
80
80
  * `<kai-menu>` — a cascading action menu driven by a JSON items-tree.
81
81
  *
82
- * Set the `items` property in JavaScript (array, not attribute). The trigger
83
- * is slotted put your icon/button content in `slot="trigger"`:
82
+ * Set the `items` property in JavaScript (array, not attribute). The menu
83
+ * provides its own trigger button (accessible + keyboard-wired); slot only the
84
+ * VISUAL content into `slot="trigger"` (an icon, text, an `<svg>`) — never your
85
+ * own `<button>`/`<a>`, which would nest interactive elements. Set the
86
+ * accessible name with the `label` attribute (for an icon-only trigger):
84
87
  *
85
88
  * ```html
86
- * <kai-menu>
87
- * <button slot="trigger" aria-label="Open menu">+</button>
89
+ * <kai-menu label="Open menu">
90
+ * <span slot="trigger">+</span>
88
91
  * </kai-menu>
89
92
  * <script type="module">
90
93
  * import '@kitn.ai/ui/elements';
@@ -7,6 +7,8 @@ import './conversation-list';
7
7
  import './prompt-input';
8
8
  import './chat';
9
9
  import './chat-workspace';
10
+ // Message-list composable (the scrolling thread slice, sans composer/header)
11
+ import './thread';
10
12
  // Composable leaf elements (spike — see docs/handoff + examples/composable)
11
13
  import './thinking-bar';
12
14
  import './model-switcher';
@@ -25,6 +27,7 @@ import './chat-scope-picker';
25
27
  import './prompt-suggestions';
26
28
  import './file-upload';
27
29
  import './voice-input';
30
+ import './audio-visualizer';
28
31
  // Phase 4 — indicators & leaves
29
32
  import './loader';
30
33
  import './text-shimmer';
@@ -55,6 +55,14 @@ interface GroupProps extends Record<string, unknown> {
55
55
  orientation?: Orientation;
56
56
  /** Which item index is maximized (null = none). Declarative source of truth. */
57
57
  maximizedIndex?: number | null;
58
+ /**
59
+ * Divider affordance drawn inside each draggable handle's 8px grab zone:
60
+ * - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag.
61
+ * - `grip` — a dotted grip handle.
62
+ * - `none` — no visible divider, just the invisible hit-area.
63
+ * The full grab zone and keyboard/ARIA behavior are identical for all three.
64
+ */
65
+ handle?: 'line' | 'grip' | 'none';
58
66
  }
59
67
 
60
68
  interface GroupEvents extends Record<string, unknown> {
@@ -75,6 +83,7 @@ interface GroupEvents extends Record<string, unknown> {
75
83
  defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
76
84
  orientation: 'horizontal',
77
85
  maximizedIndex: null,
86
+ handle: 'line',
78
87
  }, (props, { element, dispatch }) => {
79
88
  const [items, setItems] = createSignal<ItemInfo[]>([]);
80
89
  const orientation = (): Orientation => (props.orientation === 'vertical' ? 'vertical' : 'horizontal');
@@ -452,7 +461,7 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
452
461
  <>
453
462
  <Show when={i() > 0}>
454
463
  <ResizableHandle
455
- withHandle
464
+ handle={props.handle ?? 'line'}
456
465
  orientation={orientation()}
457
466
  static={isStatic()}
458
467
  onPanelResize={() => {
@@ -516,6 +525,68 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
516
525
  );
517
526
  });
518
527
 
528
+ /**
529
+ * Reflect a `<kai-resizable-item>`'s reactive CONFIG props to the ATTRIBUTES the
530
+ * parent `<kai-resizable>` reads (`readItems()` + its `size`/`min`/`max`/`locked`
531
+ * MutationObserver). Framework runtimes (React et al.) assign these as DOM
532
+ * *properties* — `el.size = "280px"` — but component-register only mirrors
533
+ * attribute→property, never the reverse, so a property-set size never becomes an
534
+ * attribute and the parent silently falls back to a flexible split. This is the
535
+ * exact class of bug the `collapsed` reflection already fixed; extended here to
536
+ * `size`/`min`/`max` (strings) and `locked` (boolean). `hidden` is deliberately
537
+ * NOT reflected — native `el.hidden` reflects to the attribute on its own and the
538
+ * parent honours the `hidden` property directly (and maximize drives `hidden`).
539
+ *
540
+ * Durability (don't fight drags): the parent WRITES `size` back to the live percent
541
+ * during a drag (`persistSizes` → `setAttribute('size', …)`), and that attribute
542
+ * write back-propagates into this prop's signal (component-register's
543
+ * attributeChangedCallback → property → the solid-element prop signal). A naive
544
+ * "reflect on every signal change" effect would therefore re-stomp the dragged size
545
+ * the next time a framework re-render re-asserts the ORIGINAL declared `size`
546
+ * (e.g. React re-runs `el.size = "280px"` on every render). So each string reflect
547
+ * only writes when the value the CONSUMER declared actually changes: a signal value
548
+ * that already equals the current attribute came FROM an attribute write (a drag),
549
+ * not the consumer, so it is left untouched and is not adopted as the new baseline.
550
+ * Net effect: a dragged size survives re-renders, while an explicit `size` change
551
+ * still applies. `size` prop signals use Solid's DEFAULT (same-value-skipping)
552
+ * equality — this guard is needed on top of that only because of the drag-write
553
+ * back-propagation, which same-value-skip alone can't see.
554
+ *
555
+ * Exported for unit testing (the live Shadow-DOM element is not jsdom-friendly).
556
+ */
557
+ export function reflectItemConfig(
558
+ element: HTMLElement,
559
+ props: { readonly size?: string; readonly min?: string; readonly max?: string },
560
+ flag: (name: string) => boolean,
561
+ ) {
562
+ const norm = (v: unknown): string | null => (v == null || v === '' ? null : String(v));
563
+ const reflectString = (name: 'size' | 'min' | 'max') => {
564
+ // Last value the CONSUMER declared (distinct from a value the parent wrote to
565
+ // the attribute mid-drag). Starts `undefined`, which `norm` never returns, so
566
+ // the first genuine value always reflects.
567
+ let lastConsumer: string | null | undefined;
568
+ createEffect(() => {
569
+ const desired = norm(props[name]); // reactive read
570
+ if (desired === lastConsumer) return; // consumer intent unchanged → leave the attr (the drag owns it)
571
+ const current = element.getAttribute(name);
572
+ // Already matches — either untouched or the parent wrote it (drag). Don't
573
+ // adopt it as the consumer baseline and don't fight it.
574
+ if (desired === current) return;
575
+ lastConsumer = desired;
576
+ if (desired === null) element.removeAttribute(name);
577
+ else element.setAttribute(name, desired);
578
+ });
579
+ };
580
+ reflectString('size');
581
+ reflectString('min');
582
+ reflectString('max');
583
+ // Boolean: mirror the `collapsed`/`loading` precedent — a bare `locked` attribute
584
+ // and the IDL property are both honoured via `flag`. The parent also writes
585
+ // `locked` during maximize/restore; because `flag` re-derives from that same
586
+ // attribute state, the reflection stays self-consistent (no fight).
587
+ createEffect(() => { element.toggleAttribute('locked', flag('locked')); });
588
+ }
589
+
519
590
  interface ItemProps extends Record<string, unknown> {
520
591
  /** Initial main-axis size: `"280px"` (fixed) or `"25%"`/`25` (percent). Omitted → flexible. */
521
592
  size?: string;
@@ -551,7 +622,7 @@ defineWebComponent<ItemProps>('kai-resizable-item', {
551
622
  locked: false,
552
623
  hidden: false,
553
624
  collapsed: false,
554
- }, (_props, { element, flag }) => {
625
+ }, (props, { element, flag }) => {
555
626
  // Reflect the reactive `collapsed` prop to the `collapsed` ATTRIBUTE the parent
556
627
  // <kai-resizable> reads (and observes). This is what makes a bare JSX boolean
557
628
  // collapse the panel at INITIAL render from any framework: component-register
@@ -563,6 +634,11 @@ defineWebComponent<ItemProps>('kai-resizable-item', {
563
634
  // `hidden`, so the back-compat hidden path and maximize (which drive `hidden`)
564
635
  // are untouched.
565
636
  createEffect(() => { element.toggleAttribute('collapsed', flag('collapsed')); });
637
+ // Reflect the other config props (`size`/`min`/`max`/`locked`) property→attribute
638
+ // for the SAME reason — a framework that sets them as DOM properties (React et al.)
639
+ // would otherwise be invisible to the parent. See reflectItemConfig for the
640
+ // drag-durability guard on `size`.
641
+ reflectItemConfig(element, props, flag);
566
642
  return (
567
643
  <>
568
644
  {/* The item host fills the panel's single grid cell (the panel stretches it on