@kitn.ai/ui 0.25.2 → 0.27.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 +17 -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-b5A8hlHs.js → Icon-BnFKqqtY.js} +1 -1
- package/dist/action-icons-CBskfBMz.js +1 -0
- package/dist/arrow-down-Br0qtpsz.js +1 -0
- package/dist/arrow-left-Dj7xgE12.js +1 -0
- package/dist/artifact-CvXEm1lS.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-C43ZegEq.js +1 -0
- package/dist/audio-visualizer-B-79y1Tj.js +1 -0
- package/dist/badge-BpUbq8c0.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-sn17Ip9v.js +1 -0
- package/dist/{elements/chunks/button-B9okqG73.js → button-BH6rN0U-.js} +1 -1
- package/dist/card-renderer-Cb1gQmth.js +1 -0
- package/dist/card-routing-DifQgS7n.js +1 -0
- package/dist/check-DPeeMUYx.js +1 -0
- package/dist/checkbox-Dc9Yov_B.js +1 -0
- package/dist/checkbox-group-B1kDLLYk.js +1 -0
- package/dist/chevron-down-CHSWk7ZT.js +1 -0
- package/dist/chevron-right-B7l_fMgx.js +1 -0
- package/dist/choice-card-BT3c1S8g.js +1 -0
- package/dist/circle-Bb0yOpqM.js +1 -0
- package/dist/circle-check-BGZpCiz7.js +1 -0
- package/dist/circle-x-wobWkoSx.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/code-block-CUQ5mtv0.js +1 -0
- package/dist/collapsible-CPJbf6FM.js +1 -0
- package/dist/components/attachment-types.d.ts +53 -1
- package/dist/components/attachments.d.ts +31 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
- package/dist/components/audio-visualizer/index.d.ts +15 -1
- package/dist/components/audio-visualizer/variant-bar.d.ts +24 -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 +49 -42
- package/dist/components/code-block.d.ts +22 -0
- package/dist/components/conversation-item.d.ts +66 -1
- package/dist/components/conversation-list.d.ts +77 -0
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +89 -0
- package/dist/components/message.d.ts +29 -0
- 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/toast.d.ts +2 -1
- package/dist/components/tool-types.d.ts +9 -0
- package/dist/components/voice-input.d.ts +8 -0
- package/dist/components/voice-output.d.ts +12 -1
- package/dist/components/workspace-shell.d.ts +87 -0
- package/dist/composer-CjngLzal.js +106 -0
- package/dist/confirm-card-DCx_A4bU.js +1 -0
- package/dist/construct-cli.es.js +1169 -0
- package/dist/context-BY8V-Nlp.js +1 -0
- package/dist/controllable-D43BKmtX.js +1 -0
- package/dist/conversation-list-D4lLU_yS.js +1 -0
- package/dist/copy-B6DO4PqB.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-CUzOlaMS.js → create-tween-BPzaefTp.js} +1 -1
- package/dist/{create-tween-B5Y4Im8G.js → create-tween-CnMHviKB.js} +1 -1
- package/dist/{create-tween-4z1XYLkR.js → create-tween-D1aiGZZh.js} +1 -1
- package/dist/{elements/chunks/create-tween-CUrM6D_o.js → create-tween-DUSQUKpr.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +3468 -1927
- package/dist/default-input-CucQEOqH.js +1 -0
- package/dist/define-CGGX-7Vg.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/diagnostics/hook.d.ts +81 -0
- package/dist/diagnostics/index.d.ts +7 -0
- package/dist/diagnostics/report-request.d.ts +61 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +209 -0
- package/dist/disclosure-CbLtajUQ.js +1 -0
- package/dist/dropdown-BwhViJFd.js +1 -0
- 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.d.ts +1 -0
- package/dist/elements/conversation-item.js +1 -0
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +3 -3
- package/dist/elements/define-entry.d.ts +12 -0
- package/dist/elements/define.d.ts +27 -0
- package/dist/elements/diagnostic-events.d.ts +74 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.d.ts +1 -0
- package/dist/elements/dock.js +243 -0
- package/dist/elements/dropdown.d.ts +1 -0
- package/dist/elements/dropdown.js +1 -0
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +37 -0
- package/dist/elements/element-diagnostics.d.ts +81 -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/register.d.ts +2 -0
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +3 -3
- 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 +36 -4
- 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 +589 -167
- package/dist/{elements/chunks/ellipsis-CVxqfsDZ.js → ellipsis-DFr4Vqr3.js} +1 -1
- package/dist/embed-5s_vU4J0.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-DG2sRdQL.js → external-link-u9d85ORQ.js} +1 -1
- package/dist/file-text-CX7_x42o.js +1 -0
- package/dist/{elements/chunks/file-tree-yGWNK0Js.js → file-tree-D3kBjXI5.js} +1 -1
- package/dist/folder-V-YdIL8f.js +1 -0
- package/dist/form-BRNUMCN2.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-DaeiYrTA.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-BpixdZNq.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8473 -6470
- package/dist/index.server.js +6495 -4958
- package/dist/info-8mFsIjS3.js +1 -0
- package/dist/input-d1Yuu-hz.js +1 -0
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kai-provider.es.js +40 -40
- package/dist/kai.es.js +1 -1
- package/dist/kbd-961zMPFB.js +1 -0
- package/dist/link-DlZZBGaR.js +1 -0
- package/dist/{elements/chunks/link-preview-Cf5KOP-i.js → link-preview-DIIbV2Sd.js} +1 -1
- package/dist/loader-YQszHFaW.js +1 -0
- package/dist/{elements/chunks/markdown-DbozyYqa.js → markdown-CVk1MPsj.js} +1 -1
- package/dist/mcp.es.js +3873 -544
- package/dist/message-CwKVEdZg.js +1 -0
- package/dist/message-DS8lzHc1.js +1 -0
- package/dist/message-circle-Xf8Q-vZp.js +1 -0
- package/dist/message-square-Cxnwgkns.js +1 -0
- package/dist/{elements/chunks/minimize-2-SegJ_oku.js → minimize-2-UEZmP2QT.js} +1 -1
- package/dist/{elements/chunks/model-switcher-D7JrFFHp.js → model-switcher-Kk1jEQ5t.js} +1 -1
- package/dist/{elements/chunks/overlay-CV7z2YuX.js → overlay-C1od6LOz.js} +1 -1
- package/dist/panel-right-ryET0yyn.js +1 -0
- package/dist/paperclip-BDNoZFJP.js +1 -0
- package/dist/play-OFIvYXUQ.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/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 +23 -0
- package/dist/primitives/use-speech-recognition.d.ts +8 -0
- package/dist/primitives/use-text-stream.d.ts +5 -0
- package/dist/progress-bar-BvYX0aoy.js +1 -0
- package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
- package/dist/radio-DcL23cNJ.js +1 -0
- package/dist/react/index.d.ts +306 -283
- package/dist/react.js +309 -247
- package/dist/reasoning-Cackq4Rh.js +1 -0
- package/dist/register-impl-C7rENvPK.js +424 -0
- package/dist/{elements/chunks/resizable-Dpzjlvp4.js → resizable-Cp9HUYih.js} +1 -1
- package/dist/rotate-ccw-0DLRDHH5.js +1 -0
- package/dist/rotate-cw-DOB9IGcU.js +1 -0
- package/dist/schemas/form.schema.json +21 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/tool-defs.d.ts +45 -0
- package/dist/schemas.js +398 -305
- package/dist/scroll-area-emVmWP4K.js +1 -0
- package/dist/scroll-button-CeP_gv1x.js +1 -0
- package/dist/select-CWmu00tS.js +1 -0
- package/dist/{elements/chunks/separator-DH2r_ZSZ.js → separator-CE6vly6z.js} +1 -1
- package/dist/{elements/chunks/settings-BFMOd6yB.js → settings-CE-78SvZ.js} +1 -1
- package/dist/{elements/chunks/settings-group-B3VZKIcz.js → settings-group-ZFWNxFQR.js} +1 -1
- package/dist/{elements/chunks/skeleton-N8BuWYmf.js → skeleton-BebdP4mf.js} +1 -1
- package/dist/slider-BAKRYUlz.js +1 -0
- package/dist/slots-DaNjX0rc.js +1 -0
- package/dist/{solid-BMuDmFo_.js → solid-BE5ui6sy.js} +10672 -8116
- package/dist/{solid-CGYNdLRR.js → solid-DFhsce3O.js} +8137 -6056
- package/dist/solid.d.ts +15 -0
- package/dist/solid.js +248 -235
- package/dist/solid.server.js +248 -235
- package/dist/source-B0FlguSx.js +1 -0
- package/dist/star-CBMZvLuN.js +1 -0
- package/dist/state/index.d.ts +5 -1
- package/dist/state/mock.d.ts +32 -2
- package/dist/state/persistence.d.ts +51 -0
- package/dist/state/stream.d.ts +50 -0
- package/dist/state/threads.d.ts +71 -0
- package/dist/state.js +297 -172
- package/dist/store-7eoadydQ.js +1 -0
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-CViARZCy.js +1 -0
- package/dist/tasks-card-MjCcOhR_.js +1 -0
- package/dist/text-shimmer-DKlwRF2G.js +1 -0
- package/dist/textarea-3ay2Me2-.js +1 -0
- package/dist/theme.tokens.css +82 -13
- package/dist/{elements/chunks/thumbs-up-DQe0hvkZ.js → thumbs-up-DQcu2HMO.js} +1 -1
- package/dist/toast-store-C6yKOhbk.js +1 -0
- package/dist/{elements/chunks/tool-C1YtxwPu.js → tool-BCYNlfKy.js} +1 -1
- package/dist/{elements/chunks/tooltip-BqwnFWhn.js → tooltip-05zI4hjs.js} +1 -1
- package/dist/trash-2-BR1RxGky.js +1 -0
- package/dist/{elements/chunks/triangle-alert-B6or7F5u.js → triangle-alert-DHohDOCy.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- package/dist/types.d.ts +4 -2
- 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 +131 -0
- package/dist/ui/hover-card.d.ts +44 -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-BbGW6A_u.js +1 -0
- package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
- package/dist/use-card-resolution-CJW4grFa.js +1 -0
- package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-BUOVgPOB.js} +27 -27
- package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CUsUJvFI.js} +28 -28
- package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CW2i4bfr.js} +2 -2
- package/dist/{elements/chunks/variant-aurora-QXTZPyST.js → variant-aurora-xiMPty8f.js} +2 -2
- package/dist/variant-custom-BQtOnmeO.js +1 -0
- package/dist/{variant-custom-BhS9wIpU.js → variant-custom-CAm9L-E5.js} +65 -65
- package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-CAnzzppW.js} +40 -40
- package/dist/variant-custom-D91HjAKz.js +1 -0
- package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-Ca1Ls4_y.js} +21 -21
- package/dist/{variant-wave-CbVsmgwT.js → variant-wave-CxX39DKd.js} +25 -25
- package/dist/{variant-wave-BwzrHD1s.js → variant-wave-DYyzIIZv.js} +2 -2
- package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-uY5crU_K.js} +2 -2
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/wire/chunk.d.ts +47 -0
- package/dist/wire/diagnostics.d.ts +575 -0
- package/dist/wire/encode-probe.d.ts +70 -0
- package/dist/wire/encode.d.ts +24 -0
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/sse.d.ts +10 -2
- package/dist/wire.js +917 -480
- package/dist/x-C5m1JFgi.js +1 -0
- package/frameworks/react/index.tsx +325 -108
- package/llms-full.txt +1354 -82
- package/llms.txt +6 -3
- package/package.json +39 -6
- package/src/agent-tooling/archetypes.ts +41 -9
- package/src/agent-tooling/catalog/README.md +676 -0
- package/src/agent-tooling/catalog/catalog-types.ts +231 -0
- package/src/agent-tooling/catalog/fabrications.ts +96 -0
- package/src/agent-tooling/catalog/invariants.ts +284 -0
- package/src/agent-tooling/catalog/labs-titles.ts +114 -0
- package/src/agent-tooling/catalog/scenarios.ts +87 -0
- package/src/agent-tooling/catalog/surfaces.ts +347 -0
- package/src/agent-tooling/construct/cli-entry.ts +8 -0
- package/src/agent-tooling/construct/cli.ts +131 -0
- package/src/agent-tooling/construct/codegen.ts +1353 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +250 -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 +23 -0
- package/src/agent-tooling/construct/schema.ts +371 -0
- package/src/agent-tooling/integrations/anthropic.ts +7 -1
- package/src/agent-tooling/integrations/cloudflare.ts +43 -2
- package/src/agent-tooling/integrations/langgraph.ts +7 -1
- package/src/agent-tooling/integrations/mastra.ts +7 -1
- package/src/agent-tooling/integrations/mock.ts +92 -17
- package/src/agent-tooling/integrations/ollama.ts +7 -1
- package/src/agent-tooling/integrations/openai.ts +7 -1
- package/src/agent-tooling/integrations/openrouter.ts +7 -1
- package/src/agent-tooling/integrations/pi.ts +56 -1
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
- package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
- package/src/agent-tooling/mcp/manifest.ts +100 -1
- package/src/agent-tooling/mcp/server.ts +63 -6
- 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 +542 -5
- package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
- package/src/agent-tooling/mcp/tools/theme.ts +146 -30
- package/src/agent-tooling/mcp/validate-args.ts +141 -0
- 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 +24 -7
- package/src/agent-tooling/route-emit.ts +26 -2
- package/src/components/attachment-types.ts +54 -1
- package/src/components/attachments.tsx +169 -22
- package/src/components/audio-visualizer/fit-scale.ts +112 -0
- package/src/components/audio-visualizer/index.tsx +80 -3
- package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
- package/src/components/audio-visualizer/variant-bar.tsx +38 -6
- package/src/components/audio-visualizer/variant-custom.tsx +8 -2
- package/src/components/audio-visualizer/variant-grid.tsx +16 -11
- package/src/components/audio-visualizer/variant-radial.tsx +15 -10
- package/src/components/audio-visualizer/variant-wave.tsx +7 -2
- package/src/components/card-renderer.tsx +39 -8
- package/src/components/card.tsx +10 -2
- package/src/components/chat-container.tsx +4 -1
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +105 -13
- package/src/components/choice-card.tsx +102 -37
- package/src/components/coachmark.tsx +1 -1
- package/src/components/code-block.tsx +81 -5
- package/src/components/composer.tsx +89 -8
- package/src/components/confirm-card.tsx +20 -4
- package/src/components/conversation-item.tsx +138 -4
- package/src/components/conversation-list.tsx +230 -15
- package/src/components/embed.tsx +4 -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 +336 -21
- package/src/components/loader.tsx +12 -5
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +142 -12
- 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/response-stream.tsx +13 -7
- package/src/components/screen.tsx +5 -0
- package/src/components/scroll-button.tsx +63 -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 +11 -5
- package/src/components/tool-types.ts +9 -0
- package/src/components/voice-input.tsx +35 -3
- package/src/components/voice-output.tsx +46 -6
- package/src/components/workspace-shell.tsx +285 -0
- package/src/diagnostics/hook.ts +352 -0
- package/src/diagnostics/index.ts +82 -0
- package/src/diagnostics/report-request.ts +296 -0
- package/src/elements/attachments.tsx +9 -8
- package/src/elements/audio-visualizer.tsx +48 -4
- package/src/elements/chat-workspace.tsx +159 -287
- package/src/elements/chat.tsx +31 -10
- package/src/elements/checkbox-group.tsx +164 -0
- package/src/elements/checkbox.tsx +134 -0
- package/src/elements/code-block.tsx +19 -1
- package/src/elements/command.tsx +4 -1
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +12 -1
- package/src/elements/conversation-item.tsx +120 -0
- package/src/elements/conversation-list.tsx +60 -11
- package/src/elements/default-input.tsx +5 -5
- package/src/elements/define-entry.ts +12 -0
- package/src/elements/define.tsx +168 -8
- package/src/elements/diagnostic-events.ts +114 -0
- package/src/elements/dialog.tsx +47 -0
- package/src/elements/disclosure.ts +34 -12
- package/src/elements/dock.tsx +215 -0
- package/src/elements/dropdown.tsx +123 -0
- package/src/elements/element-data-types.ts +40 -0
- package/src/elements/element-diagnostics.ts +392 -0
- package/src/elements/element-manifest.json +36 -0
- package/src/elements/element-meta.json +1483 -560
- package/src/elements/element-nonscalar.json +185 -0
- package/src/elements/element-types.d.ts +589 -167
- 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/menu.tsx +13 -0
- package/src/elements/message.tsx +2 -0
- package/src/elements/pane-grid.tsx +111 -0
- package/src/elements/prompt-dock.tsx +2 -1
- package/src/elements/prompt-input.tsx +16 -10
- package/src/elements/radio-group.tsx +129 -0
- package/src/elements/register-impl.ts +52 -0
- package/src/elements/register.ts +28 -0
- package/src/elements/resizable.tsx +52 -25
- package/src/elements/scroll-button.tsx +59 -6
- package/src/elements/select.tsx +165 -0
- package/src/elements/slider.tsx +171 -0
- package/src/elements/slots.ts +108 -13
- package/src/elements/styles.css +119 -9
- package/src/elements/thinking-bar.tsx +16 -7
- package/src/elements/thread.tsx +2 -0
- package/src/elements/toast.tsx +17 -1
- package/src/elements/tool.tsx +26 -5
- package/src/elements/voice-input.tsx +11 -2
- package/src/elements/voice-output.tsx +16 -3
- package/src/index.ts +4 -1
- 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/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 +33 -0
- package/src/primitives/use-speech-recognition.ts +12 -1
- package/src/primitives/use-stick-to-bottom.ts +34 -2
- package/src/primitives/use-text-stream.ts +33 -2
- package/src/remote/provider-runtime.ts +6 -3
- package/src/schemas/index.ts +1 -0
- package/src/schemas/tool-defs.ts +307 -6
- package/src/solid.ts +15 -0
- package/src/state/index.ts +13 -1
- package/src/state/mock.ts +92 -6
- package/src/state/persistence.ts +180 -0
- package/src/state/stream.ts +71 -4
- package/src/state/threads.ts +142 -0
- package/src/types.ts +4 -2
- 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/dialog.tsx +6 -0
- package/src/ui/dock.tsx +584 -0
- package/src/ui/dropdown.tsx +67 -23
- package/src/ui/hover-card.tsx +156 -6
- package/src/ui/icon.tsx +53 -0
- package/src/ui/input.tsx +275 -16
- 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/resizable.tsx +11 -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/src/wire/chunk.ts +47 -0
- package/src/wire/consume.ts +185 -26
- package/src/wire/diagnostics.ts +727 -0
- package/src/wire/encode-probe.ts +214 -0
- package/src/wire/encode.ts +267 -14
- package/src/wire/formats/anthropic.ts +8 -1
- package/src/wire/formats/openai.ts +5 -0
- package/src/wire/index.ts +22 -0
- package/src/wire/read.ts +250 -6
- package/src/wire/sse.ts +16 -3
- package/theme.css +84 -13
- package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
- package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
- package/dist/elements/chunks/artifact-BSuPYFHY.js +0 -1
- package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
- package/dist/elements/chunks/badge-TIMiJFKc.js +0 -1
- package/dist/elements/chunks/bash-InADTalH.js +0 -1
- package/dist/elements/chunks/card-renderer-CSoTL_e1.js +0 -1
- package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
- package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
- package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
- package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
- package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
- package/dist/elements/chunks/choice-card-CGMc3kek.js +0 -1
- package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
- package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
- package/dist/elements/chunks/circle-x-mhH7pOK2.js +0 -1
- package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
- package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
- package/dist/elements/chunks/composer-CZZmtYOT.js +0 -69
- package/dist/elements/chunks/confirm-card-BWjf2-xo.js +0 -1
- package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
- package/dist/elements/chunks/conversation-list-CsGJjSKG.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-B1Mem5wu.js +0 -1
- package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
- package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
- package/dist/elements/chunks/download-DnCyWFJE.js +0 -1
- package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
- package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
- package/dist/elements/chunks/file-text-D0J9F6M7.js +0 -1
- package/dist/elements/chunks/folder-NV1pi_ud.js +0 -1
- package/dist/elements/chunks/form-Cp8Y00Wn.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-6KtXpLwT.js +0 -1
- package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
- package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
- package/dist/elements/chunks/info-C8r5Q6cc.js +0 -1
- package/dist/elements/chunks/input-DrseEjhp.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-OGJDUT-O.js +0 -1
- package/dist/elements/chunks/link-BdsfLB7b.js +0 -1
- package/dist/elements/chunks/loader-B5QzK_8_.js +0 -1
- package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
- package/dist/elements/chunks/message-DtXKPqiR.js +0 -1
- package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
- package/dist/elements/chunks/paperclip-Bg0ITU3b.js +0 -1
- package/dist/elements/chunks/progress-bar-D1_rzt6N.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-D5Sm8xF1.js +0 -1
- package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
- package/dist/elements/chunks/rotate-cw-DzVDJOA1.js +0 -1
- package/dist/elements/chunks/scroll-area-93A1bbeG.js +0 -1
- package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
- package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
- package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
- package/dist/elements/chunks/source-w9V0NnBn.js +0 -1
- package/dist/elements/chunks/star-DHvACxtm.js +0 -1
- package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
- package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
- package/dist/elements/chunks/tasks-card-lPxoodlA.js +0 -1
- package/dist/elements/chunks/text-shimmer-BY_rI3s5.js +0 -1
- package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
- package/dist/elements/chunks/toast-store-VUZZBhzH.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-DJg32jGH.js +0 -1
- package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
- package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
- package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
- package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
- package/dist/llms/llms-full.txt +0 -2834
- package/dist/llms/llms.txt +0 -156
- package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
- package/dist/register-impl-DGmGzrEM.js +0 -145
- package/dist/ui/stat.d.ts +0 -16
- package/dist/variant-custom-MkTNTycd.js +0 -1
- package/src/primitives/card-validate-generator.testlib.ts +0 -38
- 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
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from 'solid-js';
|
|
15
15
|
import { cn } from '../utils/cn';
|
|
16
16
|
import { Button } from '../ui/button';
|
|
17
|
+
import { Checkbox } from '../ui/checkbox';
|
|
17
18
|
import { ProgressBar } from '../ui/progress-bar';
|
|
18
19
|
import { Card } from './card';
|
|
19
20
|
import { DismissedStub } from './dismissed-stub';
|
|
@@ -21,7 +22,7 @@ import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primi
|
|
|
21
22
|
import { useCardResolution } from './use-card-resolution';
|
|
22
23
|
import { emitCardEvent } from '../primitives/card-routing';
|
|
23
24
|
import { useCardHost } from '../primitives/card-host';
|
|
24
|
-
import { Check, Circle, CircleCheck
|
|
25
|
+
import { Check, Circle, CircleCheck } from 'lucide-solid';
|
|
25
26
|
|
|
26
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
28
|
// Types (tasks.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
|
|
@@ -415,14 +416,23 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
|
|
|
415
416
|
progress() || res.isResolved() || readOnly() ? undefined : (
|
|
416
417
|
<div class="flex w-full flex-wrap items-center justify-between gap-2">
|
|
417
418
|
<Show when={local.data?.dismissible === true}>
|
|
419
|
+
{/* The contract `dismiss` verb — a footer ACTION (it emits
|
|
420
|
+
`{kind:'dismiss'}` and collapses the card to a re-openable
|
|
421
|
+
stub), not the chrome close in `Card`'s own `dismissible`
|
|
422
|
+
prop, which the contract cards deliberately never set.
|
|
423
|
+
Labelled rather than a bare ghost ✕: every other control in
|
|
424
|
+
this row says what it does, and an unlabelled 28px glyph
|
|
425
|
+
300px from the primary button reads as stray chrome. Same
|
|
426
|
+
shape in all four cards — `form` already looked like this.
|
|
427
|
+
`aria-label` is kept (redundant with the text) so the
|
|
428
|
+
element tests that select on it keep working. */}
|
|
418
429
|
<Button
|
|
419
430
|
type="button"
|
|
420
431
|
variant="ghost"
|
|
421
|
-
size="icon-sm"
|
|
422
432
|
aria-label="Dismiss"
|
|
423
433
|
onClick={onDismiss}
|
|
424
434
|
>
|
|
425
|
-
|
|
435
|
+
Dismiss
|
|
426
436
|
</Button>
|
|
427
437
|
</Show>
|
|
428
438
|
<span id={countId} aria-live="polite" class="text-xs text-muted-foreground">
|
|
@@ -485,7 +495,7 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
|
|
|
485
495
|
if (confirmEnabled()) onConfirm();
|
|
486
496
|
}}
|
|
487
497
|
>
|
|
488
|
-
<div class="divide-y divide-border overflow-hidden rounded-lg border border-
|
|
498
|
+
<div class="divide-y divide-border overflow-hidden rounded-lg border border-border">
|
|
489
499
|
<Show when={showMaster()}>
|
|
490
500
|
{(() => {
|
|
491
501
|
const indeterminate = () => masterState() === 'indeterminate';
|
|
@@ -501,17 +511,20 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
|
|
|
501
511
|
: 'text-foreground hover:bg-muted/50',
|
|
502
512
|
)}
|
|
503
513
|
>
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
514
|
+
{/* SQUARE `Checkbox` (the `.kai-checkbox` look), and the checklist below draws a
|
|
515
|
+
ROUND lucide Circle/CircleCheck instead. Deliberate, not drift: this
|
|
516
|
+
is `select` mode — a pick-list whose rows are a pending selection that
|
|
517
|
+
only commits when the confirm button fires, which is what a checkbox
|
|
518
|
+
means everywhere else. `progress` mode has no confirm and toggling a
|
|
519
|
+
row IS the action, so it uses the done/not-done affordance of a todo
|
|
520
|
+
list. See the D-2 row in
|
|
521
|
+
docs/superpowers/plans/2026-08-25-form-control-primitives.md; owner has
|
|
522
|
+
not ruled, so do not converge these on your own. */}
|
|
523
|
+
<Checkbox
|
|
507
524
|
checked={masterState() === 'checked'}
|
|
508
525
|
disabled={groupDisabled() || readOnly()}
|
|
526
|
+
indeterminate={indeterminate()}
|
|
509
527
|
aria-checked={indeterminate() ? 'mixed' : masterState() === 'checked'}
|
|
510
|
-
ref={(el) => {
|
|
511
|
-
createEffect(() => {
|
|
512
|
-
el.indeterminate = indeterminate();
|
|
513
|
-
});
|
|
514
|
-
}}
|
|
515
528
|
onChange={(e) => toggleAll(e.currentTarget.checked)}
|
|
516
529
|
/>
|
|
517
530
|
<span>Select all</span>
|
|
@@ -541,9 +554,8 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
|
|
|
541
554
|
)}
|
|
542
555
|
data-task-id={task.id}
|
|
543
556
|
>
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
class="kai-checkbox mt-0.5"
|
|
557
|
+
<Checkbox
|
|
558
|
+
class="mt-0.5"
|
|
547
559
|
checked={checked()}
|
|
548
560
|
disabled={blocked() || readOnly()}
|
|
549
561
|
aria-disabled={blocked() ? 'true' : undefined}
|
|
@@ -603,7 +615,10 @@ function ProgressChecklist(props: {
|
|
|
603
615
|
<div class={cn('flex flex-col gap-3', props.groupClass)}>
|
|
604
616
|
<Show when={props.heading}>
|
|
605
617
|
<div class="flex items-center justify-between gap-3">
|
|
606
|
-
<span
|
|
618
|
+
<span
|
|
619
|
+
id={props.headingId}
|
|
620
|
+
class="text-title font-semibold tracking-tight text-foreground"
|
|
621
|
+
>
|
|
607
622
|
{props.heading}
|
|
608
623
|
</span>
|
|
609
624
|
<span
|
|
@@ -646,8 +661,23 @@ function ProgressChecklist(props: {
|
|
|
646
661
|
// row doesn't leave a noisy persistent outline — and it makes a
|
|
647
662
|
// Tab-focused completed row's open description perceivable. Dropped
|
|
648
663
|
// entirely when readonly (the row isn't focusable then).
|
|
664
|
+
//
|
|
665
|
+
// TWO tab stops exist per row and the ring has to cover both.
|
|
666
|
+
// `focus-visible:` matches only when the LABEL itself is focused,
|
|
667
|
+
// which is completed rows only (the `tabindex` below). Every row's
|
|
668
|
+
// other — and for an incomplete row, only — tab stop is the sr-only
|
|
669
|
+
// checkbox inside, so without `has-[:focus-visible]:` tabbing to an
|
|
670
|
+
// unchecked row put focus on an off-screen input with no indicator
|
|
671
|
+
// anywhere on screen. Both halves stay on the focus-VISIBLE axis on
|
|
672
|
+
// purpose: `focus-within:` would also fire for a mouse click (which
|
|
673
|
+
// forwards focus from the label to the checkbox), which is exactly
|
|
674
|
+
// the persistent-outline noise the paragraph above rejects. The
|
|
675
|
+
// description reveal below uses `group-focus-within:` because a
|
|
676
|
+
// hover-or-focus reveal wants the looser condition.
|
|
649
677
|
!props.readonly &&
|
|
650
678
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
|
|
679
|
+
!props.readonly &&
|
|
680
|
+
'has-[:focus-visible]:outline-none has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-ring',
|
|
651
681
|
props.readonly
|
|
652
682
|
? 'cursor-default'
|
|
653
683
|
: blocked()
|
|
@@ -680,6 +710,18 @@ function ProgressChecklist(props: {
|
|
|
680
710
|
)}
|
|
681
711
|
aria-hidden="true"
|
|
682
712
|
>
|
|
713
|
+
{/* ROUND, where `select` mode uses the square `Checkbox`. Deliberate:
|
|
714
|
+
this mode has no confirm button, so ticking a row IS the action and the
|
|
715
|
+
row is done-or-not rather than selected-or-not — the todo-list
|
|
716
|
+
affordance, which is how this mode has been described since it landed
|
|
717
|
+
(see the `mode` argType in tasks-card.stories.tsx and the JSDoc on
|
|
718
|
+
src/elements/tasks.tsx). D-2 in
|
|
719
|
+
docs/superpowers/plans/2026-08-25-form-control-primitives.md tracks
|
|
720
|
+
whether to keep the split; unruled, so leave both. NOTE the cost: the
|
|
721
|
+
icon replaces the visible native control, which is why the real input
|
|
722
|
+
above is `sr-only` and why the row's focus ring has to be driven by
|
|
723
|
+
`has-[:focus-visible]:`. Converging on `Checkbox` would take that
|
|
724
|
+
hazard away with it. */}
|
|
683
725
|
<Show
|
|
684
726
|
when={checked()}
|
|
685
727
|
fallback={<Circle size={18} />}
|
|
@@ -34,6 +34,11 @@ export interface ThreadProps {
|
|
|
34
34
|
* the kit checks its own seven built-ins and leaves your own card type
|
|
35
35
|
* unvalidated. A schema here WINS over a built-in of the same name. */
|
|
36
36
|
cardSchemas?: CardSchemaMap;
|
|
37
|
+
/** The custom-element host node to emit card events off when no `CardProvider`
|
|
38
|
+
* is present, forwarded through `MessageBody` to `CardRenderer`. The
|
|
39
|
+
* element facades pass their own host element so card events leave as the
|
|
40
|
+
* bubbling `kai-card` CustomEvent. */
|
|
41
|
+
cardHostElement?: HTMLElement;
|
|
37
42
|
/** Show a typing indicator on the pending assistant turn — use while awaiting
|
|
38
43
|
* the assistant's reply. */
|
|
39
44
|
loading?: boolean;
|
|
@@ -146,6 +151,7 @@ export function Thread(props: ThreadProps) {
|
|
|
146
151
|
parts={m().parts}
|
|
147
152
|
cardTypes={props.cardTypes}
|
|
148
153
|
cardSchemas={props.cardSchemas}
|
|
154
|
+
cardHostElement={props.cardHostElement}
|
|
149
155
|
isUser={m().role === 'user'}
|
|
150
156
|
markdown={m().role === 'assistant'}
|
|
151
157
|
actions={m().actions}
|
|
@@ -209,7 +215,10 @@ export function Thread(props: ThreadProps) {
|
|
|
209
215
|
</ChatContainerContent>
|
|
210
216
|
<Show when={showScrollButton()}>
|
|
211
217
|
<div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
|
|
212
|
-
|
|
218
|
+
{/* The button now owns its elevation (kai-elevation); a `shadow-sm`
|
|
219
|
+
here would set box-shadow a second time and the winner would
|
|
220
|
+
be stylesheet order, not this call site. */}
|
|
221
|
+
<ScrollButton />
|
|
213
222
|
</div>
|
|
214
223
|
</Show>
|
|
215
224
|
</ChatContainer>
|
package/src/components/toast.tsx
CHANGED
|
@@ -320,11 +320,17 @@ function anchorStyle(position: ToastPosition, r: TargetRect): Record<string, str
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
/** The overlay layer both region roots sit on. One token, one default:
|
|
324
|
+
* your app chrome must stay below the toast layer; set `--kai-toast-z`
|
|
325
|
+
* (default 100) on the host to move the layer. */
|
|
326
|
+
const TOAST_Z = 'var(--kai-toast-z, 100)';
|
|
327
|
+
|
|
323
328
|
/**
|
|
324
329
|
* Renders a stack of toasts at a viewport edge. Caps the visible count at `max`
|
|
325
330
|
* (newest on top); overflow waits in a queue and is promoted as visible toasts
|
|
326
331
|
* leave. `aria-live=polite` / `role=region` so screen readers announce new
|
|
327
|
-
* toasts without stealing focus.
|
|
332
|
+
* toasts without stealing focus. Your app chrome must stay below the toast
|
|
333
|
+
* layer; set `--kai-toast-z` (default 100) on the host to move the layer.
|
|
328
334
|
*/
|
|
329
335
|
export function ToastRegion(props: ToastRegionProps) {
|
|
330
336
|
const max = () => props.max ?? 3;
|
|
@@ -425,12 +431,12 @@ export function ToastRegion(props: ToastRegionProps) {
|
|
|
425
431
|
aria-live="polite"
|
|
426
432
|
data-stack="expanded"
|
|
427
433
|
class={cn(
|
|
428
|
-
'pointer-events-none fixed
|
|
434
|
+
'pointer-events-none fixed flex flex-col gap-2',
|
|
429
435
|
anchor()
|
|
430
436
|
? ANCHOR_FLEX[position()]
|
|
431
437
|
: cn('max-w-[min(30rem,calc(100vw-2rem))]', POSITION_CLASSES[position()]),
|
|
432
438
|
)}
|
|
433
|
-
style={anchor() ? anchorStyle(position(), anchor()!) : undefined}
|
|
439
|
+
style={{ 'z-index': TOAST_Z, ...(anchor() ? anchorStyle(position(), anchor()!) : undefined) }}
|
|
434
440
|
>
|
|
435
441
|
<For each={visible()}>
|
|
436
442
|
{(item) => (
|
|
@@ -456,8 +462,8 @@ export function ToastRegion(props: ToastRegionProps) {
|
|
|
456
462
|
onPointerLeave={() => setHovered(false)}
|
|
457
463
|
onFocusIn={() => setFocused(true)}
|
|
458
464
|
onFocusOut={() => setFocused(false)}
|
|
459
|
-
class={cn('pointer-events-none fixed
|
|
460
|
-
style={anchor() ? anchorStyle(position(), anchor()!) : undefined}
|
|
465
|
+
class={cn('pointer-events-none fixed', !anchor() && POSITION_CLASSES[position()])}
|
|
466
|
+
style={{ 'z-index': TOAST_Z, ...(anchor() ? anchorStyle(position(), anchor()!) : undefined) }}
|
|
461
467
|
>
|
|
462
468
|
{/* Relative stage with an explicit height so it's ONE continuous hover
|
|
463
469
|
surface (pointer-events-auto) — no flicker when the cursor crosses gaps
|
|
@@ -25,6 +25,15 @@ export interface ToolPart {
|
|
|
25
25
|
type: string;
|
|
26
26
|
/** Semantic classification for rendering. Derive with classifyTool(type). */
|
|
27
27
|
kind?: ToolKind;
|
|
28
|
+
/** The call's lifecycle. A provider stream (real or `createMockResponder`)
|
|
29
|
+
* only ever ANNOUNCES a call: arguments stream in (`input-streaming`), then
|
|
30
|
+
* the part parks at `input-available` — and stays there. The kit parses and
|
|
31
|
+
* renders the call; EXECUTING it and answering is the host's side of the
|
|
32
|
+
* seam: after the read settles, run the tool and patch the part forward with
|
|
33
|
+
* `stream.upsertTool(id, { state: 'output-available', output })` (or
|
|
34
|
+
* `upsertToolPart`), or `output-error` + `errorText` on failure. The one
|
|
35
|
+
* exception is a call the provider ran itself (`raw` carries the result and
|
|
36
|
+
* the wire sets `output` directly) — never re-execute those. */
|
|
28
37
|
state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error';
|
|
29
38
|
/** Last VALID parsed snapshot, fingerprint-deduped. The primary channel: this is
|
|
30
39
|
* what the kit renders. */
|
|
@@ -38,6 +38,10 @@ export interface VoiceInputProps {
|
|
|
38
38
|
/** Receive the imperative controller once mounted. The `<kai-voice-input>`
|
|
39
39
|
* facade forwards these as element methods (start/stop). */
|
|
40
40
|
controllerRef?: (controller: VoiceInputController) => void;
|
|
41
|
+
/** A recognition session failed or produced nothing. `error` is the platform
|
|
42
|
+
* error code, the thrown exception's name, or `no-result` when the session
|
|
43
|
+
* ended with no error and no text. The facade maps this to kai-voice-error. */
|
|
44
|
+
onError?: (detail: { source: 'recognition'; error: string; message: string }) => void;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
export function VoiceInput(props: VoiceInputProps) {
|
|
@@ -59,15 +63,43 @@ export function VoiceInput(props: VoiceInputProps) {
|
|
|
59
63
|
|
|
60
64
|
// Native path: SpeechRecognition resolves with the final transcript when it
|
|
61
65
|
// ends (click again or controller.stop()). Final text → onTranscription.
|
|
66
|
+
// Every other outcome reaches onError, so a session can never end in silence
|
|
67
|
+
// (W4 symptom 1: runtime errors set a signal nothing read, and an empty
|
|
68
|
+
// result fired no event at all).
|
|
62
69
|
async function beginRecognition() {
|
|
70
|
+
let errored = false;
|
|
63
71
|
try {
|
|
64
72
|
const text = await speech.start({
|
|
65
73
|
lang: props.lang,
|
|
66
74
|
onInterim: props.interim ? (t) => props.onInterim?.(t) : undefined,
|
|
75
|
+
onError: (error, message) => {
|
|
76
|
+
errored = true;
|
|
77
|
+
props.onError?.({ source: 'recognition', error, message });
|
|
78
|
+
},
|
|
67
79
|
});
|
|
68
|
-
if (text.trim())
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
if (text.trim()) {
|
|
81
|
+
local.onTranscription(text.trim());
|
|
82
|
+
} else if (!errored) {
|
|
83
|
+
// No error, no text: the session ended cleanly having heard nothing.
|
|
84
|
+
// Reported through the SAME error event (as `no-result`) rather than an
|
|
85
|
+
// empty kai-transcription, so kai-transcription keeps its contract
|
|
86
|
+
// (final text exists) and one listener covers every failed session; the
|
|
87
|
+
// code distinguishes "user said nothing" from "recognition broke".
|
|
88
|
+
props.onError?.({
|
|
89
|
+
source: 'recognition',
|
|
90
|
+
error: 'no-result',
|
|
91
|
+
message: 'Speech recognition ended without capturing any text',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
// start() itself threw (unsupported, or the platform refused to start).
|
|
96
|
+
if (!errored) {
|
|
97
|
+
props.onError?.({
|
|
98
|
+
source: 'recognition',
|
|
99
|
+
error: err instanceof Error ? err.name : 'unknown',
|
|
100
|
+
message: err instanceof Error ? err.message : String(err),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
71
103
|
}
|
|
72
104
|
}
|
|
73
105
|
|
|
@@ -26,8 +26,15 @@ export interface VoiceOutputProps {
|
|
|
26
26
|
/** TTS model seam: given text, return an audio Blob to play. When set, the
|
|
27
27
|
* native speechSynthesis path is bypassed. Mirrors VoiceInput's `onTranscribe`. */
|
|
28
28
|
onSynthesize?: (text: string) => Promise<Blob>;
|
|
29
|
-
/** Fires whenever playback starts or stops.
|
|
29
|
+
/** Fires whenever playback starts or stops. `speaking: true` means audio has
|
|
30
|
+
* actually started (utterance.onstart on the native path; audio.play()
|
|
31
|
+
* resolving on the model path), not that speak() was called. */
|
|
30
32
|
onSpeakingChange?: (speaking: boolean) => void;
|
|
33
|
+
/** Synthesis failed: a native utterance error, a rejecting `onSynthesize`, or
|
|
34
|
+
* audio playback failing to start. `error` is the platform error code or the
|
|
35
|
+
* exception's name. Deliberate cancellation (stop(), a new speak()) is not a
|
|
36
|
+
* failure and does not fire. The facade maps this to kai-voice-error. */
|
|
37
|
+
onError?: (detail: { source: 'synthesis'; error: string; message: string }) => void;
|
|
31
38
|
/** Fires once the model path resolves audio (model path only). */
|
|
32
39
|
onSynthesized?: (blob: Blob) => void;
|
|
33
40
|
disabled?: boolean;
|
|
@@ -68,13 +75,30 @@ export function VoiceOutput(props: VoiceOutputProps) {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
// Native path: speechSynthesis.speak(new SpeechSynthesisUtterance(text)).
|
|
78
|
+
// isSpeaking waits for utterance.onstart, so onSpeakingChange(true) means the
|
|
79
|
+
// audio actually began. It used to flip optimistically before speak(), which
|
|
80
|
+
// made "speaking" mean "speak() was called" and raced consumers watching for
|
|
81
|
+
// playback to start (W4 symptom 3).
|
|
71
82
|
function speakNative() {
|
|
72
83
|
if (!hasSpeechSynthesis() || !local.text) return;
|
|
73
84
|
window.speechSynthesis.cancel();
|
|
74
85
|
utterance = new SpeechSynthesisUtterance(local.text);
|
|
86
|
+
utterance.onstart = () => setIsSpeaking(true);
|
|
75
87
|
utterance.onend = () => setIsSpeaking(false);
|
|
76
|
-
utterance.onerror = () =>
|
|
77
|
-
|
|
88
|
+
utterance.onerror = (event) => {
|
|
89
|
+
setIsSpeaking(false);
|
|
90
|
+
const code = event?.error ?? 'unknown';
|
|
91
|
+
// `canceled`/`interrupted` are what the platform reports when the app
|
|
92
|
+
// itself cancels (stop(), or a new speak() pre-empting): deliberate, not
|
|
93
|
+
// a failure. Everything else surfaces (W4 traced a `not-allowed` here
|
|
94
|
+
// that vanished without a word).
|
|
95
|
+
if (code === 'canceled' || code === 'interrupted') return;
|
|
96
|
+
props.onError?.({
|
|
97
|
+
source: 'synthesis',
|
|
98
|
+
error: code,
|
|
99
|
+
message: `Speech synthesis error: ${code}`,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
78
102
|
window.speechSynthesis.speak(utterance);
|
|
79
103
|
}
|
|
80
104
|
|
|
@@ -90,11 +114,27 @@ export function VoiceOutput(props: VoiceOutputProps) {
|
|
|
90
114
|
audio ??= new Audio();
|
|
91
115
|
audio.src = objectUrl;
|
|
92
116
|
audio.onended = () => setIsSpeaking(false);
|
|
93
|
-
audio.onerror = () =>
|
|
94
|
-
|
|
117
|
+
audio.onerror = () => {
|
|
118
|
+
setIsSpeaking(false);
|
|
119
|
+
props.onError?.({
|
|
120
|
+
source: 'synthesis',
|
|
121
|
+
error: 'playback-failed',
|
|
122
|
+
message: 'The synthesized audio failed to play',
|
|
123
|
+
});
|
|
124
|
+
};
|
|
95
125
|
await audio.play();
|
|
96
|
-
|
|
126
|
+
// play() resolving is this path's "audio actually started" (the model
|
|
127
|
+
// path's counterpart of utterance.onstart), so isSpeaking flips only now.
|
|
128
|
+
setIsSpeaking(true);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// onSynthesize rejected, or play() did (e.g. NotAllowedError under an
|
|
131
|
+
// autoplay policy). Either way the reply is silent: say so.
|
|
97
132
|
setIsSpeaking(false);
|
|
133
|
+
props.onError?.({
|
|
134
|
+
source: 'synthesis',
|
|
135
|
+
error: err instanceof Error ? err.name : 'unknown',
|
|
136
|
+
message: err instanceof Error ? err.message : String(err),
|
|
137
|
+
});
|
|
98
138
|
} finally {
|
|
99
139
|
setIsLoading(false);
|
|
100
140
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Show, createSignal, createMemo, onMount, onCleanup, type JSX } from 'solid-js';
|
|
2
|
+
import { createControllableSignal } from '../primitives/controllable';
|
|
3
|
+
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '../ui/resizable';
|
|
4
|
+
import { cn } from '../utils/cn';
|
|
5
|
+
|
|
6
|
+
/** The two aside columns of the shell, named logically (`start` follows the
|
|
7
|
+
* writing direction; on an RTL page it is the right column). */
|
|
8
|
+
export type WorkspaceAsideSide = 'start' | 'end';
|
|
9
|
+
|
|
10
|
+
/** Detail of an aside collapse/expand, however it happened (a method, the
|
|
11
|
+
* breakpoint, the drawer's Escape). */
|
|
12
|
+
export interface WorkspaceAsideToggleDetail {
|
|
13
|
+
side: WorkspaceAsideSide;
|
|
14
|
+
collapsed: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Detail of an aside resize (fires per drag step, keyboard nudge, or a handle double-click reset), width in px. */
|
|
18
|
+
export interface WorkspaceAsideResizeDetail {
|
|
19
|
+
side: WorkspaceAsideSide;
|
|
20
|
+
width: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Imperative surface exposed through `controllerRef` (the facade forwards it
|
|
24
|
+
* as element methods). */
|
|
25
|
+
export interface WorkspaceShellController {
|
|
26
|
+
/** Collapse/expand one aside (reports through onAsideToggle). */
|
|
27
|
+
toggleAside(side: WorkspaceAsideSide): void;
|
|
28
|
+
/** Force one aside collapsed (reports through onAsideToggle). */
|
|
29
|
+
collapseAside(side: WorkspaceAsideSide): void;
|
|
30
|
+
/** Force one aside expanded (reports through onAsideToggle). */
|
|
31
|
+
expandAside(side: WorkspaceAsideSide): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WorkspaceShellProps {
|
|
35
|
+
/** Header band content. The region renders only when this is provided. */
|
|
36
|
+
header?: JSX.Element;
|
|
37
|
+
/** Start aside content (the inline-start column: a rail, a nav, a file tree).
|
|
38
|
+
* The region renders only when this is provided. */
|
|
39
|
+
start?: JSX.Element;
|
|
40
|
+
/** End aside content (the inline-end column: inspector, notes, preview).
|
|
41
|
+
* The region renders only when this is provided. */
|
|
42
|
+
end?: JSX.Element;
|
|
43
|
+
/** Footer band content. The region renders only when this is provided. */
|
|
44
|
+
footer?: JSX.Element;
|
|
45
|
+
/** Main region content. Always rendered. */
|
|
46
|
+
children?: JSX.Element;
|
|
47
|
+
|
|
48
|
+
/** Controlled collapsed state of the start aside. Omit for uncontrolled. */
|
|
49
|
+
startCollapsed?: boolean;
|
|
50
|
+
/** Initial collapsed state of the start aside when uncontrolled (default false). */
|
|
51
|
+
defaultStartCollapsed?: boolean;
|
|
52
|
+
/** Controlled collapsed state of the end aside. Omit for uncontrolled. */
|
|
53
|
+
endCollapsed?: boolean;
|
|
54
|
+
/** Initial collapsed state of the end aside when uncontrolled (default false). */
|
|
55
|
+
defaultEndCollapsed?: boolean;
|
|
56
|
+
|
|
57
|
+
/** Start aside default width in px (default 280). */
|
|
58
|
+
startWidth?: number;
|
|
59
|
+
/** Start aside minimum width in px during resize (default 200). */
|
|
60
|
+
startMinWidth?: number;
|
|
61
|
+
/** Start aside maximum width in px during resize (default 480). */
|
|
62
|
+
startMaxWidth?: number;
|
|
63
|
+
/** End aside default width in px (default 320). */
|
|
64
|
+
endWidth?: number;
|
|
65
|
+
/** End aside minimum width in px during resize (default 200). */
|
|
66
|
+
endMinWidth?: number;
|
|
67
|
+
/** End aside maximum width in px during resize (default 480). */
|
|
68
|
+
endMaxWidth?: number;
|
|
69
|
+
|
|
70
|
+
/** Auto-collapse both asides when the shell's own width drops below this many
|
|
71
|
+
* px, and re-expand when it grows back above. Applies to uncontrolled asides
|
|
72
|
+
* only (it never fights an app-driven collapsed prop); omit to disable. */
|
|
73
|
+
collapseBelow?: number;
|
|
74
|
+
/** Below this shell width in px, an expanded aside renders as an overlay
|
|
75
|
+
* drawer over the main region instead of a column beside it. Escape inside
|
|
76
|
+
* the drawer closes it and returns focus to the element focused before it
|
|
77
|
+
* opened. Omit to disable. */
|
|
78
|
+
drawerBelow?: number;
|
|
79
|
+
/** Density hint. Reflected as a `data-compact` hook on the root (and as the
|
|
80
|
+
* `compact` attribute on the element) for your CSS and slotted content; the
|
|
81
|
+
* shell itself keeps no other opinion about density. */
|
|
82
|
+
compact?: boolean;
|
|
83
|
+
|
|
84
|
+
/** An aside collapsed or expanded (any path: method, breakpoint, drawer Escape). */
|
|
85
|
+
onAsideToggle?: (detail: WorkspaceAsideToggleDetail) => void;
|
|
86
|
+
/** The aside was resized (fires per drag step, keyboard nudge, or a handle double-click reset), width in px. */
|
|
87
|
+
onAsideResize?: (detail: WorkspaceAsideResizeDetail) => void;
|
|
88
|
+
|
|
89
|
+
/** Receives the imperative controller once on mount. */
|
|
90
|
+
controllerRef?: (controller: WorkspaceShellController) => void;
|
|
91
|
+
class?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The chat-agnostic workspace layout shell: five regions (header · start aside ·
|
|
96
|
+
* main · end aside · footer) with resize handles between the columns, per-aside
|
|
97
|
+
* controlled/uncontrolled collapse, collapse-below-breakpoint, and a mobile
|
|
98
|
+
* drawer mode for the asides. It knows nothing about chat: a file tree in
|
|
99
|
+
* `start` is as valid as a conversation rail.
|
|
100
|
+
*/
|
|
101
|
+
export function WorkspaceShell(props: WorkspaceShellProps) {
|
|
102
|
+
let rootEl!: HTMLDivElement;
|
|
103
|
+
|
|
104
|
+
// --- host width (drives collapseBelow + drawerBelow) ---
|
|
105
|
+
const [hostWidth, setHostWidth] = createSignal<number | undefined>(undefined);
|
|
106
|
+
|
|
107
|
+
// --- per-aside collapse: controlled wins, otherwise internal state ---
|
|
108
|
+
const [startCollapsed, setStartCollapsed] = createControllableSignal(
|
|
109
|
+
() => props.startCollapsed,
|
|
110
|
+
props.defaultStartCollapsed ?? false,
|
|
111
|
+
);
|
|
112
|
+
const [endCollapsed, setEndCollapsed] = createControllableSignal(
|
|
113
|
+
() => props.endCollapsed,
|
|
114
|
+
props.defaultEndCollapsed ?? false,
|
|
115
|
+
);
|
|
116
|
+
const collapsed = (side: WorkspaceAsideSide) => (side === 'start' ? startCollapsed() : endCollapsed());
|
|
117
|
+
const isControlled = (side: WorkspaceAsideSide) =>
|
|
118
|
+
(side === 'start' ? props.startCollapsed : props.endCollapsed) !== undefined;
|
|
119
|
+
|
|
120
|
+
const setCollapsedTo = (side: WorkspaceAsideSide, next: boolean) => {
|
|
121
|
+
if (side === 'start') setStartCollapsed(next); else setEndCollapsed(next);
|
|
122
|
+
if (next) restoreDrawerFocus(side);
|
|
123
|
+
props.onAsideToggle?.({ side, collapsed: next });
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// --- drawer mode + focus return ---
|
|
127
|
+
const drawerMode = createMemo(() => {
|
|
128
|
+
const below = props.drawerBelow;
|
|
129
|
+
const width = hostWidth();
|
|
130
|
+
return below != null && width != null && width < below;
|
|
131
|
+
});
|
|
132
|
+
// The element focused when a drawer opened, restored on close. Per side, so a
|
|
133
|
+
// start drawer closing never steals focus a later end drawer captured.
|
|
134
|
+
const drawerOpeners: Partial<Record<WorkspaceAsideSide, HTMLElement>> = {};
|
|
135
|
+
const captureDrawerFocus = (side: WorkspaceAsideSide) => {
|
|
136
|
+
if (!drawerMode()) return;
|
|
137
|
+
const active = document.activeElement;
|
|
138
|
+
if (active instanceof HTMLElement) drawerOpeners[side] = active;
|
|
139
|
+
};
|
|
140
|
+
const restoreDrawerFocus = (side: WorkspaceAsideSide) => {
|
|
141
|
+
const opener = drawerOpeners[side];
|
|
142
|
+
delete drawerOpeners[side];
|
|
143
|
+
if (drawerMode() && opener && opener.isConnected) opener.focus();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const expand = (side: WorkspaceAsideSide) => {
|
|
147
|
+
captureDrawerFocus(side);
|
|
148
|
+
setCollapsedTo(side, false);
|
|
149
|
+
};
|
|
150
|
+
const controller: WorkspaceShellController = {
|
|
151
|
+
toggleAside: (side) => (collapsed(side) ? expand(side) : setCollapsedTo(side, true)),
|
|
152
|
+
collapseAside: (side) => setCollapsedTo(side, true),
|
|
153
|
+
expandAside: (side) => expand(side),
|
|
154
|
+
};
|
|
155
|
+
props.controllerRef?.(controller);
|
|
156
|
+
|
|
157
|
+
// --- collapse-below-breakpoint (uncontrolled asides only) ---
|
|
158
|
+
// `autoCollapsed` tracks whether WE collapsed a side, so crossing back above
|
|
159
|
+
// the breakpoint never expands an aside the user collapsed themselves.
|
|
160
|
+
const autoCollapsed: Record<WorkspaceAsideSide, boolean> = { start: false, end: false };
|
|
161
|
+
const applyBreakpoint = (width: number) => {
|
|
162
|
+
const below = props.collapseBelow;
|
|
163
|
+
if (below == null) return;
|
|
164
|
+
const isBelow = width < below;
|
|
165
|
+
for (const side of ['start', 'end'] as const) {
|
|
166
|
+
// A side with no content has nothing to collapse; reporting a toggle for
|
|
167
|
+
// a region that does not exist would be noise, not loudness.
|
|
168
|
+
if (!asideContent(side)) continue;
|
|
169
|
+
if (isControlled(side)) continue;
|
|
170
|
+
if (isBelow && !collapsed(side)) {
|
|
171
|
+
autoCollapsed[side] = true;
|
|
172
|
+
setCollapsedTo(side, true);
|
|
173
|
+
} else if (!isBelow && collapsed(side) && autoCollapsed[side]) {
|
|
174
|
+
autoCollapsed[side] = false;
|
|
175
|
+
setCollapsedTo(side, false);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
onMount(() => {
|
|
181
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
182
|
+
const ro = new ResizeObserver((entries) => {
|
|
183
|
+
const width = entries[0]?.contentRect.width ?? rootEl.clientWidth;
|
|
184
|
+
setHostWidth(width);
|
|
185
|
+
applyBreakpoint(width);
|
|
186
|
+
});
|
|
187
|
+
ro.observe(rootEl);
|
|
188
|
+
onCleanup(() => ro.disconnect());
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// --- widths ---
|
|
192
|
+
const widths = (side: WorkspaceAsideSide) =>
|
|
193
|
+
side === 'start'
|
|
194
|
+
? {
|
|
195
|
+
size: props.startWidth ?? 280,
|
|
196
|
+
min: props.startMinWidth ?? 200,
|
|
197
|
+
max: props.startMaxWidth ?? 480,
|
|
198
|
+
}
|
|
199
|
+
: {
|
|
200
|
+
size: props.endWidth ?? 320,
|
|
201
|
+
min: props.endMinWidth ?? 200,
|
|
202
|
+
max: props.endMaxWidth ?? 480,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// --- drawer Escape: scoped to the drawer (never a document listener, never
|
|
206
|
+
// swallowed — the kai-dock rule at page scale) ---
|
|
207
|
+
const onDrawerKeyDown = (side: WorkspaceAsideSide) => (event: KeyboardEvent) => {
|
|
208
|
+
if (event.key !== 'Escape') return;
|
|
209
|
+
setCollapsedTo(side, true);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const asideRefs: Partial<Record<WorkspaceAsideSide, HTMLElement>> = {};
|
|
213
|
+
const emitResize = (side: WorkspaceAsideSide) => {
|
|
214
|
+
const el = asideRefs[side];
|
|
215
|
+
if (!el) return;
|
|
216
|
+
props.onAsideResize?.({ side, width: el.getBoundingClientRect().width });
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const asideContent = (side: WorkspaceAsideSide) => (side === 'start' ? props.start : props.end);
|
|
220
|
+
|
|
221
|
+
/** An aside as a resizable column (desktop mode). */
|
|
222
|
+
const asideColumn = (side: WorkspaceAsideSide) => {
|
|
223
|
+
const w = widths(side);
|
|
224
|
+
return (
|
|
225
|
+
<ResizablePanel
|
|
226
|
+
defaultSize={`${w.size}px`}
|
|
227
|
+
minSize={`${w.min}px`}
|
|
228
|
+
maxSize={`${w.max}px`}
|
|
229
|
+
part={side === 'start' ? 'aside start' : 'aside end'}
|
|
230
|
+
ref={(el: HTMLElement) => (asideRefs[side] = el)}
|
|
231
|
+
class="bg-surface"
|
|
232
|
+
>
|
|
233
|
+
{asideContent(side)}
|
|
234
|
+
</ResizablePanel>
|
|
235
|
+
);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/** An aside as an overlay drawer (mobile mode). */
|
|
239
|
+
const asideDrawer = (side: WorkspaceAsideSide) => (
|
|
240
|
+
<div
|
|
241
|
+
part={side === 'start' ? 'aside start' : 'aside end'}
|
|
242
|
+
data-drawer=""
|
|
243
|
+
class={cn(
|
|
244
|
+
'absolute inset-y-0 z-20 max-w-[85%] overflow-auto border-border bg-surface shadow-lg',
|
|
245
|
+
side === 'start' ? 'start-0 border-e' : 'end-0 border-s',
|
|
246
|
+
)}
|
|
247
|
+
style={{ width: `${widths(side).size}px` }}
|
|
248
|
+
onKeyDown={onDrawerKeyDown(side)}
|
|
249
|
+
>
|
|
250
|
+
{asideContent(side)}
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const showAside = (side: WorkspaceAsideSide) => !!asideContent(side) && !collapsed(side);
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<div
|
|
258
|
+
ref={rootEl}
|
|
259
|
+
class={cn('flex h-full w-full flex-col overflow-hidden bg-background', props.class)}
|
|
260
|
+
data-compact={props.compact ? '' : undefined}
|
|
261
|
+
>
|
|
262
|
+
<Show when={props.header}>
|
|
263
|
+
<div part="header" class="shrink-0 border-b border-border">{props.header}</div>
|
|
264
|
+
</Show>
|
|
265
|
+
<div class="relative min-h-0 flex-1">
|
|
266
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
267
|
+
<Show when={showAside('start') && !drawerMode()}>
|
|
268
|
+
{asideColumn('start')}
|
|
269
|
+
<ResizableHandle onPanelResize={() => emitResize('start')} />
|
|
270
|
+
</Show>
|
|
271
|
+
<ResizablePanel part="main">{props.children}</ResizablePanel>
|
|
272
|
+
<Show when={showAside('end') && !drawerMode()}>
|
|
273
|
+
<ResizableHandle onPanelResize={() => emitResize('end')} />
|
|
274
|
+
{asideColumn('end')}
|
|
275
|
+
</Show>
|
|
276
|
+
</ResizablePanelGroup>
|
|
277
|
+
<Show when={showAside('start') && drawerMode()}>{asideDrawer('start')}</Show>
|
|
278
|
+
<Show when={showAside('end') && drawerMode()}>{asideDrawer('end')}</Show>
|
|
279
|
+
</div>
|
|
280
|
+
<Show when={props.footer}>
|
|
281
|
+
<div part="footer" class="shrink-0 border-t border-border">{props.footer}</div>
|
|
282
|
+
</Show>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|