@kitn.ai/ui 0.28.0 → 0.29.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 (376) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-tooling/construct/public.d.ts +15 -0
  3. package/dist/agent-tooling/construct/schema.d.ts +111 -0
  4. package/dist/construct-cli.es.js +3 -3
  5. package/dist/construct.d.ts +5 -0
  6. package/dist/construct.js +327 -0
  7. package/dist/core-C8fzo39E.js +1 -12
  8. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  9. package/dist/mcp.es.js +1 -1
  10. package/package.json +16 -16
  11. package/bin/route.test.js +0 -58
  12. package/src/agent-tooling/README.md +0 -192
  13. package/src/agent-tooling/archetypes.ts +0 -205
  14. package/src/agent-tooling/catalog/README.md +0 -676
  15. package/src/agent-tooling/catalog/catalog-types.ts +0 -231
  16. package/src/agent-tooling/catalog/fabrications.ts +0 -96
  17. package/src/agent-tooling/catalog/invariants.ts +0 -284
  18. package/src/agent-tooling/catalog/labs-titles.ts +0 -114
  19. package/src/agent-tooling/catalog/scenarios.ts +0 -87
  20. package/src/agent-tooling/catalog/surfaces.ts +0 -347
  21. package/src/agent-tooling/construct/cli-entry.ts +0 -8
  22. package/src/agent-tooling/construct/cli.ts +0 -147
  23. package/src/agent-tooling/construct/codegen.ts +0 -1635
  24. package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
  25. package/src/agent-tooling/construct/dev.ts +0 -148
  26. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
  27. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
  28. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
  29. package/src/agent-tooling/construct/schema.ts +0 -452
  30. package/src/agent-tooling/integrations/anthropic.ts +0 -442
  31. package/src/agent-tooling/integrations/cloudflare.ts +0 -218
  32. package/src/agent-tooling/integrations/langgraph.ts +0 -133
  33. package/src/agent-tooling/integrations/mastra.ts +0 -182
  34. package/src/agent-tooling/integrations/mock.ts +0 -148
  35. package/src/agent-tooling/integrations/ollama.ts +0 -97
  36. package/src/agent-tooling/integrations/openai.ts +0 -95
  37. package/src/agent-tooling/integrations/openrouter.ts +0 -82
  38. package/src/agent-tooling/integrations/pi.ts +0 -176
  39. package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
  40. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
  41. package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
  42. package/src/agent-tooling/mcp/manifest.ts +0 -422
  43. package/src/agent-tooling/mcp/server.ts +0 -116
  44. package/src/agent-tooling/mcp/stdio.ts +0 -18
  45. package/src/agent-tooling/mcp/tools/construct.ts +0 -130
  46. package/src/agent-tooling/mcp/tools/debug.ts +0 -621
  47. package/src/agent-tooling/mcp/tools/reference.ts +0 -980
  48. package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
  49. package/src/agent-tooling/mcp/tools/theme.ts +0 -456
  50. package/src/agent-tooling/mcp/tools/types.ts +0 -14
  51. package/src/agent-tooling/mcp/validate-args.ts +0 -141
  52. package/src/agent-tooling/recipes/composed-thread.ts +0 -714
  53. package/src/agent-tooling/recipes/index.ts +0 -22
  54. package/src/agent-tooling/recipes/types.ts +0 -32
  55. package/src/agent-tooling/registry.ts +0 -132
  56. package/src/agent-tooling/route-emit.ts +0 -305
  57. package/src/agent-tooling/types.ts +0 -390
  58. package/src/components/artifact-card.tsx +0 -120
  59. package/src/components/artifact.tsx +0 -914
  60. package/src/components/attachment-types.ts +0 -66
  61. package/src/components/attachments.tsx +0 -536
  62. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
  63. package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
  64. package/src/components/audio-visualizer/fit-scale.ts +0 -112
  65. package/src/components/audio-visualizer/index.tsx +0 -528
  66. package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
  67. package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
  68. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
  69. package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
  70. package/src/components/audio-visualizer/sizes.ts +0 -54
  71. package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
  72. package/src/components/audio-visualizer/variant-bar.tsx +0 -175
  73. package/src/components/audio-visualizer/variant-custom.tsx +0 -344
  74. package/src/components/audio-visualizer/variant-grid.tsx +0 -175
  75. package/src/components/audio-visualizer/variant-radial.tsx +0 -163
  76. package/src/components/audio-visualizer/variant-wave.tsx +0 -130
  77. package/src/components/audio-visualizer/wave.glsl.ts +0 -130
  78. package/src/components/card-fallback.tsx +0 -60
  79. package/src/components/card-renderer.tsx +0 -220
  80. package/src/components/card.tsx +0 -212
  81. package/src/components/chain-of-thought.tsx +0 -348
  82. package/src/components/chat-container.tsx +0 -89
  83. package/src/components/chat-scope-picker.tsx +0 -57
  84. package/src/components/chat-thread.tsx +0 -1014
  85. package/src/components/checkpoint.tsx +0 -88
  86. package/src/components/choice-card.tsx +0 -743
  87. package/src/components/coachmark.tsx +0 -249
  88. package/src/components/code-block.tsx +0 -181
  89. package/src/components/composer-dom.ts +0 -159
  90. package/src/components/composer-highlight.ts +0 -242
  91. package/src/components/composer-history.ts +0 -82
  92. package/src/components/composer.tsx +0 -1075
  93. package/src/components/confirm-card.tsx +0 -402
  94. package/src/components/context.tsx +0 -377
  95. package/src/components/conversation-item.tsx +0 -241
  96. package/src/components/conversation-list.tsx +0 -429
  97. package/src/components/conversation-panel.tsx +0 -118
  98. package/src/components/dismissed-stub.tsx +0 -80
  99. package/src/components/embed.tsx +0 -200
  100. package/src/components/empty.tsx +0 -177
  101. package/src/components/feedback-bar.tsx +0 -166
  102. package/src/components/file-tree.tsx +0 -530
  103. package/src/components/file-upload.tsx +0 -167
  104. package/src/components/form-widgets.tsx +0 -525
  105. package/src/components/form.tsx +0 -1296
  106. package/src/components/home-panel.tsx +0 -159
  107. package/src/components/image.tsx +0 -65
  108. package/src/components/link-preview.tsx +0 -195
  109. package/src/components/loader.tsx +0 -367
  110. package/src/components/markdown.tsx +0 -162
  111. package/src/components/message-skills.tsx +0 -40
  112. package/src/components/message.tsx +0 -765
  113. package/src/components/model-switcher.tsx +0 -132
  114. package/src/components/prompt-input.tsx +0 -188
  115. package/src/components/prompt-suggestion.tsx +0 -150
  116. package/src/components/reasoning.tsx +0 -293
  117. package/src/components/response-compare-types.ts +0 -152
  118. package/src/components/response-compare.tsx +0 -455
  119. package/src/components/response-stream.tsx +0 -109
  120. package/src/components/screen.tsx +0 -181
  121. package/src/components/scroll-button.tsx +0 -93
  122. package/src/components/source.tsx +0 -192
  123. package/src/components/tasks-card.tsx +0 -801
  124. package/src/components/text-shimmer.tsx +0 -37
  125. package/src/components/thinking-bar.tsx +0 -50
  126. package/src/components/thread.tsx +0 -228
  127. package/src/components/toast.tsx +0 -503
  128. package/src/components/tool-classify.ts +0 -26
  129. package/src/components/tool-types.ts +0 -49
  130. package/src/components/tool.tsx +0 -190
  131. package/src/components/use-card-resolution.ts +0 -44
  132. package/src/components/voice-input.tsx +0 -222
  133. package/src/components/voice-output.tsx +0 -242
  134. package/src/components/widget-tab-bar.tsx +0 -73
  135. package/src/components/workspace-shell.tsx +0 -285
  136. package/src/diagnostics/hook.ts +0 -352
  137. package/src/diagnostics/index.ts +0 -82
  138. package/src/diagnostics/report-request.ts +0 -296
  139. package/src/elements/agent-card.tsx +0 -67
  140. package/src/elements/artifact.tsx +0 -194
  141. package/src/elements/attachments.tsx +0 -140
  142. package/src/elements/audio-visualizer.tsx +0 -227
  143. package/src/elements/autoloader.ts +0 -89
  144. package/src/elements/avatar.tsx +0 -33
  145. package/src/elements/badge.tsx +0 -32
  146. package/src/elements/button.tsx +0 -149
  147. package/src/elements/card.tsx +0 -133
  148. package/src/elements/cards.tsx +0 -266
  149. package/src/elements/chain-of-thought.tsx +0 -141
  150. package/src/elements/chat-scope-picker.tsx +0 -65
  151. package/src/elements/chat-types.ts +0 -102
  152. package/src/elements/chat-workspace.tsx +0 -188
  153. package/src/elements/chat.tsx +0 -304
  154. package/src/elements/checkbox-group.tsx +0 -164
  155. package/src/elements/checkbox.tsx +0 -134
  156. package/src/elements/checkpoint.tsx +0 -47
  157. package/src/elements/choice.tsx +0 -100
  158. package/src/elements/coachmark.tsx +0 -90
  159. package/src/elements/code-block.tsx +0 -60
  160. package/src/elements/command.tsx +0 -197
  161. package/src/elements/compare.tsx +0 -111
  162. package/src/elements/compiled.css +0 -2
  163. package/src/elements/composer.tsx +0 -162
  164. package/src/elements/confirm-card.tsx +0 -76
  165. package/src/elements/context-meter.tsx +0 -107
  166. package/src/elements/conversation-item.tsx +0 -120
  167. package/src/elements/conversation-list.tsx +0 -203
  168. package/src/elements/css.ts +0 -5
  169. package/src/elements/default-input.tsx +0 -392
  170. package/src/elements/define-entry.ts +0 -12
  171. package/src/elements/define.tsx +0 -563
  172. package/src/elements/diagnostic-events.ts +0 -114
  173. package/src/elements/dialog.tsx +0 -145
  174. package/src/elements/disclosure.ts +0 -95
  175. package/src/elements/dock.tsx +0 -215
  176. package/src/elements/dropdown.tsx +0 -123
  177. package/src/elements/editable-label.tsx +0 -140
  178. package/src/elements/element-data-types.ts +0 -152
  179. package/src/elements/element-diagnostics.ts +0 -392
  180. package/src/elements/element-manifest.json +0 -354
  181. package/src/elements/element-nonscalar.json +0 -187
  182. package/src/elements/element-types.d.ts +0 -4353
  183. package/src/elements/embed.tsx +0 -35
  184. package/src/elements/empty.tsx +0 -29
  185. package/src/elements/feedback-bar.tsx +0 -66
  186. package/src/elements/file-tree.tsx +0 -62
  187. package/src/elements/file-upload.tsx +0 -44
  188. package/src/elements/form.tsx +0 -94
  189. package/src/elements/hover-card.tsx +0 -80
  190. package/src/elements/icon.tsx +0 -41
  191. package/src/elements/image.tsx +0 -32
  192. package/src/elements/input.tsx +0 -492
  193. package/src/elements/kbd.tsx +0 -45
  194. package/src/elements/link-preview.tsx +0 -34
  195. package/src/elements/loader.tsx +0 -25
  196. package/src/elements/markdown.tsx +0 -38
  197. package/src/elements/menu.tsx +0 -241
  198. package/src/elements/message-skills.tsx +0 -83
  199. package/src/elements/message.tsx +0 -361
  200. package/src/elements/model-switcher.tsx +0 -121
  201. package/src/elements/nav.tsx +0 -87
  202. package/src/elements/notice.tsx +0 -53
  203. package/src/elements/pane-grid.tsx +0 -111
  204. package/src/elements/pane-group.tsx +0 -119
  205. package/src/elements/pane.tsx +0 -129
  206. package/src/elements/popover.tsx +0 -81
  207. package/src/elements/progress-bar.tsx +0 -39
  208. package/src/elements/prompt-dock.tsx +0 -82
  209. package/src/elements/prompt-input.tsx +0 -240
  210. package/src/elements/prompt-suggestions.tsx +0 -113
  211. package/src/elements/radio-group.tsx +0 -129
  212. package/src/elements/reasoning.tsx +0 -71
  213. package/src/elements/register-impl.ts +0 -148
  214. package/src/elements/register.ts +0 -76
  215. package/src/elements/remote.tsx +0 -225
  216. package/src/elements/resizable.globals.d.ts +0 -36
  217. package/src/elements/resizable.tsx +0 -704
  218. package/src/elements/response-stream.tsx +0 -40
  219. package/src/elements/screen.tsx +0 -90
  220. package/src/elements/scroll-area.tsx +0 -33
  221. package/src/elements/scroll-button.tsx +0 -179
  222. package/src/elements/search.tsx +0 -185
  223. package/src/elements/segmented.tsx +0 -129
  224. package/src/elements/select.tsx +0 -165
  225. package/src/elements/separator.tsx +0 -36
  226. package/src/elements/setting-item.tsx +0 -50
  227. package/src/elements/settings-group.tsx +0 -37
  228. package/src/elements/skeleton.tsx +0 -45
  229. package/src/elements/slider.tsx +0 -171
  230. package/src/elements/slot-text.ts +0 -73
  231. package/src/elements/slots.ts +0 -787
  232. package/src/elements/source.tsx +0 -126
  233. package/src/elements/status.tsx +0 -42
  234. package/src/elements/styles.css +0 -328
  235. package/src/elements/switch.tsx +0 -118
  236. package/src/elements/tabs.tsx +0 -95
  237. package/src/elements/tasks.tsx +0 -103
  238. package/src/elements/text-shimmer.tsx +0 -28
  239. package/src/elements/thinking-bar.tsx +0 -43
  240. package/src/elements/thread.tsx +0 -139
  241. package/src/elements/toast.tsx +0 -106
  242. package/src/elements/tool.tsx +0 -78
  243. package/src/elements/tooltip.tsx +0 -85
  244. package/src/elements/validate-messages.ts +0 -76
  245. package/src/elements/voice-input.tsx +0 -100
  246. package/src/elements/voice-output.tsx +0 -82
  247. package/src/index.ts +0 -430
  248. package/src/primitives/audio-bands.ts +0 -174
  249. package/src/primitives/card-component-types.ts +0 -63
  250. package/src/primitives/card-contract.ts +0 -80
  251. package/src/primitives/card-data-types.ts +0 -361
  252. package/src/primitives/card-host.tsx +0 -37
  253. package/src/primitives/card-recovery.ts +0 -160
  254. package/src/primitives/card-registry.tsx +0 -78
  255. package/src/primitives/card-resolution.ts +0 -36
  256. package/src/primitives/card-routing.ts +0 -85
  257. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  258. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  259. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  260. package/src/primitives/card-schemas/choice.schema.json +0 -75
  261. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  262. package/src/primitives/card-schemas/embed.schema.json +0 -63
  263. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  264. package/src/primitives/card-schemas/form.schema.json +0 -53
  265. package/src/primitives/card-schemas/link.schema.json +0 -54
  266. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  267. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  268. package/src/primitives/card-tags.ts +0 -61
  269. package/src/primitives/card-validate-cards.ts +0 -255
  270. package/src/primitives/card-validate-schemas.ts +0 -58
  271. package/src/primitives/card-validate.ts +0 -131
  272. package/src/primitives/chat-config.tsx +0 -103
  273. package/src/primitives/composer-model.ts +0 -49
  274. package/src/primitives/composer-triggers.ts +0 -35
  275. package/src/primitives/controllable.ts +0 -21
  276. package/src/primitives/conversation-store.ts +0 -248
  277. package/src/primitives/create-kai-chat.ts +0 -66
  278. package/src/primitives/create-tween.ts +0 -266
  279. package/src/primitives/embed-providers.ts +0 -254
  280. package/src/primitives/field-mask.ts +0 -256
  281. package/src/primitives/field-semantics.ts +0 -115
  282. package/src/primitives/highlighter.ts +0 -157
  283. package/src/primitives/input-mask.ts +0 -853
  284. package/src/primitives/link-preview.ts +0 -87
  285. package/src/primitives/message-feedback.ts +0 -124
  286. package/src/primitives/pdf-preview.ts +0 -121
  287. package/src/primitives/toast-store.ts +0 -338
  288. package/src/primitives/url-scheme-policy.ts +0 -70
  289. package/src/primitives/use-audio-analysis.ts +0 -340
  290. package/src/primitives/use-auto-resize.ts +0 -31
  291. package/src/primitives/use-resize-observer.ts +0 -45
  292. package/src/primitives/use-sequencer.ts +0 -72
  293. package/src/primitives/use-speech-recognition.ts +0 -146
  294. package/src/primitives/use-stick-to-bottom.ts +0 -75
  295. package/src/primitives/use-text-stream.ts +0 -143
  296. package/src/primitives/use-voice-recorder.ts +0 -62
  297. package/src/primitives/visualizer-sequences.ts +0 -350
  298. package/src/remote/host-embed.ts +0 -345
  299. package/src/remote/index.ts +0 -2
  300. package/src/remote/origin.ts +0 -30
  301. package/src/remote/provider-runtime.ts +0 -262
  302. package/src/remote/provider.ts +0 -2
  303. package/src/remote/validate.ts +0 -22
  304. package/src/remote/version.ts +0 -12
  305. package/src/remote/wire.ts +0 -48
  306. package/src/schemas/from-tool-call.ts +0 -151
  307. package/src/schemas/index.ts +0 -341
  308. package/src/schemas/provider-subsets.ts +0 -538
  309. package/src/schemas/registry.ts +0 -355
  310. package/src/schemas/tool-defs.ts +0 -793
  311. package/src/solid.ts +0 -173
  312. package/src/state/index.ts +0 -44
  313. package/src/state/messages.ts +0 -53
  314. package/src/state/mock.ts +0 -297
  315. package/src/state/parts.ts +0 -295
  316. package/src/state/persistence.ts +0 -180
  317. package/src/state/stream.ts +0 -246
  318. package/src/state/suggestions.ts +0 -9
  319. package/src/state/threads.ts +0 -142
  320. package/src/test-utils/fake-clock.ts +0 -88
  321. package/src/types.ts +0 -95
  322. package/src/ui/action-icons.ts +0 -53
  323. package/src/ui/agent-card.tsx +0 -218
  324. package/src/ui/avatar.tsx +0 -23
  325. package/src/ui/badge.tsx +0 -21
  326. package/src/ui/button.tsx +0 -49
  327. package/src/ui/card.tsx +0 -245
  328. package/src/ui/checkbox-group.tsx +0 -153
  329. package/src/ui/checkbox.tsx +0 -63
  330. package/src/ui/collapsible.tsx +0 -163
  331. package/src/ui/command.tsx +0 -103
  332. package/src/ui/dialog.tsx +0 -223
  333. package/src/ui/dock.tsx +0 -630
  334. package/src/ui/dropdown.tsx +0 -636
  335. package/src/ui/editable-label.tsx +0 -112
  336. package/src/ui/hover-card.tsx +0 -345
  337. package/src/ui/icon.tsx +0 -167
  338. package/src/ui/input.tsx +0 -391
  339. package/src/ui/kbd.tsx +0 -118
  340. package/src/ui/nav.tsx +0 -282
  341. package/src/ui/notice.tsx +0 -84
  342. package/src/ui/overlay.tsx +0 -277
  343. package/src/ui/pane-grid.tsx +0 -117
  344. package/src/ui/pane-group.tsx +0 -291
  345. package/src/ui/pane.tsx +0 -193
  346. package/src/ui/popover.tsx +0 -106
  347. package/src/ui/progress-bar.tsx +0 -83
  348. package/src/ui/prompt-dock.tsx +0 -145
  349. package/src/ui/radio.tsx +0 -150
  350. package/src/ui/resizable.tsx +0 -693
  351. package/src/ui/scroll-area.tsx +0 -26
  352. package/src/ui/segmented.tsx +0 -109
  353. package/src/ui/select.tsx +0 -168
  354. package/src/ui/separator.tsx +0 -10
  355. package/src/ui/settings-group.tsx +0 -67
  356. package/src/ui/skeleton.tsx +0 -73
  357. package/src/ui/slider.tsx +0 -178
  358. package/src/ui/status.tsx +0 -48
  359. package/src/ui/switch.tsx +0 -119
  360. package/src/ui/tabs.tsx +0 -168
  361. package/src/ui/textarea.tsx +0 -21
  362. package/src/ui/tooltip.tsx +0 -118
  363. package/src/utils/cn.ts +0 -30
  364. package/src/wire/chunk.ts +0 -248
  365. package/src/wire/consume.ts +0 -599
  366. package/src/wire/diagnostics.ts +0 -727
  367. package/src/wire/encode-probe.ts +0 -214
  368. package/src/wire/encode.ts +0 -947
  369. package/src/wire/files.ts +0 -342
  370. package/src/wire/formats/anthropic.ts +0 -274
  371. package/src/wire/formats/openai.ts +0 -228
  372. package/src/wire/index.ts +0 -95
  373. package/src/wire/media-types.ts +0 -344
  374. package/src/wire/read.ts +0 -396
  375. package/src/wire/sink-helpers.ts +0 -50
  376. package/src/wire/sse.ts +0 -153
@@ -1,140 +0,0 @@
1
- import { For, Show } from 'solid-js';
2
- import { defineWebComponent } from './define';
3
- import {
4
- Attachments,
5
- Attachment,
6
- AttachmentPreview,
7
- AttachmentInfo,
8
- AttachmentRemove,
9
- AttachmentHoverCard,
10
- AttachmentHoverCardTrigger,
11
- AttachmentHoverCardContent,
12
- AttachmentEmpty,
13
- getAttachmentLabel,
14
- getMediaCategory,
15
- type AttachmentData,
16
- type AttachmentVariant,
17
- } from '../components/attachments';
18
-
19
- interface Props extends Record<string, unknown> {
20
- /** The attachments to render. Omit (or pass an empty array) for the empty
21
- * state, which shows `emptyText` if set and nothing otherwise. Set as a JS
22
- * property (array). Each item's `url` must be a `data:` URI or an https
23
- * URL, never `URL.createObjectURL`: a `blob:` URL previews here but the
24
- * wire encoders (`toOpenAIMessages`/`toAnthropicMessages`) refuse it. */
25
- items?: AttachmentData[];
26
- /** Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. */
27
- variant?: AttachmentVariant;
28
- /** Wrap each item in a hover card that previews its details. */
29
- hoverCard?: boolean;
30
- /** Show a remove button per item; clicking it fires a `kai-remove` event. */
31
- removable?: boolean;
32
- /** Also show the media type beneath the filename (non-grid variants). */
33
- showMediaType?: boolean;
34
- /** Text shown when `items` is empty. */
35
- emptyText?: string;
36
- }
37
-
38
- /** Events fired by `<kai-attachments>`. */
39
- interface Events {
40
- /** A remove button was clicked. */
41
- 'kai-remove': { id: string };
42
- }
43
-
44
- /**
45
- * `<kai-attachments>` — the exemplar for the "collapse a compound primitive to
46
- * ONE configurable element" pattern (Route 1). The presentation knobs that the
47
- * SolidJS layer expresses by composing sub-parts (`<AttachmentPreview>`,
48
- * `<AttachmentInfo>`, `<AttachmentHoverCard>`, `<AttachmentRemove>`) become
49
- * attributes/flags here:
50
- *
51
- * - icon + label .......... `variant="inline"`
52
- * - visual + hover card .... `variant="grid" hover-card`
53
- * - removable chips ........ add `removable` (emits `kai-remove` → { id })
54
- *
55
- * Data in via the `items` property; the only interaction (`remove`) comes back
56
- * as an event. For fully-custom hover content, the SolidJS primitives remain the
57
- * escape hatch (a templated slot — "Route 2" — is a deliberate future add).
58
- */
59
- defineWebComponent<Props, Events>('kai-attachments', {
60
- items: [],
61
- variant: 'grid',
62
- hoverCard: false,
63
- removable: false,
64
- showMediaType: false,
65
- emptyText: undefined,
66
- }, (props, { dispatch, flag }) => {
67
- const variant = () => props.variant ?? 'grid';
68
- const hoverCard = () => flag('hoverCard');
69
- const removable = () => flag('removable');
70
- const showMediaType = () => flag('showMediaType');
71
-
72
- return (
73
- <Show
74
- when={props.items?.length}
75
- fallback={<Show when={props.emptyText}><AttachmentEmpty>{props.emptyText}</AttachmentEmpty></Show>}
76
- >
77
- <Attachments variant={variant()}>
78
- <For each={props.items ?? []}>
79
- {(item) => (
80
- <Attachment
81
- data={item}
82
- onRemove={removable() ? () => dispatch('kai-remove', { id: item.id }) : undefined}
83
- >
84
- <Show
85
- when={hoverCard()}
86
- fallback={
87
- <>
88
- <AttachmentPreview />
89
- {/* Rendered for EVERY variant now. `AttachmentInfo` decides
90
- what a grid tile gets — a visible caption for a non-image
91
- tile, nothing for an image, which is the only tile that
92
- really is a self-contained visual. */}
93
- <AttachmentInfo showMediaType={showMediaType()} />
94
- </>
95
- }
96
- >
97
- {/* The trigger carries the layout itself — a bare inline <span>
98
- collapses inline/list rows. Grid wraps just the tile (the
99
- label/details surface in the hover card instead). */}
100
- <AttachmentHoverCard>
101
- <AttachmentHoverCardTrigger
102
- class={variant() === 'grid'
103
- ? 'block size-full'
104
- : `flex items-center gap-1.5${variant() === 'list' ? ' w-full' : ''}`}
105
- >
106
- <AttachmentPreview />
107
- <AttachmentInfo showMediaType={showMediaType()} />
108
- </AttachmentHoverCardTrigger>
109
- <AttachmentHoverCardContent>
110
- {/* For image attachments, preview the actual thumbnail;
111
- otherwise fall back to the label + media-type details. */}
112
- <Show
113
- when={getMediaCategory(item) === 'image' && item.type === 'file' && item.url}
114
- fallback={
115
- <>
116
- <div class="text-body font-medium">{getAttachmentLabel(item)}</div>
117
- <Show when={item.mediaType}>
118
- <div class="text-muted-foreground text-caption">{item.mediaType}</div>
119
- </Show>
120
- </>
121
- }
122
- >
123
- <img
124
- part="preview"
125
- src={item.url}
126
- alt={getAttachmentLabel(item)}
127
- class="block max-h-64 max-w-xs rounded object-contain"
128
- />
129
- </Show>
130
- </AttachmentHoverCardContent>
131
- </AttachmentHoverCard>
132
- </Show>
133
- <AttachmentRemove />
134
- </Attachment>
135
- )}
136
- </For>
137
- </Attachments>
138
- </Show>
139
- );
140
- });
@@ -1,227 +0,0 @@
1
- import type { JSX } from 'solid-js';
2
- import { AudioVisualizer, type ShaderSpec, type VisualizerVariant } from '../components/audio-visualizer';
3
- import type { VisualizerSize } from '../components/audio-visualizer/sizes';
4
- import { defineWebComponent, type WebComponentContext } from './define';
5
-
6
- interface Props extends Record<string, unknown> {
7
- /** Look to render: `bar` (default), `grid`, `radial`, `wave`, `aurora`, `custom`.
8
- * `aura` is accepted as a LiveKit-markup alias for `aurora`. Attribute: `variant`. */
9
- variant?: string;
10
- /** `idle` (default), `connecting`, `listening`, `thinking`, `speaking`,
11
- * `disconnected` (connection down: the dead, flat look). LiveKit's
12
- * room-lifecycle state names are accepted as aliases. Attribute: `state`. */
13
- state?: string;
14
- /** `icon` | `sm` | `md` (default) | `lg` | `xl`. Attribute: `size`. */
15
- size?: string;
16
- /** Bars to draw. Bar and radial only. Attribute: `bar-count`. */
17
- barCount?: number;
18
- /** Grid only: rows and columns of the (always square) grid. Attribute: `count`. */
19
- count?: number;
20
- /** Radial only: ring distance from center, in px. Attribute: `radius`. */
21
- radius?: number;
22
- /** Grid only: ring distance for the connecting animation, in cells. Attribute: `spread`. */
23
- spread?: number;
24
- /** Grid only: ms between scripted frames. Attribute: `interval`. */
25
- interval?: number;
26
- /** CSS color for the geometry, overriding the inherited `currentColor`. Attribute: `color`. */
27
- color?: string;
28
- /** Shader variants only: pattern density, 0..1. Attribute: `complexity`. */
29
- complexity?: number;
30
- /** Setting this makes the element an announced image (`role="img"`) instead of
31
- * decorative (`aria-hidden`). Attribute: `label`. */
32
- label?: string;
33
- /** Live microphone or WebRTC audio to analyze. JS property only. NOTE: amplitude
34
- * renders only while state is "speaking" unless listening-amplitude is set; every
35
- * other state plays its scripted animation and ignores the audio. */
36
- stream?: MediaStream;
37
- /** An `<audio>` or `<video>` element to tap for its audio. JS property only. NOTE:
38
- * amplitude renders only while state is "speaking" unless listening-amplitude is
39
- * set; every other state plays its scripted animation and ignores the audio. */
40
- audioElement?: HTMLMediaElement;
41
- /** Pre-computed levels, 0..1. Set this and no AudioContext is ever built, which is
42
- * what keeps headless/SSR rendering and browser-speech-synthesis playback (which
43
- * exposes no audio node) free of Web Audio entirely. JS property only. A new
44
- * array reference is required for each update; mutating the existing array in
45
- * place will not re-render. NOTE: amplitude renders only while state is "speaking"
46
- * unless listening-amplitude is set; every other state plays its scripted
47
- * animation and ignores the audio. */
48
- bands?: number[];
49
- /** Render live amplitude during the listening state as well, using the same
50
- * presentation as speaking. Off by default, which keeps LiveKit parity: amplitude
51
- * from stream, audio-element or bands renders only while state is "speaking". Set
52
- * it to show a real mic-level picture while the user is the one talking. Boolean.
53
- * Attribute: `listening-amplitude` (a bare attribute means true; reflected, so the
54
- * property reads back what the attribute set). */
55
- listeningAmplitude?: boolean;
56
- /** Custom fragment shader for `variant="custom"`. JS property only. */
57
- shader?: ShaderSpec;
58
- /** Shader variants only: keep animating while scrolled off screen. Off by default,
59
- * which stops drawing and releases the WebGL context until the element comes back
60
- * (browsers ration contexts to roughly 16 a page). Does not override
61
- * `prefers-reduced-motion`. Attribute: `animate-when-not-visible`. */
62
- animateWhenNotVisible?: boolean;
63
- }
64
-
65
- /**
66
- * Parse a numeric attribute or JS property value. Attribute values arrive as
67
- * strings, so a missing or non-numeric one must resolve to `undefined` (which
68
- * the composed component then defaults itself), never `NaN`, which would
69
- * poison the downstream geometry (bar counts, radii, grid dimensions).
70
- */
71
- export function num(value: unknown): number | undefined {
72
- if (value == null || value === '') return undefined;
73
- const n = Number(value);
74
- return Number.isFinite(n) ? n : undefined;
75
- }
76
-
77
- /**
78
- * The facade's render logic, extracted to a named function so it can be
79
- * exercised directly in tests (a plain Solid component call, no shadow DOM
80
- * involved) without needing `defineWebComponent`'s browser-only custom-element
81
- * upgrade path. Identical to what was previously an inline arrow passed to
82
- * `defineWebComponent` below, plus forwarding the resolved theme (see `dark`
83
- * below) once that context field existed to forward.
84
- *
85
- * `ctx` is optional so the existing declarative test (which calls this
86
- * directly with just `props`, the way it did before `dark` existed) keeps
87
- * working unchanged. `defineWebComponent` always supplies a real one; the
88
- * `false` fallback only matters for that reduced, no-shadow-DOM call path.
89
- */
90
- export function AudioVisualizerFacade(
91
- props: Props,
92
- ctx?: Partial<Pick<WebComponentContext, 'dark' | 'flag' | 'reflectFlag'>>,
93
- ): JSX.Element {
94
- // Every element already resolves `theme='light'|'dark'|'auto'` against a
95
- // live `prefers-color-scheme` listener in `defineWebComponent` (see
96
- // `createDarkMode`), to drive the `.dark` class every element's content
97
- // sits inside. A WebGL shader cannot read that class or a CSS custom
98
- // property, so `<kai-audio-visualizer variant="aurora">` needs the ALREADY
99
- // -RESOLVED boolean forwarded explicitly instead. Passing it through as a
100
- // definite 'light'/'dark' (never 'auto') means the resolution RULE itself
101
- // -- explicit wins, 'auto' follows the media query -- lives in exactly one
102
- // place (`createDarkMode`); this is a plain relay, not a second
103
- // implementation of that rule.
104
- const dark = ctx?.dark?.() ?? false;
105
-
106
- // `flag` is what makes a BARE attribute
107
- // (`<kai-audio-visualizer animate-when-not-visible>`) mean true:
108
- // component-register parses a valueless boolean attribute to `undefined`, so
109
- // the prop alone cannot tell it apart from an absent one. Same helper every
110
- // other boolean-attribute element uses, so the coercion rule lives in one
111
- // place. The `props` fallback only covers the ctx-less direct-call path the
112
- // declarative test uses, where there is no host element carrying an
113
- // attribute at all.
114
- const animateWhenNotVisible = ctx?.flag
115
- ? ctx.flag('animateWhenNotVisible')
116
- : props.animateWhenNotVisible === true;
117
-
118
- // Same bare-attribute coercion for the second boolean on this element.
119
- const listeningAmplitude = ctx?.flag
120
- ? ctx.flag('listeningAmplitude')
121
- : props.listeningAmplitude === true;
122
-
123
- // Reflected (kai-dock precedent, findings G-05): without this,
124
- // `<kai-audio-visualizer listening-amplitude>` would leave
125
- // `el.listeningAmplitude === undefined` even while the element honours the
126
- // attribute, because toggleAttribute's empty-string write-back parses to
127
- // undefined. See WebComponentContext.reflectFlag. `animateWhenNotVisible`
128
- // predates the reflectFlag seam and is deliberately left as-is here; if it
129
- // ever earns reflection that is its own change, not a rider on this one.
130
- ctx?.reflectFlag?.('listeningAmplitude');
131
-
132
- return (
133
- <AudioVisualizer
134
- variant={props.variant as VisualizerVariant | undefined}
135
- state={props.state as string | undefined}
136
- size={props.size as VisualizerSize | undefined}
137
- barCount={num(props.barCount)}
138
- count={num(props.count)}
139
- radius={num(props.radius)}
140
- spread={num(props.spread)}
141
- interval={num(props.interval)}
142
- color={props.color as string | undefined}
143
- complexity={num(props.complexity)}
144
- label={props.label as string | undefined}
145
- stream={props.stream as MediaStream | undefined}
146
- audioElement={props.audioElement as HTMLMediaElement | undefined}
147
- bands={props.bands as number[] | undefined}
148
- listeningAmplitude={listeningAmplitude}
149
- shader={props.shader as ShaderSpec | undefined}
150
- animateWhenNotVisible={animateWhenNotVisible}
151
- theme={dark ? 'dark' : 'light'}
152
- />
153
- );
154
- }
155
-
156
- /**
157
- * `<kai-audio-visualizer>` renders live audio as bars, a grid, a ring, a wave,
158
- * or a glowing aurora. It also animates from `state` alone with no audio at
159
- * all, which is what you want when the source cannot be tapped (browser
160
- * speech synthesis exposes no audio node).
161
- *
162
- * ```html
163
- * <kai-audio-visualizer variant="bar" state="speaking" size="md"></kai-audio-visualizer>
164
- * <kai-audio-visualizer variant="radial" size="lg" bar-count="24"></kai-audio-visualizer>
165
- * ```
166
- *
167
- * Audio sources and rich data are JS properties, never attributes:
168
- * ```js
169
- * el.stream = micStream // MediaStream
170
- * el.audioElement = audioRef // HTMLMediaElement
171
- * el.bands = [0.2, 0.8, 0.4] // pre-computed, skips Web Audio; new array each update
172
- * el.shader = { fragment: glsl } // variant="custom" only
173
- * ```
174
- *
175
- * Audio drives the geometry only while `state="speaking"`. Every other state
176
- * plays its scripted animation, so a mic stream attached during `listening`
177
- * changes nothing by default (LiveKit parity). Set `listening-amplitude` to
178
- * render real amplitude while the USER is the one talking:
179
- * ```html
180
- * <kai-audio-visualizer state="listening" listening-amplitude></kai-audio-visualizer>
181
- * ```
182
- *
183
- * Size tiers are fixed pixel designs, but the element never clips: in a
184
- * container narrower than the tier's natural footprint the whole picture
185
- * scales down proportionally to fit, and returns to the exact designed
186
- * metrics as soon as the space is back. Nothing to configure.
187
- *
188
- * The `wave`, `aurora`, and `custom` variants render through WebGL, and a
189
- * browser only allows about 16 live WebGL contexts per page. So a shader
190
- * variant scrolled off screen stops drawing and hands its context back,
191
- * picking up where it left off when it returns. Opt out per element when a
192
- * visualizer must keep running unseen:
193
- *
194
- * ```html
195
- * <kai-audio-visualizer variant="wave" animate-when-not-visible></kai-audio-visualizer>
196
- * ```
197
- *
198
- * This is a display element: no methods, no events.
199
- *
200
- * Restyle from outside via `::part(bar)` / `::part(cell)` / `::part(canvas)`,
201
- * the bar and cell parts each carrying `data-kai-index` and `data-kai-highlighted`.
202
- */
203
- defineWebComponent<Props>('kai-audio-visualizer', {
204
- variant: 'bar',
205
- state: 'idle',
206
- size: 'md',
207
- barCount: undefined,
208
- count: undefined,
209
- radius: undefined,
210
- spread: undefined,
211
- interval: undefined,
212
- color: undefined,
213
- complexity: undefined,
214
- label: undefined,
215
- stream: undefined,
216
- audioElement: undefined,
217
- bands: undefined,
218
- // `undefined`, not `false`, for the same resolveFlag reason as
219
- // `animateWhenNotVisible` below.
220
- listeningAmplitude: undefined,
221
- shader: undefined,
222
- // `undefined`, not `false`: `resolveFlag` short-circuits on an explicit
223
- // `false` prop value, so a `false` default would beat a bare
224
- // `animate-when-not-visible` attribute if component-register ever left the
225
- // default in place instead of writing `undefined` for a valueless attribute.
226
- animateWhenNotVisible: undefined,
227
- }, AudioVisualizerFacade);
@@ -1,89 +0,0 @@
1
- // The kai-* autoloader (opt-in, additive — the Web Awesome model).
2
- //
3
- // Include this ONE module (via a CDN/static `<script type="module">` tag) and it
4
- // watches the DOM for undefined kai-* elements and dynamically imports each
5
- // element's module on demand — so a page that only uses <kai-chat> never downloads
6
- // the others. The register-all bundle (@kitn.ai/ui/elements) stays the default;
7
- // this is the delivery for NO-BUILD / CDN / static-served pages.
8
- //
9
- // IMPORTANT — this is a CDN / static-file pattern (it resolves sibling element
10
- // modules relative to its own URL). It is NOT importable through a bundler: Vite /
11
- // webpack relocate `import.meta.url` away from the element files and cannot analyze
12
- // the dynamic import, so it 404s. In a BUNDLED app use per-element imports
13
- // (`import '@kitn.ai/ui/elements/<el>'`) or the register-all bundle. (Advanced: host
14
- // dist/elements/ yourself and call setAutoloaderBasePath('<url>/') before use.)
15
- //
16
- // Tag → module is resolved through the generated manifest (filenames don't always
17
- // equal the tag, and some modules register more than one tag), and modules are
18
- // fetched relative to THIS module's URL.
19
- import manifest from './element-manifest.json';
20
-
21
- const tagToModule: Record<string, string> = manifest.tags;
22
- const inFlight = new Set<string>();
23
-
24
- // Base = the directory of this module. Computed via string ops on import.meta.url
25
- // (NOT `new URL('./x', import.meta.url)`, which Vite rewrites into a static-asset
26
- // glob that resolves to undefined at runtime).
27
- const BASE = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1);
28
-
29
- /** Override where element modules are fetched from (e.g. a CDN). Call before use. */
30
- let baseOverride: string | null = null;
31
- export function setAutoloaderBasePath(path: string): void {
32
- baseOverride = path.endsWith('/') ? path : path + '/';
33
- }
34
-
35
- async function register(tag: string): Promise<void> {
36
- if (customElements.get(tag) || inFlight.has(tag)) return;
37
- const file = tagToModule[tag];
38
- if (!file) return; // not one of ours
39
- inFlight.add(tag);
40
- try {
41
- await import(/* @vite-ignore */ `${baseOverride ?? BASE}${file}.js`);
42
- } catch (err) {
43
- inFlight.delete(tag);
44
- warnOnce(tag, err);
45
- }
46
- }
47
-
48
- // One actionable warning per session (the failure is almost always "imported
49
- // through a bundler" — see the header note), instead of a silent per-tag error.
50
- let warned = false;
51
- function warnOnce(tag: string, err: unknown): void {
52
- if (warned) return;
53
- warned = true;
54
- const msg = (err as { message?: string })?.message ?? String(err);
55
- // eslint-disable-next-line no-console
56
- console.warn(
57
- `[kai-autoloader] could not load "${tag}" (${msg}). The autoloader is a CDN / ` +
58
- `static-file tool — load it from a <script type="module" src=".../@kitn.ai/ui/dist/elements/autoloader.js">. ` +
59
- `It is NOT importable through a bundler. In a bundled app, register elements with per-element imports ` +
60
- `(import '@kitn.ai/ui/elements/<el>') or the register-all bundle (import '@kitn.ai/ui/elements'). To drive ` +
61
- `the autoloader from a bundler, host dist/elements/ and call setAutoloaderBasePath('<url>/') before use.`,
62
- );
63
- }
64
-
65
- function discover(root: ParentNode | Element): void {
66
- const tags = new Set<string>();
67
- const self = (root as Element).tagName?.toLowerCase?.();
68
- if (self && self in tagToModule && !customElements.get(self)) tags.add(self);
69
- root.querySelectorAll?.(':not(:defined)').forEach((el) => {
70
- const t = el.tagName.toLowerCase();
71
- if (t in tagToModule) tags.add(t);
72
- });
73
- tags.forEach(register);
74
- }
75
-
76
- /** Start watching `root` (default: the whole document) for undefined kai-* elements. */
77
- export function startAutoloader(root: ParentNode = document): void {
78
- discover(root as Element);
79
- new MutationObserver((mutations) => {
80
- for (const m of mutations)
81
- for (const node of m.addedNodes)
82
- if (node.nodeType === 1) discover(node as Element);
83
- }).observe(document.documentElement, { childList: true, subtree: true });
84
- }
85
-
86
- // Self-start on import (the <script type="module"> use case). Deferred to a
87
- // microtask so a consumer that calls setAutoloaderBasePath() right after importing
88
- // (the advanced bundler-from-CDN path) takes effect before the first discovery pass.
89
- if (typeof document !== 'undefined') queueMicrotask(() => startAutoloader());
@@ -1,33 +0,0 @@
1
- import { Avatar } from '../ui/avatar';
2
- import { defineWebComponent } from './define';
3
-
4
- interface Props extends Record<string, unknown> {
5
- /** Image URL/data-URI. When absent, the `fallback` initials show instead. */
6
- src?: string;
7
- /** Alt text for the image. Defaults to `fallback`. */
8
- alt?: string;
9
- /** Short text shown when there's no image, usually initials (e.g. "JD", "AI"). */
10
- fallback?: string;
11
- /** Size token: `sm` | `md` (default) | `lg`. */
12
- size?: 'sm' | 'md' | 'lg';
13
- }
14
-
15
- /**
16
- * `<kai-avatar>` — a person/entity avatar: a rounded image that falls back to
17
- * initials when there's no `src`. This is an *identity* badge (the thing next to
18
- * a message or in a conversation list), not a generic icon — for a glyph, use an
19
- * `icon` prop on another element.
20
- *
21
- * ```html
22
- * <kai-avatar src="/me.jpg" fallback="JD"></kai-avatar>
23
- * <kai-avatar fallback="AI" size="sm"></kai-avatar>
24
- * ```
25
- */
26
- defineWebComponent<Props>('kai-avatar', {
27
- src: undefined,
28
- alt: undefined,
29
- fallback: '',
30
- size: 'md',
31
- }, (props) => (
32
- <Avatar src={props.src} alt={props.alt} fallback={props.fallback ?? ''} size={props.size ?? 'md'} />
33
- ));
@@ -1,32 +0,0 @@
1
- import { Badge } from '../ui/badge';
2
- import { defineWebComponent } from './define';
3
-
4
- interface Props extends Record<string, unknown> {
5
- /** `default` (muted pill) · `count` (compact number badge) · `citation`
6
- * (filled primary, for inline citation markers). Defaults to `default`. */
7
- variant?: 'default' | 'count' | 'citation';
8
- }
9
-
10
- /**
11
- * `<kai-badge>` — a small pill for labels, status, counts, or citation markers.
12
- * Put the content as light-DOM text.
13
- *
14
- * ```html
15
- * <kai-badge>Beta</kai-badge>
16
- * <kai-badge variant="count">3</kai-badge>
17
- * <kai-badge variant="citation">1</kai-badge>
18
- * ```
19
- * Restyle via `::part(badge)`.
20
- */
21
- defineWebComponent<Props>('kai-badge', {
22
- variant: 'default',
23
- }, (props) => (
24
- <>
25
- {/* Base sets `:host{display:block}`; a badge/pill flows inline with text and
26
- beside sibling badges, so inline-flex like kai-button. */}
27
- <style>{':host{display:inline-flex}'}</style>
28
- <Badge variant={props.variant ?? 'default'} part="badge">
29
- <slot />
30
- </Badge>
31
- </>
32
- ));
@@ -1,149 +0,0 @@
1
- import { Show } from 'solid-js';
2
- import { Button } from '../ui/button';
3
- import { renderIcon } from '../ui/icon';
4
- import { defineWebComponent } from './define';
5
- import { createSlotText } from './slot-text';
6
-
7
- interface Props extends Record<string, unknown> {
8
- /** Visual style. `default` (filled), `subtle` (muted text, hover tint, the
9
- * toolbar icon look), `ghost` (transparent, hover fill), `outline`, or
10
- * `destructive`. Defaults to `default`. */
11
- variant?: 'default' | 'subtle' | 'ghost' | 'outline' | 'destructive';
12
- /** Size token. `icon` / `icon-sm` are square (for icon-only buttons); `sm` /
13
- * `md` / `lg` size text buttons. Defaults to `md`. */
14
- size?: 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm';
15
- /** Leading icon: a named icon (e.g. `"mic"`, `"plus"`), an image URL/data-URI,
16
- * or plain text. Renders before any slotted label. */
17
- icon?: string;
18
- /** Trailing icon, after the label (e.g. `"chevron-down"` for a menu affordance). */
19
- iconTrailing?: string;
20
- /** Accessible name. REQUIRED for icon-only buttons (no visible text); ignored
21
- * when you slot visible text, which already names the button. An `aria-label`
22
- * on top of visible text REPLACES that name rather than adding to it, so a
23
- * button reading "Save" that answers to "Submit" is unusable by speech input
24
- * (WCAG 2.5.3, Label in Name). The visible text wins. An `icon` / `icon-sm`
25
- * size hides the slot, which makes the button icon-only whatever you slotted,
26
- * so `label` is what names it there. */
27
- label?: string;
28
- /** Disable the button (non-interactive, dimmed). */
29
- disabled?: boolean;
30
- /** Stretch the button to the full width of its container (a block button),
31
- * e.g. a card CTA or a stacked action. Attribute: `full`. */
32
- full?: boolean;
33
- /** Justify the button's content: `start`, `center` (default), or `end`.
34
- * Combine with `full` for a full-width, left-aligned button. */
35
- align?: 'start' | 'center' | 'end';
36
- /** Native button `type`. Defaults to `button` (so it never submits a form). */
37
- type?: 'button' | 'submit' | 'reset';
38
- }
39
-
40
- /** Events fired by `<kai-button>`. */
41
- interface Events {
42
- /** The button was activated (pointer or keyboard). Carries no detail. The
43
- * native `click` also bubbles (composed) for consumers who prefer it. */
44
- 'kai-click': void;
45
- }
46
-
47
- /**
48
- * `<kai-button>` — the kit's button as a drop-in element, so consumers compose
49
- * polished, theme-aware controls instead of hand-rolling `<button>` + inline
50
- * hover styles. Put the label as light-DOM text; use `icon` for a leading glyph
51
- * and `label` to name an icon-only button.
52
- *
53
- * ```html
54
- * <kai-button variant="subtle" size="icon" icon="mic" label="Voice input"></kai-button>
55
- * <kai-button variant="ghost" icon-trailing="chevron-down">High</kai-button>
56
- * <kai-button>Send</kai-button>
57
- * <kai-button label="Ship"><svg slot="icon" viewBox="0 0 24 24">…</svg></kai-button>
58
- * ```
59
- *
60
- * The `icon` prop renders a curated/URL icon; for anything else, slot your own
61
- * inline SVG via `slot="icon"` (it wins over `icon`). Restyle via `::part(button)`.
62
- * Emits `kai-click`.
63
- */
64
- defineWebComponent<Props, Events>('kai-button', {
65
- variant: 'default',
66
- size: 'md',
67
- icon: undefined,
68
- iconTrailing: undefined,
69
- label: undefined,
70
- disabled: false,
71
- full: false,
72
- align: 'center',
73
- type: 'button',
74
- }, (props, { dispatch, flag, element, expose }) => {
75
- // `icon` / `icon-sm` are square, icon-ONLY sizes: suppress the text label and
76
- // trailing icon so a stray label doesn't get cramped into the square (the
77
- // `label` prop still names it for assistive tech).
78
- const iconOnly = () => props.size === 'icon' || props.size === 'icon-sm';
79
-
80
- // Slotted text names the button by itself (name-from-contents reaches through
81
- // the slot), so `label` must NOT be emitted on top of it: aria-label REPLACES
82
- // the computed name, and a name that disagrees with the visible text locks
83
- // speech-input users out of the control (WCAG 2.5.3). This is what the `label`
84
- // doc has always promised, and what `kai-checkpoint` already does.
85
- //
86
- // `flatten` so a consumer who wraps <kai-button> in their own component and
87
- // forwards a <slot> still counts as having visible text. Safe here because the
88
- // default slot has no fallback content — see createSlotText.
89
- const slotted = createSlotText({ flatten: true });
90
-
91
- // The name to emit, or nothing. An `icon`/`icon-sm` size does not render the
92
- // default slot at all, so whatever was slotted is INVISIBLE and `label` is the
93
- // only name available — dropping it there would leave icon buttons nameless,
94
- // which is the worse bug.
95
- const ariaLabel = () => (!iconOnly() && slotted.hasText() ? undefined : props.label);
96
-
97
- // ── Imperative API (instance methods on the host) ──────────────────────────
98
- // The real <button> lives in the shadow root, so native focus()/blur()/click()
99
- // on the host hit the wrapper, not the control. Forward to the inner button.
100
- // These shadow the inherited HTMLElement methods — intended; expose() uses
101
- // defineProperty so the override sticks. No collision with the props above.
102
- expose({
103
- /** Focus the inner `<button>` (host.focus() would focus the wrapper). */
104
- focus: (options?: FocusOptions) =>
105
- element.shadowRoot?.querySelector('button')?.focus(options),
106
- /** Blur the inner `<button>`. */
107
- blur: () => element.shadowRoot?.querySelector('button')?.blur(),
108
- /** Programmatically activate the button. Runs the same path as a user
109
- * click and fires kai-click. Forwarding to the inner button means
110
- * `disabled` is respected automatically. */
111
- click: () => element.shadowRoot?.querySelector('button')?.click(),
112
- });
113
- return (
114
- <>
115
- {/* The host shrinks to the label by default (inline-flex), so a text button
116
- is only as wide as its content — until a parent stretches it. A text
117
- button's inner control is w-full, so when the host IS stretched (a flex
118
- column with items-stretch, e.g. a card's collapsed footer, or an explicit
119
- `full`) the button fills it. `full` forces that standalone (a block CTA).
120
- Icon buttons stay square (no w-full). */}
121
- <style>{':host{display:inline-flex}:host([full]){display:block}'}</style>
122
- <Button
123
- part="button"
124
- variant={props.variant ?? 'default'}
125
- size={props.size ?? 'md'}
126
- align={props.align ?? 'center'}
127
- full={flag('full') || !iconOnly()}
128
- type={props.type ?? 'button'}
129
- disabled={flag('disabled')}
130
- aria-label={ariaLabel()}
131
- onClick={() => dispatch('kai-click')}
132
- >
133
- {/* Leading icon: a slotted SVG (slot="icon") wins — drop in any icon
134
- library or your own; otherwise the `icon` prop renders a curated/URL one. */}
135
- <slot name="icon">
136
- <Show when={props.icon}>
137
- {renderIcon(props.icon, { class: 'size-4 shrink-0', imgClass: 'size-4 shrink-0', spanClass: 'inline-flex size-4 shrink-0 items-center justify-center', ariaHidden: true })}
138
- </Show>
139
- </slot>
140
- <Show when={!iconOnly()}>
141
- <slot ref={slotted.ref} />
142
- </Show>
143
- <Show when={props.iconTrailing && !iconOnly()}>
144
- {renderIcon(props.iconTrailing, { class: 'size-4 shrink-0 opacity-60', imgClass: 'size-4 shrink-0', spanClass: 'inline-flex size-4 shrink-0 items-center justify-center', ariaHidden: true })}
145
- </Show>
146
- </Button>
147
- </>
148
- );
149
- });