@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The ELEMENT-layer diagnostic events: what the `kai-*` custom elements SAW a
|
|
2
|
+
// consumer do to them.
|
|
3
|
+
//
|
|
4
|
+
// The wire events answer "did the data arrive". These answer the other half —
|
|
5
|
+
// "the data is right and the UI still is not updating" — which is a different
|
|
6
|
+
// failure with a different cause and, until now, no evidence at all. All three
|
|
7
|
+
// causes are documented in root CLAUDE.md's `kai-` contract section, and every
|
|
8
|
+
// one of them is silent by construction:
|
|
9
|
+
//
|
|
10
|
+
// 1. An array or object prop set as an HTML ATTRIBUTE. `component-register`
|
|
11
|
+
// parses attributes with a `JSON.parse` whose `catch` returns the raw
|
|
12
|
+
// string, so `el.setAttribute('messages', arr)` assigns the literal text
|
|
13
|
+
// `"[object Object],[object Object]"` to the property and pushes THAT into
|
|
14
|
+
// the Solid signal. Nothing throws, nothing logs, nothing renders.
|
|
15
|
+
// 2. The SAME array reference handed back. solid-element creates one signal
|
|
16
|
+
// per prop and sets it with `set(() => v)`; Solid's default equality is
|
|
17
|
+
// `===`, so re-assigning the identical reference notifies nothing.
|
|
18
|
+
// 3. A NEW array whose items are all the previous item objects. The array
|
|
19
|
+
// notifies, but the lists render through reference-keyed `<For>`s, so no
|
|
20
|
+
// row is re-invoked and nothing on screen changes.
|
|
21
|
+
//
|
|
22
|
+
// ZERO IMPORTS, DELIBERATELY. These are type declarations only, so this file is
|
|
23
|
+
// erased at build. That is what lets `wire/diagnostics.ts` name them in its
|
|
24
|
+
// union without `wire/` acquiring a runtime dependency on `elements/` — which
|
|
25
|
+
// would be a real layering inversion, and would drag the element bundle into
|
|
26
|
+
// every consumer that only parses streams.
|
|
27
|
+
//
|
|
28
|
+
// METADATA ONLY, by the same rule the wire events follow: if a value comes from
|
|
29
|
+
// the model, the end user, or the app's data it is PAYLOAD; if it describes the
|
|
30
|
+
// shape, size, timing or identity of that value it is METADATA. A tag name, a
|
|
31
|
+
// prop name, a kind, a count and a length are all identity or shape. A prop's
|
|
32
|
+
// VALUE never appears in any field below — `valuePreview` is a shape
|
|
33
|
+
// description built from a closed vocabulary, never the text itself. Pinned by
|
|
34
|
+
// `element-diagnostics-payload.test.ts`, which plants a sentinel inside every
|
|
35
|
+
// prop value it sets and asserts it never reaches a serialized event.
|
|
36
|
+
|
|
37
|
+
/** The envelope, structurally identical to `WireDiagnosticBase`. Restated here
|
|
38
|
+
* rather than imported so this file keeps its zero-import property; the two are
|
|
39
|
+
* kept in step by `element-diagnostics.test.ts`, which asserts every emitted
|
|
40
|
+
* element event carries a `type` and a numeric `t`. */
|
|
41
|
+
export interface ElementDiagnosticBase {
|
|
42
|
+
type: string;
|
|
43
|
+
/** `Date.now()` at emission. */
|
|
44
|
+
t: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Which contract was broken. A closed vocabulary, so a panel keys its own
|
|
48
|
+
* explanation off the kind and never needs prose from the kit. */
|
|
49
|
+
export type ElementViolationKind =
|
|
50
|
+
/** A non-scalar prop received a value through the ATTRIBUTE channel. The
|
|
51
|
+
* attribute cannot carry an array or an object, so the property now holds a
|
|
52
|
+
* string and the element renders nothing. */
|
|
53
|
+
| 'array-prop-as-attribute'
|
|
54
|
+
/** A list prop was set to the array reference it already held. Solid compares
|
|
55
|
+
* with `===`, so this notified nothing — a no-op write. */
|
|
56
|
+
| 'same-array-reference'
|
|
57
|
+
/** A list prop received a NEW array of the SAME length whose every item is
|
|
58
|
+
* reference-identical to the previous array's. The array notified, but every
|
|
59
|
+
* row's identity is unchanged, so a reference-keyed `<For>` re-invokes none
|
|
60
|
+
* of them. The classic "I mutated the item and copied the array" case.
|
|
61
|
+
*
|
|
62
|
+
* NOTE what this deliberately does NOT claim: the kit cannot know an item
|
|
63
|
+
* "changed" without comparing its CONTENT, and content is payload. So this
|
|
64
|
+
* reports the structurally knowable fact — an update that cannot render —
|
|
65
|
+
* and leaves the question of whether the consumer meant anything by it to
|
|
66
|
+
* the consumer, who is the only one who can answer it. */
|
|
67
|
+
| 'mutated-in-place';
|
|
68
|
+
|
|
69
|
+
/** A consumer broke one of the three `kai-` contract rules on a live element. */
|
|
70
|
+
export interface ElementViolationEvent extends ElementDiagnosticBase {
|
|
71
|
+
type: 'element.violation';
|
|
72
|
+
kind: ElementViolationKind;
|
|
73
|
+
/** The custom-element tag, e.g. `kai-chat`. */
|
|
74
|
+
tag: string;
|
|
75
|
+
/** The camelCase prop name, e.g. `messages`. */
|
|
76
|
+
prop: string;
|
|
77
|
+
/**
|
|
78
|
+
* SHAPE of the offending value, never its content. `array-prop-as-attribute`
|
|
79
|
+
* only. A closed vocabulary:
|
|
80
|
+
*
|
|
81
|
+
* `[object Object]` one object stringified into the attribute
|
|
82
|
+
* `[object Object] x N` an array of N objects stringified — the common case
|
|
83
|
+
* `json:number` the attribute held valid JSON, but a scalar
|
|
84
|
+
* `json:boolean` "
|
|
85
|
+
* `json:null` "
|
|
86
|
+
* `string(len=N)` anything else: the LENGTH of the text, never the text
|
|
87
|
+
* `empty-attribute` a bare attribute on a prop that needs a value
|
|
88
|
+
*/
|
|
89
|
+
valuePreview?: string;
|
|
90
|
+
/** Element count of the array involved. `same-array-reference` and
|
|
91
|
+
* `mutated-in-place` only. A length is shape, not content. */
|
|
92
|
+
length?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Which `kai-*` elements are DEFINED in this realm right now.
|
|
96
|
+
*
|
|
97
|
+
* The question behind it is hydration: an SSR page whose markup contains
|
|
98
|
+
* `<kai-chat>` but whose element bundle never loaded shows empty boxes and no
|
|
99
|
+
* error, because an undefined custom element is a valid, inert HTMLElement.
|
|
100
|
+
* The SSR starters already answer this by hand for a hard-coded handful
|
|
101
|
+
* (`HydrationBadge.tsx`); this is the same answer over the whole manifest. */
|
|
102
|
+
export interface ElementRegistryEvent extends ElementDiagnosticBase {
|
|
103
|
+
type: 'element.registry';
|
|
104
|
+
/** Tags `customElements.get()` resolved. */
|
|
105
|
+
defined: string[];
|
|
106
|
+
/** Tags in the manifest that are NOT defined in this realm. Named for what it
|
|
107
|
+
* is rather than `undefined`, which is a miserable key to read in a panel. */
|
|
108
|
+
notDefined: string[];
|
|
109
|
+
/** `defined.length + notDefined.length`. Present so a panel can show "62 of
|
|
110
|
+
* 79" without trusting itself to add up two arrays it may have truncated. */
|
|
111
|
+
total: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type ElementDiagnosticEvent = ElementViolationEvent | ElementRegistryEvent;
|
package/src/elements/dialog.tsx
CHANGED
|
@@ -10,6 +10,44 @@ interface Props extends Record<string, unknown> {
|
|
|
10
10
|
open?: boolean;
|
|
11
11
|
/** Initial open state on mount (uncontrolled seed). */
|
|
12
12
|
defaultOpen?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Accessible name for the modal, used when no `header` slot is projected:
|
|
15
|
+
* `<kai-dialog label="Delete workspace">`. A projected `header` WINS over this
|
|
16
|
+
* (it becomes `aria-labelledby`), because ARIA resolves `aria-labelledby` ahead
|
|
17
|
+
* of `aria-label` and the visible heading is the name both a sighted and a
|
|
18
|
+
* screen-reader user can be talked through. Defaults to `Dialog` so a modal is
|
|
19
|
+
* never nameless.
|
|
20
|
+
*/
|
|
21
|
+
label?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The fallback accessible name.
|
|
26
|
+
*
|
|
27
|
+
* DELIBERATELY GENERIC, and a decision rather than a placeholder. A modal with no
|
|
28
|
+
* name at all fails WCAG and axe's `aria-dialog-name`; "Dialog, dialog" is merely
|
|
29
|
+
* redundant, and between the two the redundant one is strictly better — it is also
|
|
30
|
+
* what Shoelace and WebAwesome ship. The kit cannot know what a given modal is about,
|
|
31
|
+
* so inventing something specific here would be a worse default than saying nothing
|
|
32
|
+
* extra. Consumers name it properly with `label` or by projecting a `header`.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_LABEL = 'Dialog';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The consumer's label, or the fallback — with "no label" meaning every way a
|
|
38
|
+
* consumer can arrive at one.
|
|
39
|
+
*
|
|
40
|
+
* The declared default is a SEED, not a floor: component-register writes the prop
|
|
41
|
+
* back as `null` when the attribute is removed and does NOT restore the declared
|
|
42
|
+
* value, so `props.label` is legitimately `null`/`undefined` at runtime. An empty or
|
|
43
|
+
* whitespace-only string is the same situation wearing a name — `aria-label=""` is a
|
|
44
|
+
* `role="dialog"` with no accessible name, i.e. the exact defect this exists to
|
|
45
|
+
* prevent, reached through the attribute that was supposed to prevent it. Each of
|
|
46
|
+
* those routes is a row in tests/elements/dialog.test.tsx, because a mutation run
|
|
47
|
+
* showed the plain `??` was uncovered.
|
|
48
|
+
*/
|
|
49
|
+
function resolveLabel(label: unknown): string {
|
|
50
|
+
return typeof label === 'string' && label.trim() !== '' ? label : DEFAULT_LABEL;
|
|
13
51
|
}
|
|
14
52
|
|
|
15
53
|
/** Events fired by `<kai-dialog>`. */
|
|
@@ -40,6 +78,10 @@ interface Events {
|
|
|
40
78
|
* the dialog. Focus moves into the panel on open and is restored on close, and a
|
|
41
79
|
* basic Tab focus trap keeps keyboard focus inside while open.
|
|
42
80
|
*
|
|
81
|
+
* The dialog is always named: a projected `header` becomes its `aria-labelledby`,
|
|
82
|
+
* and otherwise `label` becomes its `aria-label` (defaulting to `Dialog`). The
|
|
83
|
+
* header wins when both are present, since ARIA ranks `aria-labelledby` first.
|
|
84
|
+
*
|
|
43
85
|
* Open state is the standard disclosure surface: settable+reflecting `open`,
|
|
44
86
|
* `kai-open-change`, and `show()`/`hide()`/`toggle()`; seed with `default-open`.
|
|
45
87
|
* It also exposes `focus()` to move focus to the panel. Parts: `backdrop` ·
|
|
@@ -48,6 +90,7 @@ interface Events {
|
|
|
48
90
|
defineWebComponent<Props, Events>('kai-dialog', {
|
|
49
91
|
open: undefined,
|
|
50
92
|
defaultOpen: undefined,
|
|
93
|
+
label: DEFAULT_LABEL,
|
|
51
94
|
}, (props, ctx) => {
|
|
52
95
|
const { flag, element, expose } = ctx;
|
|
53
96
|
let api: DialogController | undefined;
|
|
@@ -87,6 +130,10 @@ defineWebComponent<Props, Events>('kai-dialog', {
|
|
|
87
130
|
return (
|
|
88
131
|
<Dialog
|
|
89
132
|
defaultOpen={flag('defaultOpen')}
|
|
133
|
+
// The primitive already ranks these correctly — it emits `aria-labelledby` when
|
|
134
|
+
// a header is present and `aria-label` only when one is not — so the facade just
|
|
135
|
+
// has to HAND the name over, which is the wiring that was missing.
|
|
136
|
+
aria-label={resolveLabel(props.label)}
|
|
90
137
|
header={hasHeader() ? <slot name="header" /> : undefined}
|
|
91
138
|
footer={hasFooter() ? <slot name="footer" /> : undefined}
|
|
92
139
|
controllerRef={(a) => (api = a)}
|
|
@@ -37,22 +37,23 @@ export function wireDisclosure<E extends { 'kai-open-change': { open: boolean }
|
|
|
37
37
|
getApi: () => OpenController | undefined,
|
|
38
38
|
openProp: () => unknown,
|
|
39
39
|
): void {
|
|
40
|
-
const { element, dispatch, flag, expose } = ctx;
|
|
40
|
+
const { element, dispatch, flag, reflectFlag, expose } = ctx;
|
|
41
41
|
let prev: boolean | undefined;
|
|
42
42
|
|
|
43
|
-
// Reflect internal open → the `[open]` host attribute + fire kai-open-change.
|
|
44
|
-
createEffect(() => {
|
|
45
|
-
const api = getApi();
|
|
46
|
-
if (!api) return;
|
|
47
|
-
const o = api.open();
|
|
48
|
-
element.toggleAttribute('open', o);
|
|
49
|
-
if (prev !== undefined && prev !== o) dispatch('kai-open-change', { open: o } as E['kai-open-change']);
|
|
50
|
-
prev = o;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
43
|
// External `open` prop/attr → drive internal state. Only when the consumer has
|
|
54
44
|
// EXPLICITLY set it (so a defaultOpen seed survives mount); the equality guard
|
|
55
|
-
// keeps the reflect
|
|
45
|
+
// keeps the reflect below from looping back through the prop.
|
|
46
|
+
//
|
|
47
|
+
// THIS RUNS FIRST, AND THE ORDER IS THE FIX FOR A REAL DEFECT. It used to run
|
|
48
|
+
// second, after the outward reflection — which on the very first pass read a
|
|
49
|
+
// controller still in its default CLOSED state and called
|
|
50
|
+
// `toggleAttribute('open', false)`, DELETING the author's attribute before anything
|
|
51
|
+
// had read it. This effect then saw no attribute and a prop that
|
|
52
|
+
// component-register had parsed to `undefined`, concluded nothing was asked for,
|
|
53
|
+
// and left the element shut. Net result: `<kai-reasoning open>` — the plain HTML
|
|
54
|
+
// spelling the `open` prop's own doc advertises — never opened; only `default-open`
|
|
55
|
+
// did. Reading intent IN before reflecting state OUT is what makes the author's
|
|
56
|
+
// attribute survive long enough to mean something.
|
|
56
57
|
createEffect(() => {
|
|
57
58
|
const api = getApi();
|
|
58
59
|
if (!api) return;
|
|
@@ -62,6 +63,27 @@ export function wireDisclosure<E extends { 'kai-open-change': { open: boolean }
|
|
|
62
63
|
if (desired !== untrack(api.open)) api.setOpen(desired);
|
|
63
64
|
});
|
|
64
65
|
|
|
66
|
+
// Reflect internal open → the `[open]` host attribute.
|
|
67
|
+
//
|
|
68
|
+
// Through `reflectFlag`, whose SOURCE is the controller rather than the prop — this
|
|
69
|
+
// element's truth is its internal open signal, not `props.open`. It also installs
|
|
70
|
+
// the read-back accessor, which this wiring needed and did not have: `el.open = true`
|
|
71
|
+
// used to leave `el.open === undefined`, exactly as `kai-chat`'s `loading` did
|
|
72
|
+
// (findings G-05). Returning `undefined` before the primitive has handed its
|
|
73
|
+
// controller up leaves the author's `<el open>` attribute alone.
|
|
74
|
+
reflectFlag('open', () => getApi()?.open());
|
|
75
|
+
|
|
76
|
+
// kai-open-change, fired once per change. Separate from the reflection above
|
|
77
|
+
// because it is a notification, not a reflection; both run in the same batch, so
|
|
78
|
+
// splitting them changes nothing observable about the ordering.
|
|
79
|
+
createEffect(() => {
|
|
80
|
+
const api = getApi();
|
|
81
|
+
if (!api) return;
|
|
82
|
+
const o = api.open();
|
|
83
|
+
if (prev !== undefined && prev !== o) dispatch('kai-open-change', { open: o } as E['kai-open-change']);
|
|
84
|
+
prev = o;
|
|
85
|
+
});
|
|
86
|
+
|
|
65
87
|
expose({
|
|
66
88
|
/** Open it programmatically (no-op while disabled). */
|
|
67
89
|
show: () => { if (!flag('disabled')) getApi()?.setOpen(true); },
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { untrack } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import {
|
|
4
|
+
Dock, DockCloseGlyph, DockLauncherGlyph,
|
|
5
|
+
type DockController, type DockFocusOnOpen, type DockPosition,
|
|
6
|
+
} from '../ui/dock';
|
|
7
|
+
import { wireDisclosure } from './disclosure';
|
|
8
|
+
|
|
9
|
+
interface Props extends Record<string, unknown> {
|
|
10
|
+
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open`
|
|
11
|
+
* attribute; the element still self-manages on the launcher and Escape). Set
|
|
12
|
+
* `el.open = true`, or `<kai-dock open>`; listen for `kai-open-change`. */
|
|
13
|
+
open?: boolean;
|
|
14
|
+
/** Initial open state on mount (uncontrolled seed). */
|
|
15
|
+
defaultOpen?: boolean;
|
|
16
|
+
/** Which corner the dock sits in. Logical, so `-end` follows the writing direction
|
|
17
|
+
* and an RTL page docks on the left. Attribute: `position`. */
|
|
18
|
+
position?: DockPosition;
|
|
19
|
+
/** The widget's NAME. Derives the panel's accessible name and both launcher names
|
|
20
|
+
* (`Open ${label}` / `Close ${label}`). Defaults to `Chat`. */
|
|
21
|
+
label?: string;
|
|
22
|
+
/** i18n override for the launcher's name while closed (default `Open ${label}`). */
|
|
23
|
+
openLabel?: string;
|
|
24
|
+
/** i18n override for the launcher's name while open (default `Close ${label}`). */
|
|
25
|
+
closeLabel?: string;
|
|
26
|
+
/** Show the unread dot. YOURS: it renders only while closed, and the dock never
|
|
27
|
+
* writes it back. Clear it in your `kai-open-change` handler. */
|
|
28
|
+
unread?: boolean;
|
|
29
|
+
/** Disable the launcher; `show()` and `toggle()` are gated on it. */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** Suppress the dock's own built-in mobile close X. Set this when your slotted
|
|
32
|
+
* panel content supplies its own close affordance (e.g. a `<kai-chat
|
|
33
|
+
* slot="header-end">` close button), otherwise the two stack. TRADEOFF: the
|
|
34
|
+
* mobile panel reserves a padding band above its content so the built-in X
|
|
35
|
+
* never paints over slotted content; that band stays reserved unless you set
|
|
36
|
+
* this true, so only set it once your own control is actually in place.
|
|
37
|
+
* Attribute: `hide-close`. */
|
|
38
|
+
hideClose?: boolean;
|
|
39
|
+
/** Where focus lands on open: `content` (default, the first element you slotted),
|
|
40
|
+
* `panel`, or `none`. Attribute: `focus-on-open`. */
|
|
41
|
+
focusOnOpen?: DockFocusOnOpen;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Events fired by `<kai-dock>`. */
|
|
45
|
+
interface Events {
|
|
46
|
+
/** The dock opened or closed (the launcher, Escape, a driven `open`, or a method). */
|
|
47
|
+
'kai-open-change': { open: boolean };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `<kai-dock>` — the corner launcher: a floating button pinned to a corner of the
|
|
52
|
+
* viewport, and a panel above it holding whatever you slot in. This is the "chat
|
|
53
|
+
* bubble in the bottom-right" affordance. (Not to be confused with
|
|
54
|
+
* `<kai-prompt-dock>`, which is a recessed tray that frames a prompt INPUT and does
|
|
55
|
+
* not float, launch or open anything.)
|
|
56
|
+
*
|
|
57
|
+
* **How to use** — slot the surface, and let the dock own the button:
|
|
58
|
+
* ```html
|
|
59
|
+
* <kai-dock label="Aurora support">
|
|
60
|
+
* <kai-chat slot="panel" chat-title="Aurora Support"></kai-chat>
|
|
61
|
+
* </kai-dock>
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* The panel is content-agnostic: a `<kai-chat>`, a form, your own element. The dock
|
|
65
|
+
* never reads or types it. `slot="launcher"` / `slot="launcher-open"` swap the icon
|
|
66
|
+
* INSIDE the built-in button — the button itself is never slotted away, because the
|
|
67
|
+
* dock owns `aria-expanded`, `aria-controls`, the toggle wiring and the focus return,
|
|
68
|
+
* and a replaced button would take all four with it.
|
|
69
|
+
*
|
|
70
|
+
* Open state is the standard disclosure surface: settable+reflecting `open`,
|
|
71
|
+
* `kai-open-change`, and `show()`/`hide()`/`toggle()`; seed with `default-open`. It
|
|
72
|
+
* also exposes `focus()` — the panel while open, the launcher while closed.
|
|
73
|
+
*
|
|
74
|
+
* Neither `default-open` nor `open` moves focus at mount. A widget docked on someone
|
|
75
|
+
* else's page does not get to steal focus on load, which is the one place this
|
|
76
|
+
* element differs from `<kai-dialog>`, where a modal opened at mount takes focus on
|
|
77
|
+
* purpose. Opening it later does move focus, per `focus-on-open`.
|
|
78
|
+
*
|
|
79
|
+
* Closed does not mean gone: the panel keeps its layout box (`visibility: hidden` +
|
|
80
|
+
* `inert`), so a thread inside it never re-measures from zero and a reply that lands
|
|
81
|
+
* while closed is really there. `unread` is yours to set and yours to clear; the dot
|
|
82
|
+
* just stops rendering while open.
|
|
83
|
+
*
|
|
84
|
+
* Escape closes only while the dock holds focus, and is never swallowed — a widget
|
|
85
|
+
* docked on someone else's page does not get to eat their Escape. There is no focus
|
|
86
|
+
* trap; the page stays usable, which is the point of "docked".
|
|
87
|
+
*
|
|
88
|
+
* Geometry is CSS custom properties, not props: `--kai-dock-width` ·
|
|
89
|
+
* `--kai-dock-height` · `--kai-dock-inset` · `--kai-dock-gap` · `--kai-dock-radius` ·
|
|
90
|
+
* `--kai-dock-z` · `--kai-dock-launcher-size`. Narrow viewports go full-bleed by
|
|
91
|
+
* default. Parts: `launcher` · `panel` · `badge`.
|
|
92
|
+
*
|
|
93
|
+
* Put it as a body-level child: a `transform` / `filter` / `contain` ancestor makes
|
|
94
|
+
* `position: fixed` resolve against that ancestor instead of the viewport.
|
|
95
|
+
*
|
|
96
|
+
* **Migrating a hand-rolled widget onto this element changes two behaviours**, both
|
|
97
|
+
* deliberately, so check them rather than assuming a drop-in:
|
|
98
|
+
*
|
|
99
|
+
* 1. **Escape is scoped to the dock.** Hand-rolled versions of this widget typically
|
|
100
|
+
* listen on `document` and close on any Escape anywhere on the page. This one
|
|
101
|
+
* closes only while the dock holds focus, and never stops the event. If your app
|
|
102
|
+
* relied on Escape-anywhere, that is now the host page's key again.
|
|
103
|
+
* 2. **The panel keeps its layout box while closed.** It is hidden with
|
|
104
|
+
* `visibility` + `inert`, not `display: none`, and is never unmounted, so whatever
|
|
105
|
+
* you slot in KEEPS RENDERING in the background: a stream still folds in, timers
|
|
106
|
+
* still run, and heights stay measured. That is what makes `unread` honest and
|
|
107
|
+
* what stops a reopened thread re-measuring from zero, but it does mean a panel
|
|
108
|
+
* that was free while `display: none` no longer is.
|
|
109
|
+
*/
|
|
110
|
+
defineWebComponent<Props, Events>('kai-dock', {
|
|
111
|
+
open: undefined,
|
|
112
|
+
defaultOpen: undefined,
|
|
113
|
+
position: 'bottom-end',
|
|
114
|
+
label: 'Chat',
|
|
115
|
+
openLabel: undefined,
|
|
116
|
+
closeLabel: undefined,
|
|
117
|
+
unread: undefined,
|
|
118
|
+
disabled: undefined,
|
|
119
|
+
hideClose: undefined,
|
|
120
|
+
focusOnOpen: 'content',
|
|
121
|
+
}, (props, ctx) => {
|
|
122
|
+
const { flag, element, expose, reflectFlag } = ctx;
|
|
123
|
+
let api: DockController | undefined;
|
|
124
|
+
let panel: HTMLElement | undefined;
|
|
125
|
+
let launcher: HTMLButtonElement | undefined;
|
|
126
|
+
|
|
127
|
+
// The standard disclosure surface: settable+reflecting `open`, kai-open-change,
|
|
128
|
+
// show/hide/toggle. See ./disclosure — and note it is the SOLE emitter of the
|
|
129
|
+
// event, so the primitive's onOpenChange is deliberately not wired here.
|
|
130
|
+
wireDisclosure(ctx, () => api, () => props.open);
|
|
131
|
+
|
|
132
|
+
// The other three booleans get the same read-back treatment, for the same reason:
|
|
133
|
+
// `<kai-dock unread>` parses to `undefined`, so without this the property would
|
|
134
|
+
// contradict the attribute that set it (findings G-05). Reflecting `unread` syncs
|
|
135
|
+
// the ATTRIBUTE to the prop and never changes the VALUE — the dock still never
|
|
136
|
+
// decides that a message has been read.
|
|
137
|
+
reflectFlag('unread');
|
|
138
|
+
reflectFlag('disabled');
|
|
139
|
+
reflectFlag('defaultOpen');
|
|
140
|
+
reflectFlag('hideClose');
|
|
141
|
+
|
|
142
|
+
// Seed the primitive from EITHER spelling, read once at upgrade.
|
|
143
|
+
//
|
|
144
|
+
// `open` is folded in here rather than left to wireDisclosure's effect on purpose.
|
|
145
|
+
// That effect runs a tick after mount, so `<kai-dock open>` would mount closed and
|
|
146
|
+
// then transition — which the primitive's focus effect would correctly read as a
|
|
147
|
+
// real open and steal focus with, on page load, on someone else's page. Seeding
|
|
148
|
+
// makes mount a non-event for both spellings; wireDisclosure's effect then finds
|
|
149
|
+
// the value already correct and its equality guard makes it a no-op.
|
|
150
|
+
//
|
|
151
|
+
// UNTRACKED, and that is not defensive noise — it is the fix for a real defect this
|
|
152
|
+
// element shipped for about ten minutes. A facade body runs inside the tracking
|
|
153
|
+
// scope that inserts it, so a bare `flag('open')` here subscribes the WHOLE FACADE
|
|
154
|
+
// to `open`: every toggle re-ran it, which re-created the `<Dock>`, its panel div
|
|
155
|
+
// and its controller. The visible symptoms were far away from the cause — focus
|
|
156
|
+
// never moved (each fresh focus effect was re-seeded with the current state, so it
|
|
157
|
+
// saw no transition) and `kai-open-change` never fired (wireDisclosure had latched
|
|
158
|
+
// the first, discarded controller). Reading a prop at the top level of a facade is
|
|
159
|
+
// the general trap; `untrack` is how you say "seed", and it is what makes this a
|
|
160
|
+
// seed rather than a subscription.
|
|
161
|
+
const seed = untrack(() => flag('open') || flag('defaultOpen'));
|
|
162
|
+
|
|
163
|
+
expose({
|
|
164
|
+
/** Move focus to the panel while open, or to the launcher while closed. */
|
|
165
|
+
focus: (options?: FocusOptions) =>
|
|
166
|
+
(api?.open() ? panel : launcher)?.focus(options),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// The `content` focus target: the first element assigned to the panel. Read off the
|
|
170
|
+
// live slots rather than off `element.children`, so it is the ASSIGNMENT that
|
|
171
|
+
// decides — a child whose `slot` attribute changes moves with it, and a comment or
|
|
172
|
+
// whitespace text node is not mistaken for content.
|
|
173
|
+
const contentTarget = () => {
|
|
174
|
+
const slots = panel?.querySelectorAll('slot');
|
|
175
|
+
for (const slot of Array.from(slots ?? [])) {
|
|
176
|
+
const first = (slot as HTMLSlotElement).assignedElements()[0];
|
|
177
|
+
if (first instanceof HTMLElement) return first;
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<Dock
|
|
184
|
+
defaultOpen={seed}
|
|
185
|
+
position={props.position as DockPosition}
|
|
186
|
+
label={props.label as string}
|
|
187
|
+
openLabel={props.openLabel as string | undefined}
|
|
188
|
+
closeLabel={props.closeLabel as string | undefined}
|
|
189
|
+
unread={flag('unread')}
|
|
190
|
+
disabled={flag('disabled')}
|
|
191
|
+
hideClose={flag('hideClose')}
|
|
192
|
+
focusOnOpen={props.focusOnOpen as DockFocusOnOpen}
|
|
193
|
+
contentTarget={contentTarget}
|
|
194
|
+
controllerRef={(a) => (api = a)}
|
|
195
|
+
panelRef={(el) => (panel = el)}
|
|
196
|
+
launcherRef={(el) => (launcher = el)}
|
|
197
|
+
// NATIVE SLOT FALLBACK does the whole icon chain, with no occupancy tracking and
|
|
198
|
+
// no MutationObserver: an unfilled slot renders its fallback content, and a slot
|
|
199
|
+
// nested in another slot's fallback still receives its own assignment. So
|
|
200
|
+
// `launcher-open` wins while open; failing that the consumer's `launcher` glyph
|
|
201
|
+
// STAYS rather than morphing into a built-in ✕ that clashes with their art;
|
|
202
|
+
// failing that the built-in ✕. Both defaults are the primitive's own, imported
|
|
203
|
+
// rather than re-drawn here.
|
|
204
|
+
launcher={<slot name="launcher"><DockLauncherGlyph /></slot>}
|
|
205
|
+
launcherOpen={(
|
|
206
|
+
<slot name="launcher-open">
|
|
207
|
+
<slot name="launcher"><DockCloseGlyph /></slot>
|
|
208
|
+
</slot>
|
|
209
|
+
)}
|
|
210
|
+
>
|
|
211
|
+
<slot name="panel" />
|
|
212
|
+
<slot />
|
|
213
|
+
</Dock>
|
|
214
|
+
);
|
|
215
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Show } from 'solid-js';
|
|
2
|
+
import { MoreHorizontal } from 'lucide-solid';
|
|
3
|
+
import { Dropdown, DropdownTrigger, DropdownContent, type DropdownController } from '../ui/dropdown';
|
|
4
|
+
import { renderIcon } from '../ui/icon';
|
|
5
|
+
import { cn } from '../utils/cn';
|
|
6
|
+
import { defineWebComponent } from './define';
|
|
7
|
+
import { wireDisclosure } from './disclosure';
|
|
8
|
+
|
|
9
|
+
interface Props extends Record<string, unknown> {
|
|
10
|
+
/** Built-in trigger: leading icon (a named icon like `"plus"`, an image
|
|
11
|
+
* URL/data-URI, or text). A slotted `slot="trigger"` overrides it. */
|
|
12
|
+
triggerIcon?: string;
|
|
13
|
+
/** Built-in trigger: a text label. This is the trigger's VISIBLE text, so it is
|
|
14
|
+
* also its accessible name, and `label` does not override it: an accessible
|
|
15
|
+
* name that does not contain the visible text is unreachable by speech input
|
|
16
|
+
* (WCAG 2.5.3, Label in Name). Same rule `kai-menu` follows. */
|
|
17
|
+
triggerLabel?: string;
|
|
18
|
+
/** Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). */
|
|
19
|
+
triggerIconTrailing?: string;
|
|
20
|
+
/** Accessible name for a trigger with no visible label. Ignored when
|
|
21
|
+
* `triggerLabel` is set, which is already the visible name. It DOES name a
|
|
22
|
+
* slotted `slot="trigger"`, which is VISUAL content with the name supplied
|
|
23
|
+
* separately: the same two-slot distinction `kai-menu` documents. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Stretch the trigger to the full width of its container (a block row).
|
|
26
|
+
* Attribute: `full`. */
|
|
27
|
+
full?: boolean;
|
|
28
|
+
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open`
|
|
29
|
+
* attribute, the menu still self-manages on click/keyboard). Set `el.open = true`,
|
|
30
|
+
* or `<kai-dropdown open>`; listen for `kai-open-change`. */
|
|
31
|
+
open?: boolean;
|
|
32
|
+
/** Initial open state on mount (uncontrolled seed). */
|
|
33
|
+
defaultOpen?: boolean;
|
|
34
|
+
/** Disable the trigger: click/keyboard and `show()` no longer open the menu. */
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Events fired by `<kai-dropdown>`. */
|
|
39
|
+
interface Events {
|
|
40
|
+
/** The menu opened or closed (click, keyboard, Escape, outside-click, or a method). */
|
|
41
|
+
'kai-open-change': { open: boolean };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `<kai-dropdown>` — a trigger plus a floating menu surface you fill yourself.
|
|
46
|
+
*
|
|
47
|
+
* The sibling of `<kai-menu>`, split by who owns the menu body: `kai-menu` renders
|
|
48
|
+
* a JSON `items` tree for you, this one gives you the surface and lets you slot
|
|
49
|
+
* ARBITRARY markup into it — the shape a framework consumer needs when the rows
|
|
50
|
+
* are their own components rather than data.
|
|
51
|
+
*
|
|
52
|
+
* ```html
|
|
53
|
+
* <kai-dropdown label="Row actions">
|
|
54
|
+
* <span slot="trigger">⋮</span>
|
|
55
|
+
* <div role="menuitem" tabindex="-1">Rename</div>
|
|
56
|
+
* <div role="menuitem" tabindex="-1">Duplicate</div>
|
|
57
|
+
* </kai-dropdown>
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* The default slot is the menu body; `trigger` is the control it anchors to. It
|
|
61
|
+
* fires `kai-open-change` with `{ open }` on every change, accepts `open` /
|
|
62
|
+
* `defaultOpen` / `disabled`, and exposes `show()` / `hide()` / `toggle()`.
|
|
63
|
+
*
|
|
64
|
+
* KEYBOARD: give each row `role="menuitem"` and it joins roving focus. ArrowUp/Down,
|
|
65
|
+
* Home/End and typeahead reach SLOTTED rows because `menuItems()` in
|
|
66
|
+
* `../ui/dropdown.tsx` walks the flat tree rather than querying the shadow tree,
|
|
67
|
+
* which contains none of the light-DOM nodes a `<slot>` projects. Rows carrying
|
|
68
|
+
* `aria-disabled="true"` are skipped.
|
|
69
|
+
*/
|
|
70
|
+
defineWebComponent<Props, Events>('kai-dropdown', {
|
|
71
|
+
triggerIcon: undefined,
|
|
72
|
+
triggerLabel: undefined,
|
|
73
|
+
triggerIconTrailing: undefined,
|
|
74
|
+
label: undefined,
|
|
75
|
+
full: false,
|
|
76
|
+
open: undefined,
|
|
77
|
+
defaultOpen: undefined,
|
|
78
|
+
disabled: undefined,
|
|
79
|
+
}, (props, ctx) => {
|
|
80
|
+
const { flag } = ctx;
|
|
81
|
+
let api: DropdownController | undefined;
|
|
82
|
+
|
|
83
|
+
// The standard overlay surface: settable+reflecting `open`, kai-open-change,
|
|
84
|
+
// show/hide/toggle, disabled-gating. See ./disclosure.
|
|
85
|
+
wireDisclosure(ctx, () => api, () => props.open);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<>
|
|
89
|
+
{/* The host shrinks to the trigger by default (UA inline); `full` makes it a
|
|
90
|
+
block whose trigger fills the width — the kai-button/kai-menu pattern. */}
|
|
91
|
+
<style>{':host([full]){display:block}'}</style>
|
|
92
|
+
<Dropdown
|
|
93
|
+
defaultOpen={flag('defaultOpen')}
|
|
94
|
+
disabled={flag('disabled')}
|
|
95
|
+
controllerRef={(a) => (api = a)}
|
|
96
|
+
>
|
|
97
|
+
<DropdownTrigger
|
|
98
|
+
class={cn(
|
|
99
|
+
'inline-flex items-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
100
|
+
props.triggerLabel
|
|
101
|
+
? 'gap-1.5 px-2 py-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
102
|
+
: 'justify-center p-1.5 text-foreground hover:bg-muted',
|
|
103
|
+
flag('full') && 'w-full',
|
|
104
|
+
)}
|
|
105
|
+
// A visible `triggerLabel` IS the name, so `label` must not be layered
|
|
106
|
+
// over it: aria-label REPLACES the computed name, locking speech-input
|
|
107
|
+
// users out (WCAG 2.5.3). Same rule kai-menu and kai-button follow.
|
|
108
|
+
aria-label={props.triggerLabel ? undefined : (props.label ?? 'Open menu')}
|
|
109
|
+
>
|
|
110
|
+
<slot name="trigger">
|
|
111
|
+
<Show when={props.triggerIcon}>{renderIcon(props.triggerIcon, { class: 'size-4 shrink-0' })}</Show>
|
|
112
|
+
<Show when={props.triggerLabel}>{props.triggerLabel}</Show>
|
|
113
|
+
<Show when={props.triggerIconTrailing}>{renderIcon(props.triggerIconTrailing, { class: 'size-3.5 shrink-0 opacity-60' })}</Show>
|
|
114
|
+
<Show when={!props.triggerIcon && !props.triggerLabel}><MoreHorizontal class="size-4" /></Show>
|
|
115
|
+
</slot>
|
|
116
|
+
</DropdownTrigger>
|
|
117
|
+
<DropdownContent class="min-w-[10rem]">
|
|
118
|
+
<slot />
|
|
119
|
+
</DropdownContent>
|
|
120
|
+
</Dropdown>
|
|
121
|
+
</>
|
|
122
|
+
);
|
|
123
|
+
});
|
|
@@ -36,6 +36,46 @@ export interface KaiSegmentedOption {
|
|
|
36
36
|
icon?: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** A single row of `<kai-radio-group>`.
|
|
40
|
+
*
|
|
41
|
+
* Distinct from the Solid `RadioOption`, whose `label` and `description` are JSX
|
|
42
|
+
* nodes: on the element both are plain strings, because an element property has to
|
|
43
|
+
* survive being assigned from HTML/React/Vue/Svelte with no framework in between. */
|
|
44
|
+
export interface KaiRadioOption {
|
|
45
|
+
value: string;
|
|
46
|
+
label: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A single row of `<kai-checkbox-group>`.
|
|
52
|
+
*
|
|
53
|
+
* Distinct from the Solid `CheckboxOption`, whose `value` is generic and whose
|
|
54
|
+
* `label` and `description` are JSX nodes: on the element all three are plain
|
|
55
|
+
* strings, because an element property has to survive being assigned from
|
|
56
|
+
* HTML/React/Vue/Svelte with no framework in between. Structurally identical to
|
|
57
|
+
* `KaiRadioOption`, and kept as its own name anyway: the two elements are free to
|
|
58
|
+
* diverge, and a consumer typing a checkbox list should not have to import the radio
|
|
59
|
+
* group's type to do it. */
|
|
60
|
+
export interface KaiCheckboxOption {
|
|
61
|
+
value: string;
|
|
62
|
+
label: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A single choice in `<kai-select>`.
|
|
68
|
+
*
|
|
69
|
+
* Distinct from the Solid `SelectOption`, whose `value` is generic: on the element
|
|
70
|
+
* it is a string, because a native `<option>`'s value is a string and an element
|
|
71
|
+
* property has to survive being assigned from HTML/React/Vue/Svelte with no
|
|
72
|
+
* framework in between. `label` defaults to `value`. */
|
|
73
|
+
export interface KaiSelectOption {
|
|
74
|
+
value: string;
|
|
75
|
+
label?: string;
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
39
79
|
/** A single citation in `<kai-sources>`' `sources` property. Also the shape
|
|
40
80
|
* `<kai-source>` light-DOM children are parsed into. */
|
|
41
81
|
export interface KaiSourceItem {
|