@kitn.ai/ui 0.26.0 → 0.28.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 +25 -6
- package/bin/mcp.js +16 -3
- package/bin/route.js +23 -0
- package/bin/route.test.js +58 -0
- package/dist/{elements/chunks/Icon--Y8vsFal.js → Icon-DObhsRtm.js} +1 -1
- package/dist/action-icons-CcgNN2oT.js +1 -0
- package/dist/arrow-down-CjRU3JgV.js +1 -0
- package/dist/arrow-left-Bnpb1UEW.js +1 -0
- package/dist/artifact-Bwt7OgcR.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-Qr4kg-2b.js +1 -0
- package/dist/{elements/chunks/audio-visualizer-D7lB6mvR.js → audio-visualizer-DToYdfSc.js} +1 -1
- package/dist/badge-CdEu_jn2.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-DQAAOpDz.js +1 -0
- package/dist/{elements/chunks/button-flUbeufF.js → button-DkuOnGLy.js} +1 -1
- package/dist/card-renderer-Jx70m6_l.js +1 -0
- package/dist/card-routing-0YidER_3.js +1 -0
- package/dist/check-njkVdZu2.js +1 -0
- package/dist/checkbox-D7iRB3nL.js +1 -0
- package/dist/checkbox-group-B2_rpoPM.js +1 -0
- package/dist/chevron-down-B72s6PXR.js +1 -0
- package/dist/chevron-right-BatuHzUm.js +1 -0
- package/dist/choice-card-m0BYqqOK.js +1 -0
- package/dist/circle-check-B62pODB3.js +1 -0
- package/dist/circle-x-BPG0iPst.js +1 -0
- package/dist/circle-zx0ByzY9.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/code-block-ClMLKb7C.js +1 -0
- package/dist/{elements/chunks/collapsible-BzbXpPzr.js → collapsible-B4DlWicL.js} +1 -1
- package/dist/components/attachment-types.d.ts +7 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +1 -1
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +4 -0
- package/dist/components/chat-thread.d.ts +135 -2
- package/dist/components/conversation-item.d.ts +35 -7
- package/dist/components/conversation-list.d.ts +17 -5
- package/dist/components/conversation-panel.d.ts +29 -0
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +48 -1
- package/dist/components/home-panel.d.ts +34 -0
- package/dist/components/message.d.ts +25 -3
- package/dist/components/reasoning.d.ts +6 -0
- package/dist/components/scroll-button.d.ts +11 -0
- package/dist/components/thread.d.ts +5 -0
- package/dist/components/tool-types.d.ts +9 -0
- package/dist/components/widget-tab-bar.d.ts +16 -0
- package/dist/composer-sQ0HcvyG.js +106 -0
- package/dist/confirm-card-rQWDQsnC.js +1 -0
- package/dist/construct-cli.es.js +1337 -0
- package/dist/{elements/chunks/context-VLIL3sJ-.js → context-0kg4ZX1J.js} +1 -1
- package/dist/controllable-CnGpMF6r.js +1 -0
- package/dist/conversation-item-BxwQLaFJ.js +1 -0
- package/dist/conversation-list-BCfGN1JU.js +1 -0
- package/dist/copy-n9u9rqpi.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-BFT6c-Y8.js → create-tween-C-q-JCM-.js} +1 -1
- package/dist/{create-tween-CJNlzoaO.js → create-tween-CRrJXxBd.js} +1 -1
- package/dist/{create-tween-CQED4T0g.js → create-tween-DZ3RRDHD.js} +1 -1
- package/dist/{elements/chunks/create-tween-c4Z8TDYz.js → create-tween-uPkArjIG.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +1433 -202
- package/dist/default-input-CaMrJkgH.js +1 -0
- package/dist/define-By-uL588.js +1 -0
- package/dist/define.d.ts +4 -0
- package/dist/define.js +510 -0
- package/dist/define.server.d.ts +5 -0
- package/dist/define.server.js +506 -0
- package/dist/{elements/chunks/disclosure-CcNbyLy0.js → disclosure-WUImBbDv.js} +1 -1
- package/dist/{elements/chunks/dropdown-BgWTthUx.js → dropdown-wZVeJOMd.js} +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/autoloader.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkbox-group.d.ts +1 -0
- package/dist/elements/checkbox-group.js +1 -0
- package/dist/elements/checkbox.d.ts +1 -0
- package/dist/elements/checkbox.js +1 -0
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/define-entry.d.ts +12 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.js +102 -8
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +37 -0
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-grid.d.ts +1 -0
- package/dist/elements/pane-grid.js +1 -0
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/radio-group.d.ts +1 -0
- package/dist/elements/radio-group.js +1 -0
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/select.d.ts +1 -0
- package/dist/elements/select.js +1 -0
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slider.d.ts +1 -0
- package/dist/elements/slider.js +1 -0
- package/dist/elements/slots.d.ts +1 -1
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +380 -26
- package/dist/{elements/chunks/ellipsis-7gU33mL2.js → ellipsis-CbOpJnU3.js} +1 -1
- package/dist/embed-CrfjkbUG.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-CSOEsNlx.js → external-link-ByXAwBBQ.js} +1 -1
- package/dist/file-text-DMib2qwt.js +1 -0
- package/dist/{elements/chunks/file-tree-zvB2i7P9.js → file-tree-CJqM2aP7.js} +1 -1
- package/dist/folder-_BAb929C.js +1 -0
- package/dist/form-DSZgZQZk.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-BQbul9eq.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-CdcfRhYI.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7474 -5857
- package/dist/index.server.js +5965 -4762
- package/dist/info-DsFjGuI_.js +1 -0
- package/dist/input-DKbmkMos.js +1 -0
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kai.es.js +1 -1
- package/dist/kbd-PvdGhX4s.js +1 -0
- package/dist/{elements/chunks/link-preview-OQ9IEq0f.js → link-preview-DbM1866N.js} +1 -1
- package/dist/link-zF8PzWfF.js +1 -0
- package/dist/loader-BM4CEz4G.js +1 -0
- package/dist/{elements/chunks/markdown-DgBFaGAz.js → markdown-LMvmAyBk.js} +7 -7
- package/dist/mcp.es.js +1361 -81
- package/dist/message-BSsgvHvf.js +1 -0
- package/dist/message-BYZECyek.js +1 -0
- package/dist/message-circle-DruwObfX.js +1 -0
- package/dist/message-square-BG6q6mlm.js +1 -0
- package/dist/{elements/chunks/minimize-2-DPIUKTrT.js → minimize-2-BQRoBV1V.js} +1 -1
- package/dist/{elements/chunks/model-switcher-DsT8tFB0.js → model-switcher-CMpCOYcB.js} +1 -1
- package/dist/{elements/chunks/overlay-DuiwdXjm.js → overlay-Cb-MkyDa.js} +1 -1
- package/dist/panel-right-i8warFxT.js +1 -0
- package/dist/paperclip-ByY3UVO6.js +1 -0
- package/dist/play-BvKKq3fX.js +1 -0
- package/dist/primitives/card-data-types.d.ts +49 -0
- package/dist/primitives/card-routing.d.ts +1 -33
- package/dist/primitives/card-validate.d.ts +2 -2
- package/dist/primitives/chat-config.d.ts +29 -2
- package/dist/primitives/conversation-store.d.ts +54 -0
- package/dist/primitives/field-mask.d.ts +96 -0
- package/dist/primitives/field-semantics.d.ts +36 -0
- package/dist/primitives/input-mask.d.ts +46 -0
- package/dist/primitives/url-scheme-policy.d.ts +34 -0
- package/dist/primitives/use-resize-observer.d.ts +2 -2
- package/dist/progress-bar-jVAHvMUf.js +1 -0
- package/dist/prompt-suggestion-CD-qtNgC.js +1 -0
- package/dist/radio-BsoBMhkF.js +1 -0
- package/dist/react/index.d.ts +586 -19
- package/dist/react.js +223 -187
- package/dist/reasoning-4kBPtlTx.js +1 -0
- package/dist/register-impl-B1hLkrI8.js +424 -0
- package/dist/{elements/chunks/resizable-D181b4fM.js → resizable-uPmY9VY-.js} +1 -1
- package/dist/rotate-ccw-B2xn93py.js +1 -0
- package/dist/rotate-cw-CMVLoCyH.js +1 -0
- package/dist/schemas/form.schema.json +21 -1
- package/dist/schemas/tool-defs.d.ts +11 -1
- package/dist/schemas.js +81 -81
- package/dist/scroll-area-BqLUo8ZZ.js +1 -0
- package/dist/scroll-button-DslXiJyL.js +1 -0
- package/dist/select-2cLRtPGT.js +1 -0
- package/dist/{elements/chunks/separator-CeJHQqLW.js → separator-BWQtNJcM.js} +1 -1
- package/dist/{elements/chunks/settings-CqaHgJF9.js → settings-BBR63jLg.js} +1 -1
- package/dist/{elements/chunks/settings-group-CjqjLCOn.js → settings-group-B2ciy-Iw.js} +1 -1
- package/dist/{elements/chunks/skeleton-D8akBnKS.js → skeleton-CkHI-HgC.js} +1 -1
- package/dist/slider-DDR9U-i0.js +1 -0
- package/dist/{solid-BtdqNZI7.js → solid-BzCFdL25.js} +9771 -7594
- package/dist/{solid-CnavwBye.js → solid-DruVOXnf.js} +7893 -6183
- package/dist/solid.d.ts +16 -2
- package/dist/solid.js +256 -243
- package/dist/solid.server.js +256 -243
- package/dist/source-GEnFF528.js +1 -0
- package/dist/star-CilHq8GP.js +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/mock.d.ts +32 -2
- package/dist/state.js +133 -108
- package/dist/store-Ciyk6eZG.js +1 -0
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-KkeQIbuH.js +1 -0
- package/dist/tasks-card-BrN4bggJ.js +1 -0
- package/dist/text-shimmer-Dcj1MHuq.js +1 -0
- package/dist/textarea-Dpvbngbg.js +1 -0
- package/dist/theme.tokens.css +110 -13
- package/dist/{elements/chunks/thumbs-up-DX2Ijk3H.js → thumbs-up-CsT9Wzb9.js} +1 -1
- package/dist/toast-store-tgK6MuMv.js +1 -0
- package/dist/{elements/chunks/tool-DKQ2D1DP.js → tool-DzUjIBT8.js} +1 -1
- package/dist/{elements/chunks/tooltip-B0G99y8w.js → tooltip-Bkkg_4t0.js} +1 -1
- package/dist/trash-2-jBKxjGGQ.js +1 -0
- package/dist/{elements/chunks/triangle-alert-F9cyUEyy.js → triangle-alert-DlQ68ppf.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- package/dist/types.d.ts +48 -3
- package/dist/typescript-RycA9KXf.js +1 -6
- package/dist/ui/checkbox-group.d.ts +83 -0
- package/dist/ui/checkbox.d.ts +40 -0
- package/dist/ui/dock.d.ts +51 -0
- package/dist/ui/icon.d.ts +3 -0
- package/dist/ui/input.d.ts +48 -2
- package/dist/ui/radio.d.ts +85 -0
- package/dist/ui/select.d.ts +71 -0
- package/dist/ui/slider.d.ts +65 -0
- package/dist/ui/switch.d.ts +9 -2
- package/dist/upload-TcAHXv9W.js +1 -0
- package/dist/url-scheme-policy-DR5DgvkM.js +1 -0
- package/dist/use-card-resolution-DaZqAdWf.js +1 -0
- package/dist/{elements/chunks/variant-aurora-qt6Zct8M.js → variant-aurora-BhLuYxJR.js} +1 -1
- package/dist/{variant-aurora-B8II3-I1.js → variant-aurora-CNmfHhcx.js} +2 -2
- package/dist/{variant-aurora-CMNWOSJR.js → variant-aurora-D6GsdTPA.js} +2 -2
- package/dist/{variant-aurora-CcuDMWwQ.js → variant-aurora-DbW_OWL2.js} +1 -1
- package/dist/{elements/chunks/variant-custom-tJp22_BQ.js → variant-custom-BRvaNHSi.js} +1 -1
- package/dist/{variant-custom-L16JLLZy.js → variant-custom-BvYHR62b.js} +1 -1
- package/dist/{variant-custom-BdvYC7af.js → variant-custom-DMTrEO5w.js} +2 -2
- package/dist/{variant-custom-C714mqKl.js → variant-custom-DV2x254s.js} +2 -2
- package/dist/{variant-wave-ecwykeyL.js → variant-wave-BNMGrDfj.js} +2 -2
- package/dist/{variant-wave-2S4La2Ka.js → variant-wave-BpDd8t3Z.js} +1 -1
- package/dist/{variant-wave-D7ruiA1W.js → variant-wave-CyBueXj9.js} +2 -2
- package/dist/{elements/chunks/variant-wave-CaQ5ti39.js → variant-wave-sp-qwsdI.js} +1 -1
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/x-Z5UntkIJ.js +1 -0
- package/frameworks/react/index.tsx +210 -22
- package/llms-full.txt +1184 -22
- package/llms.txt +6 -3
- package/package.json +17 -4
- package/src/agent-tooling/README.md +192 -0
- package/src/agent-tooling/catalog/invariants.ts +3 -3
- package/src/agent-tooling/catalog/surfaces.ts +72 -0
- package/src/agent-tooling/construct/cli-entry.ts +8 -0
- package/src/agent-tooling/construct/cli.ts +147 -0
- package/src/agent-tooling/construct/codegen.ts +1635 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +319 -0
- package/src/agent-tooling/construct/dev.ts +148 -0
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +32 -0
- package/src/agent-tooling/construct/schema.ts +452 -0
- package/src/agent-tooling/mcp/server.ts +2 -1
- package/src/agent-tooling/mcp/tools/construct.ts +130 -0
- package/src/agent-tooling/mcp/tools/debug.ts +126 -4
- package/src/agent-tooling/mcp/tools/reference.ts +104 -3
- package/src/agent-tooling/recipes/composed-thread.ts +714 -0
- package/src/agent-tooling/recipes/index.ts +22 -0
- package/src/agent-tooling/recipes/types.ts +32 -0
- package/src/agent-tooling/registry.ts +5 -0
- package/src/components/attachment-types.ts +7 -0
- package/src/components/audio-visualizer/fit-scale.ts +1 -1
- package/src/components/card-renderer.tsx +39 -8
- package/src/components/card.tsx +10 -2
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +602 -43
- package/src/components/choice-card.tsx +102 -37
- package/src/components/coachmark.tsx +1 -1
- package/src/components/code-block.tsx +2 -2
- package/src/components/composer.tsx +89 -15
- package/src/components/confirm-card.tsx +20 -4
- package/src/components/conversation-item.tsx +56 -9
- package/src/components/conversation-list.tsx +25 -10
- package/src/components/conversation-panel.tsx +118 -0
- package/src/components/empty.tsx +14 -3
- package/src/components/file-tree.tsx +5 -1
- package/src/components/form-widgets.tsx +257 -195
- package/src/components/form.tsx +297 -9
- package/src/components/home-panel.tsx +159 -0
- package/src/components/loader.tsx +12 -5
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +71 -11
- package/src/components/prompt-input.tsx +7 -1
- package/src/components/prompt-suggestion.tsx +4 -1
- package/src/components/reasoning.tsx +30 -3
- package/src/components/response-compare.tsx +2 -2
- package/src/components/scroll-button.tsx +56 -4
- package/src/components/source.tsx +6 -1
- package/src/components/tasks-card.tsx +58 -16
- package/src/components/thread.tsx +10 -1
- package/src/components/toast.tsx +1 -1
- package/src/components/tool-types.ts +9 -0
- package/src/components/widget-tab-bar.tsx +73 -0
- package/src/elements/attachments.tsx +3 -1
- package/src/elements/chat-workspace.tsx +1 -1
- package/src/elements/chat.tsx +115 -5
- package/src/elements/checkbox-group.tsx +164 -0
- package/src/elements/checkbox.tsx +134 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +12 -1
- package/src/elements/conversation-item.tsx +62 -22
- package/src/elements/conversation-list.tsx +2 -2
- package/src/elements/define-entry.ts +12 -0
- package/src/elements/dock.tsx +11 -0
- package/src/elements/element-data-types.ts +40 -0
- package/src/elements/element-manifest.json +24 -0
- package/src/elements/element-meta.json +767 -34
- package/src/elements/element-nonscalar.json +17 -0
- package/src/elements/element-types.d.ts +380 -26
- package/src/elements/feedback-bar.tsx +5 -0
- package/src/elements/icon-names.json +29 -0
- package/src/elements/input.tsx +312 -23
- package/src/elements/message.tsx +2 -0
- package/src/elements/pane-grid.tsx +111 -0
- package/src/elements/prompt-input.tsx +8 -2
- package/src/elements/radio-group.tsx +129 -0
- package/src/elements/register-impl.ts +6 -0
- package/src/elements/scroll-button.tsx +54 -6
- package/src/elements/select.tsx +165 -0
- package/src/elements/slider.tsx +171 -0
- package/src/elements/slots.ts +1 -1
- package/src/elements/styles.css +89 -3
- package/src/elements/thread.tsx +2 -0
- package/src/elements/toast.tsx +14 -1
- package/src/index.ts +5 -2
- package/src/primitives/card-data-types.ts +49 -0
- package/src/primitives/card-host.tsx +4 -2
- package/src/primitives/card-routing.ts +7 -56
- package/src/primitives/card-schemas/form.schema.json +21 -1
- package/src/primitives/card-validate.ts +10 -2
- package/src/primitives/chat-config.tsx +33 -6
- package/src/primitives/conversation-store.ts +248 -0
- package/src/primitives/field-mask.ts +256 -0
- package/src/primitives/field-semantics.ts +115 -0
- package/src/primitives/input-mask.ts +853 -0
- package/src/primitives/toast-store.ts +66 -4
- package/src/primitives/url-scheme-policy.ts +70 -0
- package/src/primitives/use-resize-observer.ts +2 -2
- package/src/schemas/tool-defs.ts +12 -2
- package/src/solid.ts +20 -2
- package/src/state/index.ts +1 -1
- package/src/state/mock.ts +92 -6
- package/src/types.ts +45 -3
- package/src/ui/agent-card.tsx +2 -2
- package/src/ui/avatar.tsx +1 -1
- package/src/ui/checkbox-group.tsx +153 -0
- package/src/ui/checkbox.tsx +63 -0
- package/src/ui/dock.tsx +192 -7
- package/src/ui/icon.tsx +53 -0
- package/src/ui/input.tsx +249 -9
- package/src/ui/kbd.tsx +1 -1
- package/src/ui/nav.tsx +1 -1
- package/src/ui/pane-group.tsx +2 -2
- package/src/ui/radio.tsx +150 -0
- package/src/ui/select.tsx +168 -0
- package/src/ui/slider.tsx +178 -0
- package/src/ui/switch.tsx +49 -18
- package/src/utils/cn.ts +11 -5
- package/theme.css +112 -13
- package/dist/elements/chunks/action-icons-D9u3OzYW.js +0 -1
- package/dist/elements/chunks/arrow-left-CVh1TeN7.js +0 -1
- package/dist/elements/chunks/artifact-BkYcfIlZ.js +0 -1
- package/dist/elements/chunks/attachments-DwfNlubq.js +0 -1
- package/dist/elements/chunks/badge-Q0-Cmwzz.js +0 -1
- package/dist/elements/chunks/bash-InADTalH.js +0 -1
- package/dist/elements/chunks/card-renderer-3Dyl-FpN.js +0 -1
- package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
- package/dist/elements/chunks/check-B5kBPKeo.js +0 -1
- package/dist/elements/chunks/chevron-down-DZ1GS39h.js +0 -1
- package/dist/elements/chunks/chevron-right-CvP7OUd2.js +0 -1
- package/dist/elements/chunks/choice-card-D4QUIOWZ.js +0 -1
- package/dist/elements/chunks/circle-CDf9g6MQ.js +0 -1
- package/dist/elements/chunks/circle-check-CA47Nqbz.js +0 -1
- package/dist/elements/chunks/circle-x-CJ_HVbC8.js +0 -1
- package/dist/elements/chunks/code-block-BdLiTitd.js +0 -1
- package/dist/elements/chunks/composer-Byve-n8K.js +0 -69
- package/dist/elements/chunks/confirm-card-B0flE3MN.js +0 -1
- package/dist/elements/chunks/controllable-DTwTIlwp.js +0 -1
- package/dist/elements/chunks/conversation-item-BDztezOG.js +0 -1
- package/dist/elements/chunks/copy-Nf-kqxXH.js +0 -1
- package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
- package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
- package/dist/elements/chunks/default-input-C2iLFq0b.js +0 -1
- package/dist/elements/chunks/define-wjHR1MkU.js +0 -1
- package/dist/elements/chunks/download-C4V7tBdb.js +0 -1
- package/dist/elements/chunks/embed-D3KewsIJ.js +0 -1
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
- package/dist/elements/chunks/file-text-D1G8khle.js +0 -1
- package/dist/elements/chunks/folder-WW2LGLid.js +0 -1
- package/dist/elements/chunks/form-CQirdC4W.js +0 -1
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
- package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
- package/dist/elements/chunks/hover-card-CJJkfHTK.js +0 -1
- package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
- package/dist/elements/chunks/icon-Cfgmf4fa.js +0 -1
- package/dist/elements/chunks/info-DX3YMOt6.js +0 -1
- package/dist/elements/chunks/input-BtW9U6Sq.js +0 -1
- package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
- package/dist/elements/chunks/json-DxJze_jm.js +0 -1
- package/dist/elements/chunks/kbd-LGXwuT_V.js +0 -1
- package/dist/elements/chunks/link-2My69BqT.js +0 -1
- package/dist/elements/chunks/loader-iOxc_Tu-.js +0 -1
- package/dist/elements/chunks/message-VMZohrXx.js +0 -1
- package/dist/elements/chunks/message-circle-CpXMM-_n.js +0 -1
- package/dist/elements/chunks/message-square-DxcWtETQ.js +0 -1
- package/dist/elements/chunks/message-tZmV2A3p.js +0 -1
- package/dist/elements/chunks/paperclip-CRZVp4z_.js +0 -1
- package/dist/elements/chunks/progress-bar-DNy66e_E.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-Bn64Nl7L.js +0 -1
- package/dist/elements/chunks/reasoning-Emn9N4-T.js +0 -1
- package/dist/elements/chunks/rotate-cw-CPWe37D9.js +0 -1
- package/dist/elements/chunks/scroll-area-ClZkZxIk.js +0 -1
- package/dist/elements/chunks/scroll-button-DPE-t3x_.js +0 -1
- package/dist/elements/chunks/share-CyfnNEqp.js +0 -1
- package/dist/elements/chunks/source-UWCvmVak.js +0 -1
- package/dist/elements/chunks/star-DoHFAikQ.js +0 -1
- package/dist/elements/chunks/store-Cjq0qwYN.js +0 -1
- package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
- package/dist/elements/chunks/tasks-card-CmZ5xh6v.js +0 -1
- package/dist/elements/chunks/text-shimmer-DYn09zrz.js +0 -1
- package/dist/elements/chunks/textarea-skiGHURM.js +0 -1
- package/dist/elements/chunks/toast-store-CFnQpThX.js +0 -1
- package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
- package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
- package/dist/elements/chunks/use-card-resolution-DoA2f_EU.js +0 -1
- package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
- package/dist/elements/chunks/x-B6UnOiLw.js +0 -1
- package/dist/llms/llms-full.txt +0 -2949
- package/dist/llms/llms.txt +0 -156
- package/dist/register-impl-DpE7icIb.js +0 -293
- package/dist/ui/stat.d.ts +0 -16
- package/src/ui/stat.tsx +0 -41
- /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
- /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
- /package/dist/{elements/chunks/slots-DaNjX0rc.js → slots-DaNjX0rc.js} +0 -0
package/src/ui/input.tsx
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { type JSX, Show, splitProps, createUniqueId } from 'solid-js';
|
|
1
|
+
import { type JSX, Show, splitProps, createUniqueId, createEffect, onCleanup, untrack } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
|
+
import type { CaseMode } from '../primitives/field-mask';
|
|
4
|
+
import { fieldSemantics, type FieldSemanticType } from '../primitives/field-semantics';
|
|
5
|
+
import {
|
|
6
|
+
createInputMask,
|
|
7
|
+
type CopyPolicy,
|
|
8
|
+
type InputMask,
|
|
9
|
+
type InputMaskRejectReason,
|
|
10
|
+
} from '../primitives/input-mask';
|
|
3
11
|
|
|
4
12
|
export interface InputProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'onInput' | 'onChange' | 'size'> {
|
|
5
13
|
/** Field label rendered above the control and linked via `for`/`id`. */
|
|
@@ -16,12 +24,58 @@ export interface InputProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElemen
|
|
|
16
24
|
leading?: JSX.Element;
|
|
17
25
|
/** Trailing affix (icon, inline button). Rendered inside the field row, after the input. */
|
|
18
26
|
trailing?: JSX.Element;
|
|
19
|
-
/** Fires per keystroke with the current value
|
|
27
|
+
/** Fires per keystroke with the current value — the CANONICAL value when a mask is
|
|
28
|
+
* active (digits for `tel`/`ssn`/`credit-card`, the formatted text for
|
|
29
|
+
* `custom`), and the raw text of the field otherwise. */
|
|
20
30
|
onValueInput?: (value: string) => void;
|
|
21
|
-
/** Fires on commit (blur) with the current value. */
|
|
31
|
+
/** Fires on commit (blur) with the current value; canonical when a mask is active. */
|
|
22
32
|
onValueChange?: (value: string) => void;
|
|
33
|
+
|
|
34
|
+
// --- Form-field formats (spec §7.2). All five are SCALARS, which is what lets them
|
|
35
|
+
// survive as HTML attributes on the `<kai-input>` facade. Absent `format` AND absent
|
|
36
|
+
// `semantic` is the behavior of today, byte for byte (owner decision 1 / spec §1.1): no mask,
|
|
37
|
+
// no extra attributes, nothing.
|
|
38
|
+
|
|
39
|
+
/** Tier-2 mask pattern: `#` a digit, `@` an alphanumeric, `*` an obscurable
|
|
40
|
+
* alphanumeric, everything else a positional literal (`@@@-####`).
|
|
41
|
+
*
|
|
42
|
+
* The literal `default` is the OPT-IN sentinel: it resolves to the default format of
|
|
43
|
+
* `semantic` (`tel` → `###-###-####`). It cannot collide with a real pattern — `default`
|
|
44
|
+
* as a format is eight literals with no fill position, i.e. a field that can hold
|
|
45
|
+
* nothing — and an opt-in token is needed because decision 1 forbids a bare semantic
|
|
46
|
+
* type from starting to mask on its own. */
|
|
47
|
+
format?: string;
|
|
48
|
+
/** The placeholder guide shown at unfilled positions, aligned position-for-position
|
|
49
|
+
* with `format` (` - - `, `mm/dd/yyyy`). Without it the field shows only up
|
|
50
|
+
* to the last typed character. */
|
|
51
|
+
guide?: string;
|
|
52
|
+
/** Tier-1 semantic type. On its own it applies `inputmode` / `autocomplete` /
|
|
53
|
+
* `spellcheck` / `autocorrect` / `autocapitalize` and decides the canonical value —
|
|
54
|
+
* it never starts masking by itself. */
|
|
55
|
+
semantic?: FieldSemanticType;
|
|
56
|
+
/** Case folding applied to typed and pasted text. Defaults to `preserve`. */
|
|
57
|
+
caseMode?: CaseMode;
|
|
58
|
+
/** What a copy or cut of a masked field puts on the clipboard. Defaults to `canonical`. */
|
|
59
|
+
copyPolicy?: CopyPolicy;
|
|
60
|
+
/** A mask refused, or partly refused, some content — `full`, `wrong-class`,
|
|
61
|
+
* `over-capacity`, or `format-change-clipped`. Not a scalar and therefore not an
|
|
62
|
+
* attribute: the facade projects this onto its `kai-input-rejected` event.
|
|
63
|
+
*
|
|
64
|
+
* NOT an error state. `format-change-clipped` in particular fires on a routine
|
|
65
|
+
* reactive `format` change, so this widget deliberately does not touch `invalid` —
|
|
66
|
+
* validity belongs to the consumer, and stays there. */
|
|
67
|
+
onMaskReject?: (detail: { reason: InputMaskRejectReason; data: string }) => void;
|
|
23
68
|
}
|
|
24
69
|
|
|
70
|
+
/** The `format` value that means: resolve the default format of the semantic type.
|
|
71
|
+
*
|
|
72
|
+
* EXPORTED because `src/elements/input.tsx` has to recognise the same sentinel when it
|
|
73
|
+
* resolves a seeded value ahead of the masker, and a restated literal there would fail
|
|
74
|
+
* SILENTLY if this string ever changed: `format === 'default'` would simply go false and
|
|
75
|
+
* `compileMask('default')` would compile seven literals, garbling the seed with no error
|
|
76
|
+
* anywhere. One exported constant instead of two copies of a magic string. */
|
|
77
|
+
export const DEFAULT_FORMAT = 'default';
|
|
78
|
+
|
|
25
79
|
// The single source of the field shell styling — lifted verbatim from the
|
|
26
80
|
// `inputBase` constant that used to live in `src/components/form-widgets.tsx`.
|
|
27
81
|
// `Input` now owns it; the form widgets render `Input` rather than re-pasting it.
|
|
@@ -37,7 +91,12 @@ const ROW_INPUT =
|
|
|
37
91
|
'min-w-0 flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed';
|
|
38
92
|
|
|
39
93
|
const SIZE_SM = 'px-2.5 py-1';
|
|
40
|
-
|
|
94
|
+
// The invalid-state border, EXPORTED because `src/ui/select.tsx` renders the same
|
|
95
|
+
// field box and a second hand-typed copy of this string is exactly the kind of
|
|
96
|
+
// restatement that rots (`docs/coupling-map.md` §4). `INVALID` stays as the local
|
|
97
|
+
// alias so the three call sites below read unchanged.
|
|
98
|
+
export const FIELD_INVALID = 'border-destructive dark:border-red-400/70';
|
|
99
|
+
const INVALID = FIELD_INVALID;
|
|
41
100
|
|
|
42
101
|
// Suppress the native search affordances Chrome/WebKit render for `type="search"`.
|
|
43
102
|
// Without this the browser's `::-webkit-search-cancel-button` (×) stacks on top of
|
|
@@ -61,6 +120,7 @@ export function Input(props: InputProps): JSX.Element {
|
|
|
61
120
|
const [local, rest] = splitProps(props, [
|
|
62
121
|
'class', 'label', 'hint', 'error', 'size', 'invalid',
|
|
63
122
|
'leading', 'trailing', 'onValueInput', 'onValueChange', 'onBlur', 'id', 'disabled',
|
|
123
|
+
'format', 'guide', 'semantic', 'caseMode', 'copyPolicy', 'onMaskReject',
|
|
64
124
|
]);
|
|
65
125
|
|
|
66
126
|
const id = local.id ?? createUniqueId();
|
|
@@ -69,6 +129,138 @@ export function Input(props: InputProps): JSX.Element {
|
|
|
69
129
|
const hasHint = () => !!local.error || !!local.hint;
|
|
70
130
|
const hasAffix = () => local.leading != null || local.trailing != null;
|
|
71
131
|
|
|
132
|
+
// -------------------------------------------------------------------------------
|
|
133
|
+
// Form-field formats (spec §7.2). Tier 1 is a handful of attributes; tier 2 is one
|
|
134
|
+
// `createInputMask` bound to whichever `<input>` node is currently mounted.
|
|
135
|
+
// -------------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
const semantics = () => (local.semantic === undefined ? undefined : fieldSemantics(local.semantic));
|
|
138
|
+
|
|
139
|
+
// Both warn paths below run from a TRACKED accessor or from a reconfigure, i.e. from a
|
|
140
|
+
// routine reactive render, and both interpolate a value that reaches this widget from
|
|
141
|
+
// the form card and therefore from MODEL output. So: report each distinct bad value
|
|
142
|
+
// ONCE per widget, and clip what gets interpolated. The console is not a sink — this is
|
|
143
|
+
// about a 500-character pattern printing 500 characters on every keystroke, not about
|
|
144
|
+
// injection.
|
|
145
|
+
const warned = new Set<string>();
|
|
146
|
+
const warnOnce = (key: string, message: string): void => {
|
|
147
|
+
if (warned.has(key)) return;
|
|
148
|
+
warned.add(key);
|
|
149
|
+
console.warn(message);
|
|
150
|
+
};
|
|
151
|
+
const clip = (text: string): string =>
|
|
152
|
+
text.length <= 32 ? text : `${text.slice(0, 32)}… (${text.length} chars)`;
|
|
153
|
+
|
|
154
|
+
/** The pattern to mask with, or `undefined` for no mask at all. */
|
|
155
|
+
const maskFormat = (): string | undefined => {
|
|
156
|
+
const format = local.format;
|
|
157
|
+
if (format === undefined || format === '') return undefined;
|
|
158
|
+
if (format !== DEFAULT_FORMAT) return format;
|
|
159
|
+
const resolved = semantics()?.defaultFormat;
|
|
160
|
+
if (resolved === undefined) {
|
|
161
|
+
// Loud, never a silent no-mask: the consumer asked for a mask and is not getting
|
|
162
|
+
// one. `custom` has no default by design (its `format` IS the pattern), and a
|
|
163
|
+
// semantic type that never arrived is the more likely mistake.
|
|
164
|
+
warnOnce(
|
|
165
|
+
`default:${String(local.semantic)}`,
|
|
166
|
+
`Input: format=${DEFAULT_FORMAT} resolves the default format of a semantic type, and ` +
|
|
167
|
+
`semantic=${JSON.stringify(local.semantic)} has none. Rendering unmasked.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return resolved;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
let plainEl: HTMLInputElement | undefined;
|
|
174
|
+
let rowEl: HTMLInputElement | undefined;
|
|
175
|
+
let mask: InputMask | undefined;
|
|
176
|
+
/** The node `mask` is bound to. The affix toggle is the one thing that changes it. */
|
|
177
|
+
let maskedEl: HTMLInputElement | undefined;
|
|
178
|
+
|
|
179
|
+
const detachMask = (): void => {
|
|
180
|
+
mask?.detach();
|
|
181
|
+
mask = undefined;
|
|
182
|
+
maskedEl = undefined;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const warnBadFormat = (format: string, err: unknown, kept: boolean): void => {
|
|
186
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
187
|
+
warnOnce(
|
|
188
|
+
`format:${format}`,
|
|
189
|
+
`Input: format ${JSON.stringify(clip(format))} did not compile (${reason}); ` +
|
|
190
|
+
(kept ? 'the field keeps its previous format.' : 'rendering unmasked.'),
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
createEffect(() => {
|
|
195
|
+
// Everything reactive is read HERE, so the effect re-runs on a config change or an
|
|
196
|
+
// affix toggle and on nothing else.
|
|
197
|
+
const format = maskFormat();
|
|
198
|
+
const config = {
|
|
199
|
+
guide: local.guide,
|
|
200
|
+
semantic: local.semantic,
|
|
201
|
+
caseMode: local.caseMode,
|
|
202
|
+
copyPolicy: local.copyPolicy,
|
|
203
|
+
};
|
|
204
|
+
// WHICH NODE IS MOUNTED. `Input` caches two `<input>`s and `<Show>` swaps between
|
|
205
|
+
// them when a leading/trailing affix appears or disappears — the one legitimate node
|
|
206
|
+
// change in this file (spec §8.1). The `ref` of a node fires only when it is BUILT, and
|
|
207
|
+
// the cached node is built once, so a toggle back to an existing node notifies
|
|
208
|
+
// nobody. Re-attachment therefore has to be explicit, and this read is what makes it
|
|
209
|
+
// happen. Everything else about this widget is pinned to keeping the SAME node alive
|
|
210
|
+
// (`tests/ui/input-node-identity.test.tsx`), which is the only reason a long-lived
|
|
211
|
+
// masker on it is safe at all.
|
|
212
|
+
//
|
|
213
|
+
// THE ASSUMPTION THIS RELIES ON, stated: `plainEl`/`rowEl` are plain mutable refs, so
|
|
214
|
+
// this reads a node only because Solid runs the `<Show>` render effect that BUILDS it
|
|
215
|
+
// (and its `ref`) before this user effect. If that ever stopped holding, `el` would be
|
|
216
|
+
// `undefined` on the toggle and the branch below would detach with nothing queued to
|
|
217
|
+
// re-attach — silently. The affix tests in the node-identity file are what would catch it.
|
|
218
|
+
const el = hasAffix() ? rowEl : plainEl;
|
|
219
|
+
|
|
220
|
+
untrack(() => {
|
|
221
|
+
if (format === undefined || el === undefined) {
|
|
222
|
+
detachMask();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (mask !== undefined && maskedEl === el) {
|
|
227
|
+
// Same node, new configuration: RE-CONFIGURE. Re-creating would drop the undo
|
|
228
|
+
// stack and re-seed from the DOM text. `update()` is atomic — a format that
|
|
229
|
+
// fails to compile leaves the masker exactly as it was — so a half-typed
|
|
230
|
+
// reactive pattern cannot corrupt the field.
|
|
231
|
+
try {
|
|
232
|
+
mask.update({ format, ...config });
|
|
233
|
+
} catch (err) {
|
|
234
|
+
warnBadFormat(format, err, true);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// First attach, or the affix toggle. Carry the value across: the incoming node is
|
|
240
|
+
// a different element and knows nothing about what the user typed into the old one.
|
|
241
|
+
const carried = mask?.getFormattedValue() ?? el.value;
|
|
242
|
+
detachMask();
|
|
243
|
+
try {
|
|
244
|
+
mask = createInputMask(el, {
|
|
245
|
+
...config,
|
|
246
|
+
format,
|
|
247
|
+
initialValue: carried,
|
|
248
|
+
// The callback of the masker, not the DOM `input` event, is the emission point: a
|
|
249
|
+
// canceled `beforeinput` means a real browser never fires `input` at all for a
|
|
250
|
+
// typed character.
|
|
251
|
+
onInput: ({ canonical }) => local.onValueInput?.(canonical),
|
|
252
|
+
onReject: (detail) => local.onMaskReject?.(detail),
|
|
253
|
+
});
|
|
254
|
+
maskedEl = el;
|
|
255
|
+
} catch (err) {
|
|
256
|
+
// A bad pattern falls back to a plain text field, loudly (spec §7.3).
|
|
257
|
+
warnBadFormat(format, err, false);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
onCleanup(detachMask);
|
|
263
|
+
|
|
72
264
|
// The class arrives as an ACCESSOR, not a string, and this is load-bearing.
|
|
73
265
|
//
|
|
74
266
|
// Solid evaluates a `<Show>` `fallback` inside the Show's own memo. When the
|
|
@@ -83,20 +275,57 @@ export function Input(props: InputProps): JSX.Element {
|
|
|
83
275
|
// Solid compiles it into a nested effect that sets the attribute on the
|
|
84
276
|
// EXISTING node. Same reason the affix branch was always fine: it inserts the
|
|
85
277
|
// input through a function. Pinned by `tests/ui/input-node-identity.test.tsx`.
|
|
86
|
-
|
|
278
|
+
// Tier-1 attributes (spec §2). Each is a DEFAULT the semantic type supplies: an
|
|
279
|
+
// explicit prop always wins, because the consumer knows something the enum does not.
|
|
280
|
+
// With no `semantic` every one of these is `undefined` — the attribute is simply not
|
|
281
|
+
// set, which is what byte-for-byte parity with the behavior of today means here.
|
|
282
|
+
const inputmodeAttr = () => rest.inputmode ?? semantics()?.inputmode;
|
|
283
|
+
const autocompleteAttr = () =>
|
|
284
|
+
(rest.autocomplete ?? semantics()?.autocomplete) as JSX.InputHTMLAttributes<HTMLInputElement>['autocomplete'];
|
|
285
|
+
const spellcheckAttr = () => {
|
|
286
|
+
const explicit = rest.spellcheck;
|
|
287
|
+
// Normalized to the attribute spelling so both forms land as an attribute rather
|
|
288
|
+
// than one of them as a property: `spellcheck={false}` is a real value, not an
|
|
289
|
+
// absent one, and it must stay visible in the DOM.
|
|
290
|
+
if (explicit !== undefined) return explicit === true || explicit === 'true' ? 'true' : 'false';
|
|
291
|
+
const semantic = semantics();
|
|
292
|
+
if (semantic === undefined) return undefined;
|
|
293
|
+
return semantic.spellcheck ? 'true' : 'false';
|
|
294
|
+
};
|
|
295
|
+
const autocorrectAttr = () => rest.autocorrect ?? semantics()?.autocorrect;
|
|
296
|
+
const autocapitalizeAttr = () => rest.autocapitalize ?? semantics()?.autocapitalize;
|
|
297
|
+
|
|
298
|
+
const inputEl = (
|
|
299
|
+
cls: () => string,
|
|
300
|
+
part: string,
|
|
301
|
+
register: (el: HTMLInputElement) => void,
|
|
302
|
+
): JSX.Element => (
|
|
87
303
|
<input
|
|
88
304
|
{...rest}
|
|
305
|
+
ref={register}
|
|
89
306
|
id={id}
|
|
90
307
|
part={part}
|
|
91
308
|
disabled={local.disabled}
|
|
92
309
|
aria-invalid={isInvalid() ? 'true' : undefined}
|
|
93
310
|
aria-describedby={hasHint() ? hintId : undefined}
|
|
311
|
+
inputmode={inputmodeAttr()}
|
|
312
|
+
autocomplete={autocompleteAttr()}
|
|
313
|
+
spellcheck={spellcheckAttr()}
|
|
314
|
+
autocorrect={autocorrectAttr()}
|
|
315
|
+
autocapitalize={autocapitalizeAttr()}
|
|
94
316
|
class={cn(SEARCH_RESET, cls())}
|
|
95
|
-
|
|
317
|
+
// When a mask is active the masker owns the emission (see its `onInput` above) and
|
|
318
|
+
// this handler stands down — otherwise a jsdom-style `input` event, which reaches
|
|
319
|
+
// both, would emit twice, and the raw text it reads is the FORMATTED value rather
|
|
320
|
+
// than the canonical one.
|
|
321
|
+
onInput={(e) => {
|
|
322
|
+
if (mask !== undefined) return;
|
|
323
|
+
local.onValueInput?.(e.currentTarget.value);
|
|
324
|
+
}}
|
|
96
325
|
onBlur={(e) => {
|
|
97
326
|
const handler = local.onBlur;
|
|
98
327
|
if (typeof handler === 'function') handler(e);
|
|
99
|
-
local.onValueChange?.(e.currentTarget.value);
|
|
328
|
+
local.onValueChange?.(mask?.getCanonicalValue() ?? e.currentTarget.value);
|
|
100
329
|
}}
|
|
101
330
|
/>
|
|
102
331
|
);
|
|
@@ -107,10 +336,21 @@ export function Input(props: InputProps): JSX.Element {
|
|
|
107
336
|
// Created on first use and then REUSED, so toggling an affix on or off does
|
|
108
337
|
// not discard a focused input either. The lazy cache also keeps the unused
|
|
109
338
|
// branch's node (and its effects) from being built at all.
|
|
339
|
+
//
|
|
340
|
+
// KEEP PROSE IN THIS FILE FREE OF STRAY QUOTE CHARACTERS. The part-name guard in
|
|
341
|
+
// `src/elements/slots.test.ts` scans this source with a naive quote regex, so one
|
|
342
|
+
// apostrophe in a comment shifts its parity and swallows the part literals below —
|
|
343
|
+
// that guard then reddens over a comment, naming a part that is still right there.
|
|
344
|
+
//
|
|
345
|
+
// Each branch registers its node so the mask effect above can find whichever one is
|
|
346
|
+
// mounted; `ref` fires once per node, at construction, which is exactly when a node
|
|
347
|
+
// first becomes findable.
|
|
110
348
|
let plainNode: JSX.Element;
|
|
111
|
-
const plainInput = () =>
|
|
349
|
+
const plainInput = () =>
|
|
350
|
+
(plainNode ??= inputEl(fieldClass, 'field input', (el) => (plainEl = el)));
|
|
112
351
|
let rowNode: JSX.Element;
|
|
113
|
-
const rowInput = () =>
|
|
352
|
+
const rowInput = () =>
|
|
353
|
+
(rowNode ??= inputEl(() => ROW_INPUT, 'input', (el) => (rowEl = el)));
|
|
114
354
|
|
|
115
355
|
return (
|
|
116
356
|
<div class="flex w-full flex-col gap-1.5">
|
package/src/ui/kbd.tsx
CHANGED
package/src/ui/nav.tsx
CHANGED
|
@@ -221,7 +221,7 @@ function NavRows(props: NavRowsProps) {
|
|
|
221
221
|
</span>
|
|
222
222
|
</Show>
|
|
223
223
|
<Show when={item.badge}>
|
|
224
|
-
<Badge variant="default" class="px-1.5 py-0 text-
|
|
224
|
+
<Badge variant="default" class="px-1.5 py-0 text-micro font-medium uppercase tracking-wide">{item.badge}</Badge>
|
|
225
225
|
</Show>
|
|
226
226
|
<Show when={item.trailing}>{renderIcon(item.trailing, { class: 'size-4 shrink-0 opacity-0 transition-opacity group-hover:opacity-60' })}</Show>
|
|
227
227
|
</button>
|
package/src/ui/pane-group.tsx
CHANGED
|
@@ -201,7 +201,7 @@ export function PaneGroup(props: PaneGroupProps) {
|
|
|
201
201
|
</Show>
|
|
202
202
|
<span
|
|
203
203
|
class={cn(
|
|
204
|
-
'relative flex size-[18px] items-center justify-center rounded text-
|
|
204
|
+
'relative flex size-[18px] items-center justify-center rounded text-caption font-bold leading-none tabular-nums',
|
|
205
205
|
TONE_BG[tone()],
|
|
206
206
|
TONE_BADGE_FG[tone()],
|
|
207
207
|
)}
|
|
@@ -215,7 +215,7 @@ export function PaneGroup(props: PaneGroupProps) {
|
|
|
215
215
|
<Show when={tab().status?.label}>
|
|
216
216
|
<span
|
|
217
217
|
class={cn(
|
|
218
|
-
'truncate text-
|
|
218
|
+
'truncate text-micro font-medium',
|
|
219
219
|
TONE_TEXT[tone()],
|
|
220
220
|
alwaysWord() ? 'inline' : 'hidden group-hover/tab:inline',
|
|
221
221
|
)}
|
package/src/ui/radio.tsx
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type JSX, For, Show, splitProps, createUniqueId } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface RadioProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'type' | 'children'> {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A single radio button. A REAL `<input type="radio">` behind `appearance: none`,
|
|
8
|
+
* styled by the kit's `.kai-radio` rule, never a `<div role="radio">`.
|
|
9
|
+
*
|
|
10
|
+
* The native element is what makes a set of radios a GROUP: browsers give same-`name`
|
|
11
|
+
* radios one tab stop, arrow-key navigation between them, mutual exclusion and form
|
|
12
|
+
* participation, and screen readers announce "2 of 4". None of that is reimplementable
|
|
13
|
+
* for free, and every hand-rolled radio in this kit's control audit had lost some of it.
|
|
14
|
+
*
|
|
15
|
+
* Use {@link RadioGroup} for a set. Reach for a bare `Radio` when you are laying the
|
|
16
|
+
* group out yourself — give every member the same `name`.
|
|
17
|
+
*
|
|
18
|
+
* Everything not listed in `RadioProps` is forwarded to the input, so `id`, `name`,
|
|
19
|
+
* `value`, `required`, `disabled`, `aria-*` and any `data-*` hook behave exactly as
|
|
20
|
+
* they do on a plain `<input>`. No validation is applied: `required` reaches the
|
|
21
|
+
* native attribute and stops there.
|
|
22
|
+
*/
|
|
23
|
+
export function Radio(props: RadioProps): JSX.Element {
|
|
24
|
+
const [local, rest] = splitProps(props, ['class']);
|
|
25
|
+
return <input {...rest} type="radio" class={cn('kai-radio', local.class)} />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One choice in a {@link RadioGroup}. */
|
|
29
|
+
export interface RadioOption<T = string> {
|
|
30
|
+
/** The value this row selects. Compared to the group's `value` by identity. */
|
|
31
|
+
value: T;
|
|
32
|
+
/** The row's visible label. */
|
|
33
|
+
label: JSX.Element;
|
|
34
|
+
/** Optional second line under the label. */
|
|
35
|
+
description?: JSX.Element;
|
|
36
|
+
/** Disable this row alone (the group's `disabled` disables all of them). */
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RadioGroupProps<T = string>
|
|
41
|
+
extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onChange' | 'children'> {
|
|
42
|
+
/** The choices, in display order. */
|
|
43
|
+
options: readonly RadioOption<T>[];
|
|
44
|
+
/** The selected value. Matched against each option's `value` by identity. */
|
|
45
|
+
value?: T;
|
|
46
|
+
/**
|
|
47
|
+
* The shared form-control name every radio in the group carries — what makes the
|
|
48
|
+
* browser treat them as one control. Defaults to a generated id, so the group is
|
|
49
|
+
* exclusive and keyboard-navigable even when nothing is being submitted.
|
|
50
|
+
*/
|
|
51
|
+
name?: string;
|
|
52
|
+
/** Disable every row. */
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
/** Accessible name for the group. Rendered as `aria-label` on the `radiogroup`. */
|
|
55
|
+
label?: string;
|
|
56
|
+
/** Fires with the newly selected value (and its option) on selection. */
|
|
57
|
+
onChange?: (value: T, option: RadioOption<T>) => void;
|
|
58
|
+
/** Fires when a radio loses focus — the commit point for a form field. */
|
|
59
|
+
onOptionBlur?: () => void;
|
|
60
|
+
/** Extra classes for each row. */
|
|
61
|
+
itemClass?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Presentation slot. Replaces the default label/description column with whatever
|
|
64
|
+
* you return, so a row can carry media, a badge or a price without a second radio
|
|
65
|
+
* component existing. The control, the row chrome and the group semantics stay ours.
|
|
66
|
+
*/
|
|
67
|
+
children?: (option: RadioOption<T>, state: { checked: boolean; disabled: boolean }) => JSX.Element;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A vertical set of radio rows in a bordered, divided list — the kit's standard
|
|
72
|
+
* "pick exactly one" control.
|
|
73
|
+
*
|
|
74
|
+
* The group is a `role="radiogroup"` wrapper around real `<input type="radio">`s that
|
|
75
|
+
* share a `name`, so the keyboard behaviour (one tab stop, arrows to move) is the
|
|
76
|
+
* browser's and not a reimplementation. Clicking anywhere on a row selects it, because
|
|
77
|
+
* each row is a `<label>`.
|
|
78
|
+
*
|
|
79
|
+
* Everything not listed in `RadioGroupProps` is forwarded to the group element, so
|
|
80
|
+
* `id`, `aria-*` and any `data-*` hook land where a form expects them.
|
|
81
|
+
*
|
|
82
|
+
* ```tsx
|
|
83
|
+
* <RadioGroup
|
|
84
|
+
* label="Severity"
|
|
85
|
+
* options={[{ value: 'blocking', label: 'Blocking' }, { value: 'cosmetic', label: 'Cosmetic' }]}
|
|
86
|
+
* value={severity()}
|
|
87
|
+
* onChange={setSeverity}
|
|
88
|
+
* />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function RadioGroup<T = string>(props: RadioGroupProps<T>): JSX.Element {
|
|
92
|
+
const [local, rest] = splitProps(props, [
|
|
93
|
+
'options', 'value', 'name', 'disabled', 'label', 'onChange', 'onOptionBlur', 'itemClass', 'children', 'class',
|
|
94
|
+
]);
|
|
95
|
+
const fallbackName = createUniqueId();
|
|
96
|
+
const groupName = () => local.name ?? fallbackName;
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
{...rest}
|
|
100
|
+
role="radiogroup"
|
|
101
|
+
aria-label={local.label}
|
|
102
|
+
class={cn('divide-y divide-border overflow-hidden rounded-lg border border-border', local.class)}
|
|
103
|
+
>
|
|
104
|
+
<For each={local.options}>
|
|
105
|
+
{(opt) => {
|
|
106
|
+
const checked = () => local.value === opt.value;
|
|
107
|
+
const rowDisabled = () => !!(local.disabled || opt.disabled);
|
|
108
|
+
return (
|
|
109
|
+
<label
|
|
110
|
+
class={cn(
|
|
111
|
+
'flex cursor-pointer items-center gap-3 px-3 py-2.5 text-sm transition-colors',
|
|
112
|
+
opt.description !== undefined && 'items-start',
|
|
113
|
+
checked()
|
|
114
|
+
? 'bg-accent font-medium text-accent-foreground'
|
|
115
|
+
: 'text-foreground hover:bg-muted/50',
|
|
116
|
+
local.itemClass,
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
<Radio
|
|
120
|
+
name={groupName()}
|
|
121
|
+
value={String(opt.value)}
|
|
122
|
+
checked={checked()}
|
|
123
|
+
disabled={rowDisabled()}
|
|
124
|
+
class={opt.description === undefined ? undefined : 'mt-0.5'}
|
|
125
|
+
onChange={() => local.onChange?.(opt.value, opt)}
|
|
126
|
+
onBlur={() => local.onOptionBlur?.()}
|
|
127
|
+
/>
|
|
128
|
+
<Show
|
|
129
|
+
when={local.children}
|
|
130
|
+
fallback={
|
|
131
|
+
// No description → a bare <span>, byte for byte what every call site
|
|
132
|
+
// rendered before this component existed. The two-line column only
|
|
133
|
+
// appears when there is a second line to put in it.
|
|
134
|
+
<Show when={opt.description !== undefined} fallback={<span>{opt.label}</span>}>
|
|
135
|
+
<span class="flex flex-col gap-0.5">
|
|
136
|
+
<span>{opt.label}</span>
|
|
137
|
+
<span class="text-xs font-normal text-muted-foreground">{opt.description}</span>
|
|
138
|
+
</span>
|
|
139
|
+
</Show>
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
{(render) => render()(opt, { checked: checked(), disabled: rowDisabled() })}
|
|
143
|
+
</Show>
|
|
144
|
+
</label>
|
|
145
|
+
);
|
|
146
|
+
}}
|
|
147
|
+
</For>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|