@kitn.ai/ui 0.16.0 → 0.18.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.
- package/README.md +55 -17
- package/dist/components/artifact.d.ts +39 -1
- package/dist/components/attachment-types.d.ts +12 -0
- package/dist/components/attachments.d.ts +2 -10
- package/dist/components/card.d.ts +27 -0
- package/dist/components/chain-of-thought.d.ts +78 -2
- package/dist/components/chat-scope-picker.d.ts +7 -0
- package/dist/components/chat-thread.d.ts +39 -16
- package/dist/components/choice-card.d.ts +27 -0
- package/dist/components/coachmark.d.ts +49 -0
- package/dist/components/composer-dom.d.ts +35 -0
- package/dist/components/composer-highlight.d.ts +48 -0
- package/dist/components/composer-history.d.ts +34 -0
- package/dist/components/composer.d.ts +86 -0
- package/dist/components/confirm-card.d.ts +17 -0
- package/dist/components/conversation-item.d.ts +9 -0
- package/dist/components/conversation-list.d.ts +36 -1
- package/dist/components/dismissed-stub.d.ts +22 -0
- package/dist/components/file-tree.d.ts +15 -0
- package/dist/components/form.d.ts +34 -0
- package/dist/components/markdown.d.ts +3 -0
- package/dist/components/message.d.ts +71 -2
- package/dist/components/model-switcher.d.ts +7 -0
- package/dist/components/prompt-input.d.ts +13 -4
- package/dist/components/prompt-suggestion.d.ts +9 -1
- package/dist/components/reasoning.d.ts +14 -1
- package/dist/components/response-compare-types.d.ts +66 -0
- package/dist/components/response-compare.d.ts +65 -0
- package/dist/components/screen.d.ts +46 -0
- package/dist/components/tasks-card.d.ts +50 -1
- package/dist/components/toast.d.ts +58 -0
- package/dist/components/tool-types.d.ts +10 -0
- package/dist/components/tool.d.ts +8 -8
- package/dist/components/use-card-resolution.d.ts +10 -6
- package/dist/components/voice-input.d.ts +27 -0
- package/dist/components/voice-output.d.ts +33 -0
- package/dist/core-C8fzo39E.js +12 -0
- package/dist/custom-elements.json +8472 -1691
- package/dist/elements/agent-card.d.ts +1 -0
- package/dist/elements/agent-card.js +1 -0
- package/dist/elements/artifact.js +1 -0
- package/dist/elements/attachments.js +1 -0
- package/dist/elements/autoloader.d.ts +2 -0
- package/dist/elements/autoloader.js +1 -0
- package/dist/elements/avatar.d.ts +1 -0
- package/dist/elements/avatar.js +1 -0
- package/dist/elements/badge.d.ts +1 -0
- package/dist/elements/badge.js +1 -0
- package/dist/elements/button.d.ts +1 -0
- package/dist/elements/button.js +1 -0
- package/dist/elements/card.js +1 -0
- package/dist/elements/cards.d.ts +1 -0
- package/dist/elements/cards.js +1 -0
- package/dist/elements/chain-of-thought.d.ts +0 -12
- package/dist/elements/chain-of-thought.js +1 -0
- package/dist/elements/chat-scope-picker.js +1 -0
- package/dist/elements/chat-types.d.ts +8 -2
- package/dist/elements/chat-workspace.js +1 -0
- package/dist/elements/chat.js +1 -0
- package/dist/elements/checkpoint.js +1 -0
- package/dist/elements/choice.js +1 -0
- package/dist/elements/chunks/Icon-DLLBeLG5.js +1 -0
- package/dist/elements/chunks/action-icons-UB4V5FNq.js +1 -0
- package/dist/elements/chunks/arrow-left-C-gwlUmx.js +1 -0
- package/dist/elements/chunks/attachments-CgbErf1r.js +1 -0
- package/dist/elements/chunks/badge-DTQpgB6N.js +1 -0
- package/dist/elements/chunks/bash-InADTalH.js +1 -0
- package/dist/elements/chunks/button-XDUM6lXs.js +1 -0
- package/dist/elements/chunks/card-CBlFZ-7I.js +1 -0
- package/dist/elements/chunks/card-routing-C986KuUA.js +1 -0
- package/dist/elements/chunks/chat-thread-BIhmqwNL.js +1 -0
- package/dist/elements/chunks/check-hGUoVIKb.js +1 -0
- package/dist/elements/chunks/chevron-down-DDoaSUtO.js +1 -0
- package/dist/elements/chunks/chevron-right-CdGEUakB.js +1 -0
- package/dist/elements/chunks/circle-BKAZdmzb.js +1 -0
- package/dist/elements/chunks/circle-check-CmYG-y3I.js +1 -0
- package/dist/elements/chunks/circle-x-DewgAEym.js +1 -0
- package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
- package/dist/elements/chunks/code-block-DedwxwFI.js +1 -0
- package/dist/elements/chunks/collapsible-FfgmTHtM.js +1 -0
- package/dist/elements/chunks/composer-Cfbw5HKT.js +65 -0
- package/dist/elements/chunks/context-DwJmxlRE.js +1 -0
- package/dist/elements/chunks/conversation-list-DsCQMfPq.js +1 -0
- package/dist/elements/chunks/core-AYMC6_lb.js +12 -0
- package/dist/elements/chunks/css-M7EaDHN_.js +1 -0
- package/dist/elements/chunks/default-input-BuqsoENC.js +1 -0
- package/dist/elements/chunks/define-rGsaWsLL.js +1 -0
- package/dist/elements/chunks/disclosure-CwoAkpMH.js +1 -0
- package/dist/elements/chunks/download-DJ06G3Bz.js +1 -0
- package/dist/elements/chunks/dropdown-BFg8ri_R.js +1 -0
- package/dist/elements/chunks/ellipsis-dlN6YvGw.js +1 -0
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/elements/chunks/external-link-DBX2lc_R.js +1 -0
- package/dist/elements/chunks/file-text-HfwUx4F9.js +1 -0
- package/dist/elements/chunks/file-tree-C6Toj71u.js +1 -0
- package/dist/elements/chunks/folder-MYuqa9Xp.js +1 -0
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/elements/chunks/github-light-JYsPkUQd.js +1 -0
- package/dist/elements/chunks/hover-card-gE7iyXdW.js +1 -0
- package/dist/elements/chunks/html-CPZ3oZQ7.js +1 -0
- package/dist/elements/chunks/icon-Vqz4ivrY.js +1 -0
- package/dist/elements/chunks/index-BCRhi84a.js +1 -0
- package/dist/elements/chunks/info-B_toFS0l.js +1 -0
- package/dist/elements/chunks/input-gzbHYZvQ.js +1 -0
- package/dist/elements/chunks/javascript-C25yR2R2.js +1 -0
- package/dist/elements/chunks/json-DxJze_jm.js +1 -0
- package/dist/elements/chunks/kbd-n49c4KZm.js +1 -0
- package/dist/elements/chunks/link-CKPs9yM0.js +1 -0
- package/dist/elements/chunks/loader-q6MamGus.js +1 -0
- package/dist/elements/chunks/markdown-DBrcqq4S.js +64 -0
- package/dist/elements/chunks/message-CTqaYI0s.js +1 -0
- package/dist/elements/chunks/message-feedback-Cvzm03w0.js +1 -0
- package/dist/elements/chunks/message-square-CUg9eRCi.js +1 -0
- package/dist/elements/chunks/minimize-2-l2klxP6K.js +1 -0
- package/dist/elements/chunks/model-switcher-CmyjiHO1.js +1 -0
- package/dist/elements/chunks/overlay-C8sOYqjT.js +1 -0
- package/dist/elements/chunks/paperclip-42Ia7xHD.js +1 -0
- package/dist/elements/chunks/progress-bar-tzKMWAwO.js +1 -0
- package/dist/elements/chunks/prompt-suggestion-D3F6vP_A.js +1 -0
- package/dist/elements/chunks/reasoning-BJNmmyov.js +1 -0
- package/dist/elements/chunks/resizable-BRsk19qI.js +1 -0
- package/dist/elements/chunks/rotate-cw-BgcggLHC.js +1 -0
- package/dist/elements/chunks/scroll-area-CgGbFFbT.js +1 -0
- package/dist/elements/chunks/separator-8hM4GdHE.js +1 -0
- package/dist/elements/chunks/settings-CaUIYOg-.js +1 -0
- package/dist/elements/chunks/settings-group-CYjOVIZQ.js +1 -0
- package/dist/elements/chunks/share-BTYM3Ddt.js +1 -0
- package/dist/elements/chunks/skeleton-DsTYrPUe.js +1 -0
- package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
- package/dist/elements/chunks/star-9R3LBQ9n.js +1 -0
- package/dist/elements/chunks/store-CYl5Y0_u.js +1 -0
- package/dist/elements/chunks/svelte--5p79yCD.js +1 -0
- package/dist/elements/chunks/text-shimmer-CaYGv8Se.js +1 -0
- package/dist/elements/chunks/textarea-NEr2SLcD.js +1 -0
- package/dist/elements/chunks/thumbs-up-DZYZkFfc.js +1 -0
- package/dist/elements/chunks/toast-store-ClxVZRcc.js +1 -0
- package/dist/elements/chunks/tool-CHZOmoe9.js +1 -0
- package/dist/elements/chunks/tooltip-WpHElELC.js +1 -0
- package/dist/elements/chunks/triangle-alert-BlS80PUg.js +1 -0
- package/dist/elements/chunks/tsx-B8rCNbgL.js +1 -0
- package/dist/elements/chunks/typescript-RycA9KXf.js +1 -0
- package/dist/elements/chunks/use-card-resolution-VSVoxDeJ.js +1 -0
- package/dist/elements/chunks/video-DxTPZqT8.js +1 -0
- package/dist/elements/chunks/vue-BmIZj4XD.js +1 -0
- package/dist/elements/chunks/x-C1qtndxn.js +1 -0
- package/dist/elements/coachmark.d.ts +1 -0
- package/dist/elements/coachmark.js +1 -0
- package/dist/elements/code-block.js +1 -0
- package/dist/elements/command.d.ts +1 -0
- package/dist/elements/command.js +1 -0
- package/dist/elements/compare.d.ts +1 -0
- package/dist/elements/compare.js +1 -0
- package/dist/elements/composer.d.ts +1 -0
- package/dist/elements/composer.js +1 -0
- package/dist/elements/confirm-card.js +1 -0
- package/dist/elements/context-meter.js +1 -0
- package/dist/elements/conversation-list.d.ts +1 -11
- package/dist/elements/conversation-list.js +1 -0
- package/dist/elements/default-input.d.ts +20 -9
- package/dist/elements/define.d.ts +11 -0
- package/dist/elements/dialog.d.ts +1 -0
- package/dist/elements/dialog.js +1 -0
- package/dist/elements/disclosure.d.ts +37 -0
- package/dist/elements/editable-label.d.ts +1 -0
- package/dist/elements/editable-label.js +1 -0
- package/dist/elements/embed.js +1 -0
- package/dist/elements/empty.js +1 -0
- package/dist/elements/feedback-bar.js +1 -0
- package/dist/elements/file-tree.js +1 -0
- package/dist/elements/file-upload.js +1 -0
- package/dist/elements/form.js +1 -0
- package/dist/elements/hover-card.d.ts +1 -0
- package/dist/elements/hover-card.js +1 -0
- package/dist/elements/icon.d.ts +1 -0
- package/dist/elements/icon.js +1 -0
- package/dist/elements/image.js +1 -0
- package/dist/elements/input.d.ts +1 -0
- package/dist/elements/input.js +1 -0
- package/dist/elements/kbd.d.ts +1 -0
- package/dist/elements/kbd.js +1 -0
- package/dist/elements/link-preview.js +1 -0
- package/dist/elements/loader.js +1 -0
- package/dist/elements/markdown.js +1 -0
- package/dist/elements/menu.d.ts +1 -0
- package/dist/elements/menu.js +1 -0
- package/dist/elements/message-skills.d.ts +0 -16
- package/dist/elements/message-skills.js +1 -0
- package/dist/elements/message.js +1 -0
- package/dist/elements/model-switcher.d.ts +1 -11
- package/dist/elements/model-switcher.js +1 -0
- package/dist/elements/nav.d.ts +1 -0
- package/dist/elements/nav.js +1 -0
- package/dist/elements/notice.d.ts +1 -0
- package/dist/elements/notice.js +1 -0
- package/dist/elements/pane-group.d.ts +1 -0
- package/dist/elements/pane-group.js +1 -0
- package/dist/elements/pane.d.ts +1 -0
- package/dist/elements/pane.js +1 -0
- package/dist/elements/popover.js +1 -0
- package/dist/elements/progress-bar.d.ts +1 -0
- package/dist/elements/progress-bar.js +1 -0
- package/dist/elements/prompt-dock.d.ts +1 -0
- package/dist/elements/prompt-dock.js +1 -0
- package/dist/elements/prompt-input.d.ts +1 -9
- package/dist/elements/prompt-input.js +1 -0
- package/dist/elements/prompt-suggestions.d.ts +0 -6
- package/dist/elements/prompt-suggestions.js +1 -0
- package/dist/elements/reasoning.js +1 -0
- package/dist/elements/register.d.ts +10 -0
- package/dist/elements/remote.js +1 -0
- package/dist/elements/resizable.d.ts +1 -17
- package/dist/elements/resizable.js +1 -0
- package/dist/elements/response-stream.js +14 -0
- package/dist/elements/screen.d.ts +1 -0
- package/dist/elements/screen.js +1 -0
- package/dist/elements/scroll-area.d.ts +1 -0
- package/dist/elements/scroll-area.js +1 -0
- package/dist/elements/scroll-button.js +1 -0
- package/dist/elements/search.d.ts +1 -0
- package/dist/elements/search.js +1 -0
- package/dist/elements/segmented.d.ts +1 -0
- package/dist/elements/segmented.js +1 -0
- package/dist/elements/separator.d.ts +1 -0
- package/dist/elements/separator.js +1 -0
- package/dist/elements/setting-item.d.ts +1 -0
- package/dist/elements/setting-item.js +1 -0
- package/dist/elements/settings-group.d.ts +1 -0
- package/dist/elements/settings-group.js +1 -0
- package/dist/elements/skeleton.d.ts +1 -0
- package/dist/elements/skeleton.js +1 -0
- package/dist/elements/slots.d.ts +176 -0
- package/dist/elements/source.d.ts +0 -17
- package/dist/elements/source.js +1 -0
- package/dist/elements/status.d.ts +1 -0
- package/dist/elements/status.js +1 -0
- package/dist/elements/switch.js +1 -0
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +1 -0
- package/dist/elements/tasks.js +1 -0
- package/dist/elements/text-shimmer.js +1 -0
- package/dist/elements/thinking-bar.js +1 -0
- package/dist/elements/toast.d.ts +1 -0
- package/dist/elements/toast.js +1 -0
- package/dist/elements/tool.js +1 -0
- package/dist/elements/tooltip.d.ts +1 -0
- package/dist/elements/tooltip.js +1 -0
- package/dist/elements/voice-input.js +1 -0
- package/dist/elements/voice-output.d.ts +1 -0
- package/dist/elements/voice-output.js +1 -0
- package/dist/elements.d.ts +772 -61
- package/dist/engine-javascript-C1x7zo1_.js +141 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +14405 -9810
- package/dist/kai.es.js +1 -0
- package/dist/llms/llms-full.txt +1234 -107
- package/dist/llms/llms.txt +4 -4
- package/dist/mcp.es.js +217 -6
- package/dist/primitives/card-contract.d.ts +20 -4
- package/dist/primitives/card-recovery.d.ts +55 -0
- package/dist/primitives/composer-model.d.ts +22 -0
- package/dist/primitives/composer-triggers.d.ts +19 -0
- package/dist/primitives/create-kai-chat.d.ts +38 -0
- package/dist/primitives/message-feedback.d.ts +35 -0
- package/dist/primitives/toast-store.d.ts +118 -0
- package/dist/primitives/use-speech-recognition.d.ts +26 -0
- package/dist/react/index.d.ts +1166 -98
- package/dist/react/runtime.d.ts +9 -2
- package/dist/react/use-kai-chat.d.ts +41 -0
- package/dist/react.js +575 -186
- package/dist/register-impl-CYWGkgbI.js +141 -0
- package/dist/schemas/choice.schema.json +5 -0
- package/dist/schemas/tasks.schema.json +7 -2
- package/dist/state/index.d.ts +4 -0
- package/dist/state/messages.d.ts +11 -0
- package/dist/state/stream.d.ts +22 -0
- package/dist/state/suggestions.d.ts +4 -0
- package/dist/state.js +117 -0
- package/dist/theme.tokens.css +87 -0
- package/dist/types.d.ts +4 -0
- package/dist/ui/agent-card.d.ts +61 -0
- package/dist/ui/button.d.ts +5 -2
- package/dist/ui/card.d.ts +61 -0
- package/dist/ui/collapsible.d.ts +15 -1
- package/dist/ui/command.d.ts +33 -0
- package/dist/ui/dialog.d.ts +42 -0
- package/dist/ui/dropdown.d.ts +95 -1
- package/dist/ui/editable-label.d.ts +26 -0
- package/dist/ui/hover-card.d.ts +13 -1
- package/dist/ui/icon.d.ts +18 -0
- package/dist/ui/input.d.ts +33 -0
- package/dist/ui/kbd.d.ts +21 -0
- package/dist/ui/nav.d.ts +62 -0
- package/dist/ui/notice.d.ts +31 -0
- package/dist/ui/overlay.d.ts +12 -0
- package/dist/ui/pane-grid.d.ts +58 -0
- package/dist/ui/pane-group.d.ts +75 -0
- package/dist/ui/pane.d.ts +78 -0
- package/dist/ui/popover.d.ts +11 -1
- package/dist/ui/progress-bar.d.ts +23 -0
- package/dist/ui/prompt-dock.d.ts +96 -0
- package/dist/ui/resizable.d.ts +12 -0
- package/dist/ui/scroll-area.d.ts +4 -0
- package/dist/ui/segmented.d.ts +28 -0
- package/dist/ui/settings-group.d.ts +32 -0
- package/dist/ui/skeleton.d.ts +12 -0
- package/dist/ui/stat.d.ts +16 -0
- package/dist/ui/status.d.ts +13 -0
- package/dist/ui/switch.d.ts +8 -1
- package/dist/ui/tabs.d.ts +35 -0
- package/dist/ui/tooltip.d.ts +20 -1
- package/frameworks/react/index.tsx +1100 -163
- package/frameworks/react/runtime.tsx +30 -1
- package/frameworks/react/use-kai-chat.tsx +87 -0
- package/llms-full.txt +1234 -107
- package/llms.txt +4 -4
- package/package.json +36 -12
- package/src/agent-tooling/mcp/manifest.ts +14 -0
- package/src/agent-tooling/mcp/tools/debug.ts +194 -6
- package/src/agent-tooling/mcp/tools/reference.ts +32 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +162 -1
- package/src/components/artifact.tsx +98 -13
- package/src/components/attachment-types.ts +13 -0
- package/src/components/attachments.tsx +4 -14
- package/src/components/card.tsx +109 -15
- package/src/components/chain-of-thought.tsx +210 -1
- package/src/components/chat-scope-picker.tsx +13 -3
- package/src/components/chat-thread.tsx +238 -137
- package/src/components/choice-card.tsx +136 -15
- package/src/components/coachmark.tsx +245 -0
- package/src/components/component-meta.json +921 -108
- package/src/components/composer-dom.ts +159 -0
- package/src/components/composer-highlight.ts +242 -0
- package/src/components/composer-history.ts +82 -0
- package/src/components/composer.tsx +979 -0
- package/src/components/confirm-card.tsx +69 -2
- package/src/components/conversation-item.tsx +69 -7
- package/src/components/conversation-list.tsx +134 -37
- package/src/components/dismissed-stub.tsx +80 -0
- package/src/components/file-tree.tsx +197 -47
- package/src/components/form-widgets.tsx +9 -7
- package/src/components/form.tsx +161 -20
- package/src/components/image.tsx +3 -5
- package/src/components/link-preview.tsx +5 -4
- package/src/components/markdown.tsx +5 -2
- package/src/components/message.tsx +174 -14
- package/src/components/model-switcher.tsx +19 -4
- package/src/components/prompt-input.tsx +46 -75
- package/src/components/prompt-suggestion.tsx +48 -16
- package/src/components/reasoning.tsx +29 -6
- package/src/components/response-compare-types.ts +127 -0
- package/src/components/response-compare.tsx +460 -0
- package/src/components/screen.tsx +176 -0
- package/src/components/tasks-card.tsx +374 -23
- package/src/components/toast.tsx +486 -0
- package/src/components/tool-types.ts +10 -0
- package/src/components/tool.tsx +22 -16
- package/src/components/use-card-resolution.ts +24 -8
- package/src/components/voice-input.tsx +102 -19
- package/src/components/voice-output.tsx +202 -0
- package/src/elements/agent-card.tsx +67 -0
- package/src/elements/artifact.tsx +53 -4
- package/src/elements/attachments.tsx +1 -0
- package/src/elements/autoloader.ts +89 -0
- package/src/elements/avatar.tsx +33 -0
- package/src/elements/badge.tsx +32 -0
- package/src/elements/button.tsx +126 -0
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/card.tsx +124 -31
- package/src/elements/cards.tsx +81 -7
- package/src/elements/chain-of-thought.tsx +77 -32
- package/src/elements/chat-scope-picker.tsx +35 -8
- package/src/elements/chat-types.ts +9 -2
- package/src/elements/chat-workspace.tsx +157 -43
- package/src/elements/chat.tsx +49 -25
- package/src/elements/choice.tsx +71 -19
- package/src/elements/coachmark.tsx +90 -0
- package/src/elements/command.tsx +209 -0
- package/src/elements/compare.tsx +110 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +151 -0
- package/src/elements/confirm-card.tsx +36 -11
- package/src/elements/conversation-list.tsx +80 -11
- package/src/elements/default-input.tsx +75 -53
- package/src/elements/define.tsx +47 -6
- package/src/elements/dialog.tsx +98 -0
- package/src/elements/disclosure.ts +73 -0
- package/src/elements/editable-label.tsx +125 -0
- package/src/elements/element-manifest.json +310 -0
- package/src/elements/element-meta.json +5954 -1605
- package/src/elements/element-types.d.ts +771 -60
- package/src/elements/file-tree.tsx +12 -2
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +54 -11
- package/src/elements/hover-card.tsx +80 -0
- package/src/elements/icon-names.json +48 -0
- package/src/elements/icon.tsx +41 -0
- package/src/elements/input.tsx +203 -0
- package/src/elements/kbd.tsx +45 -0
- package/src/elements/menu.tsx +217 -0
- package/src/elements/message.tsx +84 -55
- package/src/elements/model-switcher.tsx +27 -1
- package/src/elements/nav.tsx +87 -0
- package/src/elements/notice.tsx +53 -0
- package/src/elements/pane-group.tsx +119 -0
- package/src/elements/pane.tsx +129 -0
- package/src/elements/popover.tsx +39 -17
- package/src/elements/progress-bar.tsx +39 -0
- package/src/elements/prompt-dock.tsx +81 -0
- package/src/elements/prompt-input.tsx +112 -63
- package/src/elements/prompt-suggestions.tsx +25 -9
- package/src/elements/reasoning.tsx +30 -9
- package/src/elements/register-impl.ts +41 -3
- package/src/elements/register.ts +20 -3
- package/src/elements/remote.tsx +4 -0
- package/src/elements/resizable.tsx +48 -11
- package/src/elements/screen.tsx +90 -0
- package/src/elements/scroll-area.tsx +33 -0
- package/src/elements/search.tsx +180 -0
- package/src/elements/segmented.tsx +135 -0
- package/src/elements/separator.tsx +36 -0
- package/src/elements/setting-item.tsx +50 -0
- package/src/elements/settings-group.tsx +37 -0
- package/src/elements/skeleton.tsx +45 -0
- package/src/elements/slots.ts +607 -0
- package/src/elements/source.tsx +13 -6
- package/src/elements/status.tsx +42 -0
- package/src/elements/styles.css +19 -0
- package/src/elements/switch.tsx +89 -14
- package/src/elements/tabs.tsx +95 -0
- package/src/elements/tasks.tsx +70 -14
- package/src/elements/toast.tsx +89 -0
- package/src/elements/tool.tsx +43 -9
- package/src/elements/tooltip.tsx +85 -0
- package/src/elements/video-poster.jpg +0 -0
- package/src/elements/voice-input.tsx +66 -16
- package/src/elements/voice-output.tsx +69 -0
- package/src/index.ts +35 -2
- package/src/primitives/card-contract.ts +14 -5
- package/src/primitives/card-recovery.ts +149 -0
- package/src/primitives/card-routing.ts +3 -0
- package/src/primitives/card-schemas/choice.schema.json +5 -0
- package/src/primitives/card-schemas/tasks.schema.json +7 -2
- package/src/primitives/composer-model.ts +49 -0
- package/src/primitives/composer-triggers.ts +35 -0
- package/src/primitives/create-kai-chat.ts +66 -0
- package/src/primitives/message-feedback.ts +123 -0
- package/src/primitives/toast-store.ts +276 -0
- package/src/primitives/use-speech-recognition.ts +135 -0
- package/src/state/index.ts +6 -0
- package/src/state/messages.ts +36 -0
- package/src/state/stream.ts +92 -0
- package/src/state/suggestions.ts +9 -0
- package/src/types.ts +4 -0
- package/src/ui/agent-card.tsx +212 -0
- package/src/ui/button.tsx +16 -5
- package/src/ui/card.tsx +245 -0
- package/src/ui/collapsible.tsx +30 -4
- package/src/ui/command.tsx +99 -0
- package/src/ui/dialog.tsx +217 -0
- package/src/ui/dropdown.tsx +375 -13
- package/src/ui/editable-label.tsx +112 -0
- package/src/ui/hover-card.tsx +28 -5
- package/src/ui/icon.tsx +110 -0
- package/src/ui/input.tsx +132 -0
- package/src/ui/kbd.tsx +118 -0
- package/src/ui/nav.tsx +282 -0
- package/src/ui/notice.tsx +84 -0
- package/src/ui/overlay.tsx +108 -2
- package/src/ui/pane-grid.tsx +116 -0
- package/src/ui/pane-group.tsx +280 -0
- package/src/ui/pane.tsx +193 -0
- package/src/ui/popover.tsx +16 -2
- package/src/ui/progress-bar.tsx +83 -0
- package/src/ui/prompt-dock.tsx +145 -0
- package/src/ui/resizable.tsx +69 -1
- package/src/ui/scroll-area.tsx +14 -3
- package/src/ui/segmented.tsx +109 -0
- package/src/ui/settings-group.tsx +67 -0
- package/src/ui/skeleton.tsx +62 -5
- package/src/ui/stat.tsx +41 -0
- package/src/ui/status.tsx +48 -0
- package/src/ui/switch.tsx +49 -23
- package/src/ui/tabs.tsx +168 -0
- package/src/ui/tooltip.tsx +57 -7
- package/theme.css +113 -0
- package/dist/components/slash-command.d.ts +0 -15
- package/dist/kitn-chat.es.js +0 -1
- package/dist/register-impl-wCsFPoh9.js +0 -77
- package/dist/register-ujhr1nBS.js +0 -1
- package/src/components/slash-command.tsx +0 -232
- package/src/elements/framework-usage.json +0 -563
- /package/dist/{kitn-chat-provider.d.ts → kai-provider.d.ts} +0 -0
- /package/dist/{kitn-chat-provider.es.js → kai-provider.es.js} +0 -0
|
@@ -30,8 +30,30 @@ export interface FileTreeFile {
|
|
|
30
30
|
language?: string;
|
|
31
31
|
/** Kind — drives the icon + whether Code applies. */
|
|
32
32
|
type?: 'html' | 'pdf' | 'image' | 'other';
|
|
33
|
+
/** Lines added vs the base. Rendered as a trailing `+N` stat (success hue,
|
|
34
|
+
* tabular-nums). Only shown when present; omit for a plain file row. */
|
|
35
|
+
additions?: number;
|
|
36
|
+
/** Lines removed vs the base. Rendered as a trailing `-N` stat (error hue). */
|
|
37
|
+
deletions?: number;
|
|
38
|
+
/** Change status vs the base. Drives a small trailing status letter in the
|
|
39
|
+
* conventional VCS hue (added=green, modified=amber, deleted=red,
|
|
40
|
+
* renamed=blue, untracked=muted). Only shown when present. */
|
|
41
|
+
status?: 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked';
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
/** A change status a file can carry. */
|
|
45
|
+
export type FileStatus = NonNullable<FileTreeFile['status']>;
|
|
46
|
+
|
|
47
|
+
/** Per-status letter, accessible word, and tool-hue text class. Conventional VCS
|
|
48
|
+
* colors so a changed-files view reads at a glance. */
|
|
49
|
+
const STATUS_META: Record<FileStatus, { letter: string; label: string; class: string }> = {
|
|
50
|
+
added: { letter: 'A', label: 'added', class: 'text-tool-green' },
|
|
51
|
+
modified: { letter: 'M', label: 'modified', class: 'text-tool-amber' },
|
|
52
|
+
deleted: { letter: 'D', label: 'deleted', class: 'text-tool-red' },
|
|
53
|
+
renamed: { letter: 'R', label: 'renamed', class: 'text-tool-blue' },
|
|
54
|
+
untracked: { letter: 'U', label: 'untracked', class: 'text-muted-foreground' },
|
|
55
|
+
};
|
|
56
|
+
|
|
35
57
|
/** A folder node in the built tree. */
|
|
36
58
|
export interface FileTreeFolderNode {
|
|
37
59
|
kind: 'folder';
|
|
@@ -134,6 +156,48 @@ function iconFor(type: FileTreeFile['type']) {
|
|
|
134
156
|
}
|
|
135
157
|
}
|
|
136
158
|
|
|
159
|
+
/** Every folder path in the tree (render order) — drives collapse-all/expand-all. */
|
|
160
|
+
function collectFolderPaths(nodes: FileTreeNode[], out: string[] = []): string[] {
|
|
161
|
+
for (const n of nodes) {
|
|
162
|
+
if (n.kind === 'folder') {
|
|
163
|
+
out.push(n.path);
|
|
164
|
+
collectFolderPaths(n.children, out);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Every leaf file in the tree — drives the summary header totals. */
|
|
171
|
+
function collectFiles(nodes: FileTreeNode[], out: FileTreeFile[] = []): FileTreeFile[] {
|
|
172
|
+
for (const n of nodes) {
|
|
173
|
+
if (n.kind === 'folder') collectFiles(n.children, out);
|
|
174
|
+
else out.push(n.file);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Whether a file carries any diff metadata (status / additions / deletions). */
|
|
180
|
+
function hasDiffMeta(file: FileTreeFile): boolean {
|
|
181
|
+
return file.status !== undefined || file.additions !== undefined || file.deletions !== undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Accessible name for a changed-file row: the file name plus its status and diff
|
|
186
|
+
* stats, e.g. `App.tsx, modified, 3 additions, 1 deletion`. Returns `undefined`
|
|
187
|
+
* when the file has no diff metadata, so a plain row keeps its text content as
|
|
188
|
+
* the accessible name (unchanged behavior).
|
|
189
|
+
*/
|
|
190
|
+
function fileAccessibleName(name: string, file: FileTreeFile): string | undefined {
|
|
191
|
+
if (!hasDiffMeta(file)) return undefined;
|
|
192
|
+
const parts: string[] = [];
|
|
193
|
+
if (file.status) parts.push(STATUS_META[file.status].label);
|
|
194
|
+
if (file.additions !== undefined)
|
|
195
|
+
parts.push(`${file.additions} addition${file.additions === 1 ? '' : 's'}`);
|
|
196
|
+
if (file.deletions !== undefined)
|
|
197
|
+
parts.push(`${file.deletions} deletion${file.deletions === 1 ? '' : 's'}`);
|
|
198
|
+
return [name, ...parts].join(', ');
|
|
199
|
+
}
|
|
200
|
+
|
|
137
201
|
export interface FileTreeProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
138
202
|
/** Flat file list; folders are derived from `/`-delimited paths. */
|
|
139
203
|
files: FileTreeFile[];
|
|
@@ -143,6 +207,10 @@ export interface FileTreeProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>,
|
|
|
143
207
|
onSelect?: (path: string, file: FileTreeFile) => void;
|
|
144
208
|
/** Folder paths expanded by default. When omitted, all folders start open. */
|
|
145
209
|
defaultExpanded?: string[];
|
|
210
|
+
/** Show a summary header above the tree: the changed-file count, the summed
|
|
211
|
+
* `+additions / -deletions`, and a Collapse-all/Expand-all toggle wired to the
|
|
212
|
+
* folder-expand state. Off by default (no header — unchanged behavior). */
|
|
213
|
+
summary?: boolean;
|
|
146
214
|
}
|
|
147
215
|
|
|
148
216
|
/**
|
|
@@ -157,6 +225,7 @@ export function FileTree(props: FileTreeProps): JSX.Element {
|
|
|
157
225
|
'activeFile',
|
|
158
226
|
'onSelect',
|
|
159
227
|
'defaultExpanded',
|
|
228
|
+
'summary',
|
|
160
229
|
'class',
|
|
161
230
|
]);
|
|
162
231
|
|
|
@@ -196,6 +265,23 @@ export function FileTree(props: FileTreeProps): JSX.Element {
|
|
|
196
265
|
}
|
|
197
266
|
};
|
|
198
267
|
|
|
268
|
+
// Summary header derivations (only read when `summary` is set).
|
|
269
|
+
const folderPaths = createMemo(() => collectFolderPaths(tree()));
|
|
270
|
+
const leafFiles = createMemo(() => collectFiles(tree()));
|
|
271
|
+
const fileCount = () => leafFiles().length;
|
|
272
|
+
const totalAdds = () => leafFiles().reduce((sum, f) => sum + (f.additions ?? 0), 0);
|
|
273
|
+
const totalDels = () => leafFiles().reduce((sum, f) => sum + (f.deletions ?? 0), 0);
|
|
274
|
+
const anyOpen = () => folderPaths().some((p) => isOpen(p));
|
|
275
|
+
const collapseAll = () => {
|
|
276
|
+
if (defaultAllOpen()) setClosedSet(new Set<string>(folderPaths()));
|
|
277
|
+
else setOpenSet(new Set<string>());
|
|
278
|
+
};
|
|
279
|
+
const expandAll = () => {
|
|
280
|
+
if (defaultAllOpen()) setClosedSet(new Set<string>());
|
|
281
|
+
else setOpenSet(new Set<string>(folderPaths()));
|
|
282
|
+
};
|
|
283
|
+
const toggleAll = () => (anyOpen() ? collapseAll() : expandAll());
|
|
284
|
+
|
|
199
285
|
const [focusedPath, setFocusedPath] = createSignal<string | undefined>();
|
|
200
286
|
|
|
201
287
|
const selectFile = (node: FileTreeFileNode) => {
|
|
@@ -247,27 +333,59 @@ export function FileTree(props: FileTreeProps): JSX.Element {
|
|
|
247
333
|
};
|
|
248
334
|
|
|
249
335
|
return (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
336
|
+
<>
|
|
337
|
+
<Show when={local.summary}>
|
|
338
|
+
<div
|
|
339
|
+
part="summary"
|
|
340
|
+
class="flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground border-b border-border"
|
|
341
|
+
>
|
|
342
|
+
<span class="font-medium text-foreground">
|
|
343
|
+
{fileCount()} {fileCount() === 1 ? 'file' : 'files'} changed
|
|
344
|
+
</span>
|
|
345
|
+
<Show when={totalAdds() > 0}>
|
|
346
|
+
<span class="text-tool-green tabular-nums">+{totalAdds()}</span>
|
|
347
|
+
</Show>
|
|
348
|
+
<Show when={totalDels() > 0}>
|
|
349
|
+
<span class="text-tool-red tabular-nums">-{totalDels()}</span>
|
|
350
|
+
</Show>
|
|
351
|
+
<Show when={folderPaths().length > 0}>
|
|
352
|
+
<button
|
|
353
|
+
type="button"
|
|
354
|
+
class={cn(
|
|
355
|
+
'ml-auto rounded-md px-1.5 py-0.5 font-medium text-muted-foreground outline-none',
|
|
356
|
+
'hover:bg-muted hover:text-foreground',
|
|
357
|
+
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
358
|
+
)}
|
|
359
|
+
aria-label={anyOpen() ? 'Collapse all folders' : 'Expand all folders'}
|
|
360
|
+
onClick={toggleAll}
|
|
361
|
+
>
|
|
362
|
+
{anyOpen() ? 'Collapse all' : 'Expand all'}
|
|
363
|
+
</button>
|
|
364
|
+
</Show>
|
|
365
|
+
</div>
|
|
366
|
+
</Show>
|
|
367
|
+
<div
|
|
368
|
+
role="tree"
|
|
369
|
+
aria-label="Files"
|
|
370
|
+
class={cn('text-sm select-none', local.class)}
|
|
371
|
+
{...rest}
|
|
372
|
+
>
|
|
373
|
+
<For each={tree()}>
|
|
374
|
+
{(node) => (
|
|
375
|
+
<TreeNode
|
|
376
|
+
node={node}
|
|
377
|
+
depth={0}
|
|
378
|
+
isOpen={isOpen}
|
|
379
|
+
toggle={toggle}
|
|
380
|
+
activeFile={() => local.activeFile}
|
|
381
|
+
focusedPath={focusedPath}
|
|
382
|
+
onSelectFile={selectFile}
|
|
383
|
+
onKeyDown={onKeyDown}
|
|
384
|
+
/>
|
|
385
|
+
)}
|
|
386
|
+
</For>
|
|
387
|
+
</div>
|
|
388
|
+
</>
|
|
271
389
|
);
|
|
272
390
|
}
|
|
273
391
|
|
|
@@ -300,32 +418,64 @@ function TreeNode(props: TreeNodeProps): JSX.Element {
|
|
|
300
418
|
return (
|
|
301
419
|
<Show
|
|
302
420
|
when={props.node.kind === 'folder'}
|
|
303
|
-
fallback={
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
421
|
+
fallback={(() => {
|
|
422
|
+
const file = (props.node as FileTreeFileNode).file;
|
|
423
|
+
const ariaName = fileAccessibleName(node().name, file);
|
|
424
|
+
const hasMeta = ariaName !== undefined;
|
|
425
|
+
const status = file.status ? STATUS_META[file.status] : undefined;
|
|
426
|
+
return (
|
|
427
|
+
<div
|
|
428
|
+
role="treeitem"
|
|
429
|
+
aria-selected={isActive()}
|
|
430
|
+
aria-label={ariaName}
|
|
431
|
+
data-tree-path={props.node.path}
|
|
432
|
+
data-tree-kind="file"
|
|
433
|
+
tabindex={tabIndex()}
|
|
434
|
+
class={cn(
|
|
435
|
+
'flex items-center gap-1.5 rounded-md py-1 pr-2 cursor-pointer outline-none',
|
|
436
|
+
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
437
|
+
isActive()
|
|
438
|
+
? 'bg-primary/10 text-foreground font-medium'
|
|
439
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
|
440
|
+
)}
|
|
441
|
+
style={indent()}
|
|
442
|
+
onClick={() => props.onSelectFile(props.node as FileTreeFileNode)}
|
|
443
|
+
onKeyDown={(e) => props.onKeyDown(e, props.node)}
|
|
444
|
+
>
|
|
445
|
+
<span class="w-3.5 shrink-0" />
|
|
446
|
+
{(() => {
|
|
447
|
+
const Icon = iconFor(file.type);
|
|
448
|
+
return <Icon size={14} class="shrink-0 opacity-70" aria-hidden="true" />;
|
|
449
|
+
})()}
|
|
450
|
+
<span class={cn('truncate', hasMeta && 'min-w-0 flex-1')}>{node().name}</span>
|
|
451
|
+
<Show when={hasMeta}>
|
|
452
|
+
<span
|
|
453
|
+
class="ml-auto flex items-center gap-1.5 shrink-0 pl-2 text-xs"
|
|
454
|
+
aria-hidden="true"
|
|
455
|
+
>
|
|
456
|
+
<Show when={file.additions !== undefined}>
|
|
457
|
+
<span part="stat-additions" class="text-tool-green tabular-nums">
|
|
458
|
+
+{file.additions}
|
|
459
|
+
</span>
|
|
460
|
+
</Show>
|
|
461
|
+
<Show when={file.deletions !== undefined}>
|
|
462
|
+
<span part="stat-deletions" class="text-tool-red tabular-nums">
|
|
463
|
+
-{file.deletions}
|
|
464
|
+
</span>
|
|
465
|
+
</Show>
|
|
466
|
+
<Show when={status}>
|
|
467
|
+
<span
|
|
468
|
+
part="status"
|
|
469
|
+
class={cn('w-3 text-center font-mono font-semibold', status!.class)}
|
|
470
|
+
>
|
|
471
|
+
{status!.letter}
|
|
472
|
+
</span>
|
|
473
|
+
</Show>
|
|
474
|
+
</span>
|
|
475
|
+
</Show>
|
|
476
|
+
</div>
|
|
477
|
+
);
|
|
478
|
+
})()}
|
|
329
479
|
>
|
|
330
480
|
<div role="none">
|
|
331
481
|
<div
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type JSX, For, Show, createSignal } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
import { Textarea } from '../ui/textarea';
|
|
4
|
+
import { Input, FIELD_BASE as inputBase } from '../ui/input';
|
|
4
5
|
import { Star } from 'lucide-solid';
|
|
5
6
|
import type { FormField } from './form';
|
|
6
7
|
|
|
@@ -19,9 +20,6 @@ export interface WidgetProps {
|
|
|
19
20
|
onBlur: () => void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const inputBase =
|
|
23
|
-
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none';
|
|
24
|
-
|
|
25
23
|
function ariaProps(p: WidgetProps) {
|
|
26
24
|
return {
|
|
27
25
|
'aria-required': p.required || undefined,
|
|
@@ -52,20 +50,24 @@ export function TextWidget(
|
|
|
52
50
|
return 'text';
|
|
53
51
|
}
|
|
54
52
|
};
|
|
53
|
+
// Render the bare `Input` control (no label/hint/error): `kai-form`'s FieldRow
|
|
54
|
+
// already supplies the label, description, and inline error around the widget.
|
|
55
|
+
// `Input` owns the field styling (its `FIELD_BASE` is the former `inputBase`),
|
|
56
|
+
// so the rendered input is byte-identical to the old raw `<input>`.
|
|
55
57
|
return (
|
|
56
|
-
<
|
|
58
|
+
<Input
|
|
57
59
|
id={props.id}
|
|
58
60
|
data-control
|
|
59
61
|
type={inputType()}
|
|
60
|
-
class={cn(inputBase, props.invalid && 'border-destructive dark:border-red-400/70')}
|
|
61
62
|
value={(props.value as string) ?? ''}
|
|
62
63
|
placeholder={props.placeholder}
|
|
64
|
+
invalid={props.invalid}
|
|
63
65
|
disabled={props.disabled}
|
|
64
66
|
minLength={props.field.minLength}
|
|
65
67
|
maxLength={props.field.maxLength}
|
|
66
68
|
{...ariaProps(props)}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
onValueInput={(value) => props.onInput(value)}
|
|
70
|
+
onValueChange={() => props.onBlur()}
|
|
69
71
|
/>
|
|
70
72
|
);
|
|
71
73
|
}
|
package/src/components/form.tsx
CHANGED
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
createMemo,
|
|
9
9
|
createEffect,
|
|
10
10
|
on,
|
|
11
|
+
onMount,
|
|
11
12
|
ErrorBoundary,
|
|
12
13
|
} from 'solid-js';
|
|
13
14
|
import { createStore, produce, unwrap } from 'solid-js/store';
|
|
14
15
|
import { cn } from '../utils/cn';
|
|
15
16
|
import { Button } from '../ui/button';
|
|
16
17
|
import { Card } from './card';
|
|
18
|
+
import { DismissedStub } from './dismissed-stub';
|
|
17
19
|
import {
|
|
18
20
|
validateAgainstSchema,
|
|
19
21
|
type JsonSchema,
|
|
@@ -354,6 +356,25 @@ export function buildResult(
|
|
|
354
356
|
// The <Form> component.
|
|
355
357
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
356
358
|
|
|
359
|
+
/** Imperative handle exposed via `controllerRef` — surfaces the form's latent
|
|
360
|
+
* capabilities (focus the first/first-invalid control, validate, programmatic
|
|
361
|
+
* send, reset to defaults, dismiss/reopen) so the `<kai-form>` facade can forward
|
|
362
|
+
* them as instance methods. */
|
|
363
|
+
export interface FormController {
|
|
364
|
+
/** Focus the first control, or the first INVALID control after a failed validation. */
|
|
365
|
+
focus(options?: FocusOptions): void;
|
|
366
|
+
/** Run full validation + submit: focus the first invalid field on failure, else emit `submit`. */
|
|
367
|
+
send(): void;
|
|
368
|
+
/** Run client-side validation now and return per-field errors WITHOUT submitting. */
|
|
369
|
+
validate(): { valid: boolean; errors?: Record<string, string> };
|
|
370
|
+
/** Re-seed from each field's `default` and clear errors. */
|
|
371
|
+
reset(): void;
|
|
372
|
+
/** Trigger the dismiss path (emit `dismiss` + collapse to the re-openable stub). */
|
|
373
|
+
dismiss(): void;
|
|
374
|
+
/** Re-open a dismissed card from its stub (emit `reopen`). */
|
|
375
|
+
reopen(): void;
|
|
376
|
+
}
|
|
377
|
+
|
|
357
378
|
export interface FormProps {
|
|
358
379
|
/** The form definition (CardEnvelope.data). */
|
|
359
380
|
data?: FormDefinition;
|
|
@@ -369,6 +390,16 @@ export interface FormProps {
|
|
|
369
390
|
class?: string;
|
|
370
391
|
/** When set, render the chromed read-only view instead of the form inputs. */
|
|
371
392
|
resolution?: CardResolution;
|
|
393
|
+
/** Controlled field values — when set, this wins over internal/uncontrolled state. */
|
|
394
|
+
values?: Record<string, unknown>;
|
|
395
|
+
/** Initial values overlaying the schema defaults (uncontrolled seed). */
|
|
396
|
+
defaultValues?: Record<string, unknown>;
|
|
397
|
+
/** Disable all fields + submit. */
|
|
398
|
+
disabled?: boolean;
|
|
399
|
+
/** Fires on input with the current coerced values + validity (distinct from submit). */
|
|
400
|
+
onValuesChange?: (payload: { values: Record<string, unknown>; valid: boolean }) => void;
|
|
401
|
+
/** Receive the imperative controller once mounted. */
|
|
402
|
+
controllerRef?: (controller: FormController) => void;
|
|
372
403
|
}
|
|
373
404
|
|
|
374
405
|
const DEFAULT_FORM: FormDefinition = { type: 'object', properties: {} };
|
|
@@ -382,7 +413,10 @@ const DEFAULT_FORM: FormDefinition = { type: 'object', properties: {} };
|
|
|
382
413
|
*/
|
|
383
414
|
export function Form(props: FormProps): JSX.Element {
|
|
384
415
|
const merged = mergeProps({ cardId: 'kai-form' }, props);
|
|
385
|
-
const [local] = splitProps(merged, [
|
|
416
|
+
const [local] = splitProps(merged, [
|
|
417
|
+
'data', 'cardId', 'heading', 'host', 'hostElement', 'class', 'resolution',
|
|
418
|
+
'values', 'defaultValues', 'disabled', 'onValuesChange', 'controllerRef',
|
|
419
|
+
]);
|
|
386
420
|
|
|
387
421
|
const ctxHost = useCardHost();
|
|
388
422
|
|
|
@@ -413,7 +447,34 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
413
447
|
const [values, setValues] = createStore<Record<string, unknown>>({});
|
|
414
448
|
const [errors, setErrors] = createStore<Record<string, string>>({});
|
|
415
449
|
|
|
450
|
+
// Build the seed value map: schema `default`s, overlaid by `defaultValues`, then
|
|
451
|
+
// (when controlled) the live `values` prop. The controlled prop always wins.
|
|
452
|
+
const seedMap = (d: FormDefinition): Record<string, unknown> => {
|
|
453
|
+
const next: Record<string, unknown> = {};
|
|
454
|
+
for (const [key, field] of Object.entries(d.properties ?? {})) {
|
|
455
|
+
if (field.default !== undefined) next[key] = field.default;
|
|
456
|
+
else if (field.type === 'array') next[key] = [];
|
|
457
|
+
}
|
|
458
|
+
if (local.defaultValues) Object.assign(next, local.defaultValues);
|
|
459
|
+
if (local.values) Object.assign(next, local.values);
|
|
460
|
+
return next;
|
|
461
|
+
};
|
|
462
|
+
|
|
416
463
|
const seed = (d: FormDefinition): void => {
|
|
464
|
+
const next = seedMap(d);
|
|
465
|
+
setValues(produce((s) => {
|
|
466
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
467
|
+
Object.assign(s, next);
|
|
468
|
+
}));
|
|
469
|
+
setErrors(produce((s) => {
|
|
470
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
471
|
+
}));
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Reset to defaults only (ignore the controlled `values`/`defaultValues` overlay):
|
|
475
|
+
// re-seed from each field's schema `default` and clear errors. Used by reset().
|
|
476
|
+
const seedDefaults = (): void => {
|
|
477
|
+
const d = def();
|
|
417
478
|
const next: Record<string, unknown> = {};
|
|
418
479
|
for (const [key, field] of Object.entries(d.properties ?? {})) {
|
|
419
480
|
if (field.default !== undefined) next[key] = field.default;
|
|
@@ -431,23 +492,40 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
431
492
|
// Reseed whenever a NEW valid definition arrives.
|
|
432
493
|
createEffect(on(() => local.data, () => { if (envelopeValid().ok) seed(def()); }));
|
|
433
494
|
|
|
495
|
+
// Controlled values: when the consumer drives `values`, mirror it into the store
|
|
496
|
+
// (the controlled prop wins over local edits). Deferred so mount's seed runs first.
|
|
497
|
+
createEffect(on(() => local.values, (v) => {
|
|
498
|
+
if (!v) return;
|
|
499
|
+
setValues(produce((s) => {
|
|
500
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
501
|
+
Object.assign(s, v);
|
|
502
|
+
}));
|
|
503
|
+
}, { defer: true }));
|
|
504
|
+
|
|
434
505
|
// ready + error lifecycle emits.
|
|
435
506
|
createEffect(on(envelopeValid, (state) => {
|
|
436
507
|
if (state.ok) emit({ kind: 'ready', cardId: local.cardId });
|
|
437
508
|
else emit({ kind: 'error', cardId: local.cardId, message: state.message });
|
|
438
509
|
}));
|
|
439
510
|
|
|
440
|
-
// Surface the resolved state for host styling.
|
|
511
|
+
// Surface the resolved state for host styling. Only a `submit` resolution is the
|
|
512
|
+
// "submitted" state; a deferred `dismissed` (or `expired`) is not.
|
|
441
513
|
createEffect(() => {
|
|
442
514
|
const el = local.hostElement;
|
|
443
515
|
if (!el) return;
|
|
444
|
-
if (res.
|
|
516
|
+
if (res.resolution()?.kind === 'submit') el.setAttribute('data-kai-resolved', 'submitted');
|
|
445
517
|
else el.removeAttribute('data-kai-resolved');
|
|
446
518
|
});
|
|
447
519
|
|
|
448
520
|
const setField = (key: string, raw: unknown): void => {
|
|
449
521
|
setValues(key, raw);
|
|
450
522
|
if (errors[key]) setErrors(key, undefined as unknown as string);
|
|
523
|
+
// Fire the live change signal: current coerced values + validity (distinct from
|
|
524
|
+
// the terminal submit). Validation here is read-only — it doesn't surface errors.
|
|
525
|
+
const snapshot = unwrap(values) as Record<string, unknown>;
|
|
526
|
+
const out = buildResult(def(), snapshot);
|
|
527
|
+
const { valid } = validateForm(def(), snapshot);
|
|
528
|
+
local.onValuesChange?.({ values: out, valid });
|
|
451
529
|
};
|
|
452
530
|
|
|
453
531
|
const validateField = (key: string): void => {
|
|
@@ -460,12 +538,21 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
460
538
|
setErrors(key, single.fieldErrors[key]);
|
|
461
539
|
};
|
|
462
540
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
541
|
+
// Resolve the root to query controls inside (the live <form> when called from the
|
|
542
|
+
// submit event, else the host element's shadow root for a programmatic call).
|
|
543
|
+
const queryRoot = (formEl?: HTMLElement | null): ParentNode =>
|
|
544
|
+
formEl?.closest('form') ?? formEl ?? local.hostElement?.shadowRoot ?? document;
|
|
545
|
+
|
|
546
|
+
// Focus the control for a field key (the `[data-control]` inside its `[data-field]`).
|
|
547
|
+
const focusControl = (key: string, root: ParentNode, options?: FocusOptions): void => {
|
|
548
|
+
root.querySelector<HTMLElement>(`[data-field="${cssEscape(key)}"] [data-control]`)?.focus(options);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// Run full validation; on failure surface the per-field errors + focus the first
|
|
552
|
+
// invalid control and return false; on success emit `submit` + resolve. Shared by
|
|
553
|
+
// the form's onSubmit handler and the controller's send().
|
|
554
|
+
const runSubmit = (formEl?: HTMLElement | null): boolean => {
|
|
555
|
+
if (res.isResolved()) return false;
|
|
469
556
|
const snapshot = unwrap(values);
|
|
470
557
|
const result = validateForm(def(), snapshot as Record<string, unknown>);
|
|
471
558
|
setErrors(produce((s) => {
|
|
@@ -474,20 +561,67 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
474
561
|
}));
|
|
475
562
|
if (!result.valid) {
|
|
476
563
|
const firstBad = keys().find((k) => result.fieldErrors[k]);
|
|
477
|
-
if (firstBad
|
|
478
|
-
|
|
479
|
-
queueMicrotask(() =>
|
|
480
|
-
const root: ParentNode = formEl?.closest('form') ?? formEl ?? document;
|
|
481
|
-
root.querySelector<HTMLElement>(`[data-field="${cssEscape(firstBad)}"] [data-control]`)?.focus();
|
|
482
|
-
});
|
|
564
|
+
if (firstBad) {
|
|
565
|
+
const root = queryRoot(formEl);
|
|
566
|
+
queueMicrotask(() => focusControl(firstBad, root));
|
|
483
567
|
}
|
|
484
|
-
return;
|
|
568
|
+
return false;
|
|
485
569
|
}
|
|
486
570
|
const out = buildResult(def(), snapshot as Record<string, unknown>);
|
|
487
571
|
emit({ kind: 'submit', cardId: local.cardId, data: out });
|
|
488
572
|
res.setLocal({ kind: 'submit', data: out });
|
|
573
|
+
return true;
|
|
489
574
|
};
|
|
490
575
|
|
|
576
|
+
const onSubmit = (e: Event): void => {
|
|
577
|
+
e.preventDefault();
|
|
578
|
+
// Capture the <form> synchronously — `e.currentTarget` is nulled out once the
|
|
579
|
+
// event has finished dispatching (so it can't be read in a later microtask).
|
|
580
|
+
runSubmit(e.currentTarget as HTMLElement | null);
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Dismiss: emit `dismiss` AND optimistically flip to a `dismissed` resolution so
|
|
584
|
+
// the form collapses to its re-openable stub immediately.
|
|
585
|
+
const onDismiss = (): void => {
|
|
586
|
+
if (res.isResolved()) return;
|
|
587
|
+
emit({ kind: 'dismiss', cardId: local.cardId });
|
|
588
|
+
res.setLocal({ kind: 'dismissed' });
|
|
589
|
+
};
|
|
590
|
+
const onReopen = (): void => emit({ kind: 'reopen', cardId: local.cardId });
|
|
591
|
+
|
|
592
|
+
const disabled = (): boolean => local.disabled === true;
|
|
593
|
+
|
|
594
|
+
// Imperative controller (Pattern C): hand the facade a handle over the form's
|
|
595
|
+
// latent capabilities. focus targets the first control (or the first INVALID one
|
|
596
|
+
// after a failed validation); validate runs the existing validateForm and surfaces
|
|
597
|
+
// the per-field errors WITHOUT submitting; send runs the same path as the Submit
|
|
598
|
+
// button; reset re-seeds from schema defaults; dismiss/reopen drive the stub.
|
|
599
|
+
onMount(() => {
|
|
600
|
+
local.controllerRef?.({
|
|
601
|
+
focus: (options) => {
|
|
602
|
+
const root = queryRoot();
|
|
603
|
+
const firstBad = keys().find((k) => errors[k]);
|
|
604
|
+
const target = firstBad ?? keys()[0];
|
|
605
|
+
if (target) focusControl(target, root, options);
|
|
606
|
+
},
|
|
607
|
+
send: () => { runSubmit(); },
|
|
608
|
+
validate: () => {
|
|
609
|
+
const snapshot = unwrap(values) as Record<string, unknown>;
|
|
610
|
+
const result = validateForm(def(), snapshot);
|
|
611
|
+
setErrors(produce((s) => {
|
|
612
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
613
|
+
Object.assign(s, result.fieldErrors);
|
|
614
|
+
}));
|
|
615
|
+
return result.valid
|
|
616
|
+
? { valid: true }
|
|
617
|
+
: { valid: false, errors: result.fieldErrors };
|
|
618
|
+
},
|
|
619
|
+
reset: () => seedDefaults(),
|
|
620
|
+
dismiss: () => onDismiss(),
|
|
621
|
+
reopen: () => onReopen(),
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
491
625
|
const actions = createMemo(() => def()['x-kai-actions'] ?? []);
|
|
492
626
|
const submitLabel = () => def()['x-kai-submitLabel'] ?? 'Submit';
|
|
493
627
|
const dismissible = () => def()['x-kai-dismissible'] === true;
|
|
@@ -509,6 +643,12 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
509
643
|
return <Card heading={local.heading} errorMessage="The form failed to render." />;
|
|
510
644
|
}}
|
|
511
645
|
>
|
|
646
|
+
<Show
|
|
647
|
+
when={!res.isDeferred()}
|
|
648
|
+
fallback={
|
|
649
|
+
<DismissedStub type="form" title={local.heading ?? def().title} onReopen={onReopen} />
|
|
650
|
+
}
|
|
651
|
+
>
|
|
512
652
|
<Card
|
|
513
653
|
heading={local.heading ?? def().title}
|
|
514
654
|
description={def().description}
|
|
@@ -522,7 +662,7 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
522
662
|
<Button
|
|
523
663
|
type="button"
|
|
524
664
|
variant="ghost"
|
|
525
|
-
onClick={
|
|
665
|
+
onClick={onDismiss}
|
|
526
666
|
>
|
|
527
667
|
Dismiss
|
|
528
668
|
</Button>
|
|
@@ -539,7 +679,7 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
539
679
|
</Button>
|
|
540
680
|
)}
|
|
541
681
|
</For>
|
|
542
|
-
<Button type="submit" form={formId()}>
|
|
682
|
+
<Button type="submit" form={formId()} disabled={disabled()}>
|
|
543
683
|
{submitLabel()}
|
|
544
684
|
</Button>
|
|
545
685
|
</div>
|
|
@@ -566,7 +706,7 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
566
706
|
inlineMax={inlineMax()}
|
|
567
707
|
value={() => values[key]}
|
|
568
708
|
error={() => errors[key]}
|
|
569
|
-
disabled={
|
|
709
|
+
disabled={disabled()}
|
|
570
710
|
onInput={(v) => setField(key, v)}
|
|
571
711
|
onBlur={() => validateField(key)}
|
|
572
712
|
/>
|
|
@@ -575,6 +715,7 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
575
715
|
</form>
|
|
576
716
|
</Show>
|
|
577
717
|
</Card>
|
|
718
|
+
</Show>
|
|
578
719
|
</ErrorBoundary>
|
|
579
720
|
</Show>
|
|
580
721
|
);
|
|
@@ -660,7 +801,7 @@ function FieldRow(props: FieldRowProps): JSX.Element {
|
|
|
660
801
|
['fieldset', 'repeater', 'checkbox-group', 'multiselect', 'radio', 'taglist'].includes(widget());
|
|
661
802
|
|
|
662
803
|
return (
|
|
663
|
-
<div class="flex flex-col gap-2 rounded-xl bg-
|
|
804
|
+
<div class="flex flex-col gap-2 rounded-xl bg-surface p-3.5" data-field={props.fieldKey}>
|
|
664
805
|
<Show when={!isGrouped()}>
|
|
665
806
|
<label for={id} class="text-sm font-medium text-foreground">
|
|
666
807
|
{label()}
|
package/src/components/image.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type JSX, splitProps, createSignal, createEffect, onCleanup, Show } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
|
+
import { Skeleton } from '../ui/skeleton';
|
|
3
4
|
|
|
4
5
|
export interface GeneratedImageLike {
|
|
5
6
|
base64?: string;
|
|
@@ -44,13 +45,10 @@ function Image(props: ImageProps) {
|
|
|
44
45
|
<Show
|
|
45
46
|
when={src()}
|
|
46
47
|
fallback={
|
|
47
|
-
<
|
|
48
|
+
<Skeleton
|
|
48
49
|
aria-label={local.alt}
|
|
49
50
|
role="img"
|
|
50
|
-
class={cn(
|
|
51
|
-
'h-auto max-w-full animate-pulse overflow-hidden rounded-md bg-muted',
|
|
52
|
-
local.class
|
|
53
|
-
)}
|
|
51
|
+
class={cn('h-auto max-w-full overflow-hidden', local.class)}
|
|
54
52
|
/>
|
|
55
53
|
}
|
|
56
54
|
>
|