@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
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
For,
|
|
12
12
|
} from 'solid-js';
|
|
13
13
|
import { cn } from '../utils/cn';
|
|
14
|
+
import { useChatConfig, textClass } from '../primitives/chat-config';
|
|
14
15
|
import {
|
|
15
16
|
type ComposerDoc,
|
|
16
17
|
type EntityRef,
|
|
@@ -242,6 +243,7 @@ function setCaretToOffset(root: HTMLElement, offset: number): void {
|
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
export function Composer(props: ComposerProps): JSX.Element {
|
|
246
|
+
const config = useChatConfig();
|
|
245
247
|
let editable!: HTMLDivElement;
|
|
246
248
|
// A SIGNAL (not a plain `let`): usePosition's floating ref must be reactive so
|
|
247
249
|
// Floating UI recomputes once the menu mounts. A non-reactive ref left the menu
|
|
@@ -799,16 +801,78 @@ export function Composer(props: ComposerProps): JSX.Element {
|
|
|
799
801
|
// This is covered by updateTriggerState() in handleInput.
|
|
800
802
|
|
|
801
803
|
const maxH = () => props.maxHeight ?? 240;
|
|
804
|
+
// This element is `contenteditable` with `role="textbox"` — a real tab stop,
|
|
805
|
+
// and the primary one in the kit — so it must never be left with NO focus
|
|
806
|
+
// affordance. WHICH element draws it depends on who owns the frame:
|
|
807
|
+
//
|
|
808
|
+
// - Standalone (the `else` branch below renders the rounded shell): the FRAME
|
|
809
|
+
// owns the ring via `focus-within:ring-*`, exactly like `PromptInput`, so
|
|
810
|
+
// the editable neutralizes its own. The editable is a square, full-bleed
|
|
811
|
+
// box inside a `rounded-xl` shell, so an outline on IT painted a sharp
|
|
812
|
+
// rectangle hugging the text region inside the rounded border — the bug.
|
|
813
|
+
// - Bare: the host owns radius/bg/padding and may draw nothing at all
|
|
814
|
+
// (`PromptInput` passes its own `editableClass` and rings its frame), so
|
|
815
|
+
// the editable keeps `kai-focus-inset` — a ring inside the border box,
|
|
816
|
+
// since an outset one would be clipped by a rounded host shell.
|
|
817
|
+
//
|
|
818
|
+
// A consumer passing `editableClass` still replaces this wholesale — including
|
|
819
|
+
// the `text-start` pin below, so a replacement string owes its own (`PromptInput`,
|
|
820
|
+
// the one in-repo caller, carries it).
|
|
802
821
|
const editableCls = () =>
|
|
803
822
|
props.editableClass ??
|
|
804
|
-
|
|
823
|
+
cn(
|
|
824
|
+
// `text-start` is a PIN, not a style choice: `text-align` inherits, so any
|
|
825
|
+
// centered ancestor (`Empty`'s root did exactly this) reached in and centered
|
|
826
|
+
// the placeholder AND the typed text. An input control's text alignment is a
|
|
827
|
+
// fact about the control, so it states it rather than inheriting it. LOGICAL
|
|
828
|
+
// (`start`), not `text-left` — in RTL the correct edge is the right one, and
|
|
829
|
+
// pinning the physical value would be a worse bug than the one it fixes.
|
|
830
|
+
'text-foreground min-h-[44px] w-full text-start overflow-y-auto whitespace-pre-wrap break-words',
|
|
831
|
+
// Size from the SAME token every other text surface reads (`ChatConfig`'s
|
|
832
|
+
// proseSize, default `sm` = the 14px the rest of the kit sets explicitly) —
|
|
833
|
+
// `Markdown`, `MessageContent` and `PromptInputTextarea` all go through
|
|
834
|
+
// `textClass`. Without it the editable set no size at all and just inherited
|
|
835
|
+
// whatever the shadow root's base was (16px in a stock page), so the composer
|
|
836
|
+
// read a step larger than every component beside it. Going through the token
|
|
837
|
+
// also means `proseSize` moves the composer like it moves `PromptInput`,
|
|
838
|
+
// rather than leaving one input deaf to it.
|
|
839
|
+
textClass(config.proseSize()),
|
|
840
|
+
props.bare
|
|
841
|
+
? 'kai-focus-inset'
|
|
842
|
+
: 'outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
|
|
843
|
+
);
|
|
805
844
|
|
|
806
845
|
const inner = (
|
|
807
846
|
<>
|
|
808
847
|
{/* Static style for CSS Custom Highlight API decoration.
|
|
809
848
|
No-op in browsers that don't support ::highlight(); the selector
|
|
810
849
|
is simply unrecognized and dropped. */}
|
|
811
|
-
<style>{
|
|
850
|
+
<style>{`
|
|
851
|
+
/* MARKED text, not selected text. Reads --color-highlight (overridable via
|
|
852
|
+
--kai-color-highlight), a warm <mark> yellow. It used to be a 22% tint of
|
|
853
|
+
--color-primary with a #6366f1 indigo fallback — but the kit's
|
|
854
|
+
--color-primary is a NEUTRAL (near-black in light, near-white in dark),
|
|
855
|
+
so the indigo the fallback implied never appeared and the highlight
|
|
856
|
+
rendered plain grey in both themes.
|
|
857
|
+
|
|
858
|
+
NO \`prefers-color-scheme\` here, deliberately — the same trap that put
|
|
859
|
+
the pill hues below at 2.33:1. The OS scheme and the kit's RESOLVED
|
|
860
|
+
theme are different questions, and \`theme="light"\` on a dark-OS machine
|
|
861
|
+
is an ordinary configuration; a scheme-keyed value would paint the dark
|
|
862
|
+
amber on the light field. The pills answer this by scoping to \`.dark\`,
|
|
863
|
+
which is NOT available here: \`::highlight()\` names a document-registered
|
|
864
|
+
highlight, so there is no wrapper to hang a descendant selector on.
|
|
865
|
+
|
|
866
|
+
It does not need one. \`--color-highlight\` reaches this pseudo through
|
|
867
|
+
highlight inheritance from the ORIGINATING element — the text inside the
|
|
868
|
+
\`.dark\` wrapper — so the token already answers the resolved theme,
|
|
869
|
+
verified by rendering the dark amber under \`theme="dark"\` with only the
|
|
870
|
+
LIGHT literal below as a fallback. The literal is therefore reachable
|
|
871
|
+
only when no kit stylesheet is loaded at all, and it is deliberately the
|
|
872
|
+
light one: an unstyled page is a white page, and guessing from the OS is
|
|
873
|
+
exactly the failure above. */
|
|
874
|
+
::highlight(${highlightName}) { background-color: var(--color-highlight, hsl(45 96% 78%)); }
|
|
875
|
+
`}</style>
|
|
812
876
|
{/* Atomic entity pill styling. Self-contained (currentColor-based) so it
|
|
813
877
|
renders correctly without depending on a Tailwind rebuild for the
|
|
814
878
|
custom .kai-composer-pill class. */}
|
|
@@ -839,12 +903,24 @@ export function Composer(props: ComposerProps): JSX.Element {
|
|
|
839
903
|
.kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #7c3aed); }
|
|
840
904
|
/* Brighter hues on dark so they don't muddy against a dark field. The
|
|
841
905
|
consumer's --kai-pill-* override still wins (it's the var value); only
|
|
842
|
-
the FALLBACK shifts per scheme.
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
906
|
+
the FALLBACK shifts per scheme.
|
|
907
|
+
|
|
908
|
+
Keyed on the kit's RESOLVED theme (the \`.dark\` wrapper an element
|
|
909
|
+
facade puts inside its shadow root, and the \`.dark\` class the docs /
|
|
910
|
+
Storybook put on <html>) — NOT \`prefers-color-scheme\`. Those are
|
|
911
|
+
different questions: \`theme="light"\` on a machine whose OS is dark is
|
|
912
|
+
an ordinary, common configuration, and the media query answered the OS
|
|
913
|
+
while the surface under the pill answered the attribute. That painted
|
|
914
|
+
the DARK hue (#6ea8fe) on the LIGHT field: 2.33:1, under both the
|
|
915
|
+
4.5:1 text floor (SC 1.4.3) and even 3:1. Matched pairs looked fine,
|
|
916
|
+
which is why it survived — never scheme-match a colour whose backdrop
|
|
917
|
+
is chosen by the theme attribute. */
|
|
918
|
+
.dark .kai-composer-pill[data-kind="skill"] { color: var(--kai-pill-skill, #6ea8fe); }
|
|
919
|
+
.dark .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #c4a7fc); }
|
|
920
|
+
/* Dimmer than the label, but the sigil is what makes a pill read as
|
|
921
|
+
\`/skill\` vs \`@agent\`, so it is a meaningful glyph and owes 3:1
|
|
922
|
+
(SC 1.4.11). At 0.6 the light-mode composite was 2.52:1. */
|
|
923
|
+
.kai-composer-pill-sigil { opacity: 0.8; font-weight: 600; }
|
|
848
924
|
/* Plugins (and any other/unknown kind): the richer CHIP — background +
|
|
849
925
|
icon — so a composite bundle reads differently at a glance. */
|
|
850
926
|
.kai-composer-pill:not([data-kind="skill"]):not([data-kind="agent"]) {
|
|
@@ -984,7 +1060,12 @@ export function Composer(props: ComposerProps): JSX.Element {
|
|
|
984
1060
|
) : (
|
|
985
1061
|
<div
|
|
986
1062
|
class={cn(
|
|
1063
|
+
// Same frame-owns-the-ring pattern as `PromptInput`: the editable inside
|
|
1064
|
+
// neutralizes its own outline (see `editableCls`), so this rounded shell
|
|
1065
|
+
// draws the focus affordance and the ring follows `rounded-xl` instead of
|
|
1066
|
+
// painting a square box around the inner text region.
|
|
987
1067
|
'kai-composer relative rounded-xl bg-surface p-2',
|
|
1068
|
+
'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-0',
|
|
988
1069
|
props.disabled && 'cursor-not-allowed opacity-60',
|
|
989
1070
|
)}
|
|
990
1071
|
>
|
|
@@ -18,7 +18,7 @@ import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primi
|
|
|
18
18
|
import { useCardResolution } from './use-card-resolution';
|
|
19
19
|
import { emitCardEvent } from '../primitives/card-routing';
|
|
20
20
|
import { useCardHost } from '../primitives/card-host';
|
|
21
|
-
import { AlertTriangle, Check
|
|
21
|
+
import { AlertTriangle, Check } from 'lucide-solid';
|
|
22
22
|
|
|
23
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
24
|
// Types (confirm.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
|
|
@@ -316,14 +316,23 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
|
|
|
316
316
|
>
|
|
317
317
|
<div class="flex w-full flex-wrap items-center justify-between gap-2">
|
|
318
318
|
<Show when={local.data?.dismissible === true}>
|
|
319
|
+
{/* The contract `dismiss` verb — a footer ACTION (it emits
|
|
320
|
+
`{kind:'dismiss'}` and collapses the card to a re-openable
|
|
321
|
+
stub), not the chrome close in `Card`'s own `dismissible`
|
|
322
|
+
prop, which the contract cards deliberately never set.
|
|
323
|
+
Labelled rather than a bare ghost ✕: every other control in
|
|
324
|
+
this row says what it does, and an unlabelled 28px glyph
|
|
325
|
+
300px from the primary button reads as stray chrome. Same
|
|
326
|
+
shape in all four cards — `form` already looked like this.
|
|
327
|
+
`aria-label` is kept (redundant with the text) so the
|
|
328
|
+
element tests that select on it keep working. */}
|
|
319
329
|
<Button
|
|
320
330
|
type="button"
|
|
321
331
|
variant="ghost"
|
|
322
|
-
size="icon-sm"
|
|
323
332
|
aria-label="Dismiss"
|
|
324
333
|
onClick={onDismiss}
|
|
325
334
|
>
|
|
326
|
-
|
|
335
|
+
Dismiss
|
|
327
336
|
</Button>
|
|
328
337
|
</Show>
|
|
329
338
|
<div ref={actionsRef} class="ml-auto flex flex-wrap items-center gap-2">
|
|
@@ -375,7 +384,14 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
|
|
|
375
384
|
<p class="text-sm font-semibold text-foreground">{local.data?.heading}</p>
|
|
376
385
|
</Show>
|
|
377
386
|
<Show when={local.data?.body}>
|
|
378
|
-
|
|
387
|
+
{/* The supporting sentence sits directly under the heading it
|
|
388
|
+
supports, muted — the same contrast step the shell header makes
|
|
389
|
+
between `heading` and `description`. It stays in the BODY (not
|
|
390
|
+
the shell's `description` slot, where `form`'s and `choice`'s
|
|
391
|
+
live) because `data.heading` is a documented IN-BODY heading
|
|
392
|
+
that this sentence has to stay beneath, and because this div is
|
|
393
|
+
the Enter-to-confirm tab stop. */}
|
|
394
|
+
<p class="text-sm text-muted-foreground">{local.data?.body}</p>
|
|
379
395
|
</Show>
|
|
380
396
|
</div>
|
|
381
397
|
</Card>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Show, splitProps, createMemo } from 'solid-js';
|
|
1
|
+
import { Show, splitProps, createMemo, type JSX } from 'solid-js';
|
|
2
2
|
import { MessageSquare } from 'lucide-solid';
|
|
3
3
|
import { cn } from '../utils/cn';
|
|
4
4
|
import type { ConversationSummary } from '../types';
|
|
@@ -31,12 +31,146 @@ export function relativeTimeShort(iso?: string, now: number = Date.now()): strin
|
|
|
31
31
|
return `${Math.floor(hours / 24)}d ago`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The slotted-item shape rendered by `<kai-conversation-item>` — the composed
|
|
36
|
+
* row of the consumer-owned loop. Distinct from the
|
|
37
|
+
* data-mode `ConversationItem` above, which batteries mode keeps rendering
|
|
38
|
+
* unchanged: this one takes REGIONS, not a `ConversationSummary`.
|
|
39
|
+
*
|
|
40
|
+
* Regions map 1:1 to the element's slots: `children` = the default slot (the
|
|
41
|
+
* title), plus `leading`, `meta`, and `menu`. The `menu` region takes the
|
|
42
|
+
* consumer's OWN popover (rename / fork / archive live there); the component
|
|
43
|
+
* provides only the region plus focus and ARIA plumbing, never a declarative
|
|
44
|
+
* actions prop. Activation has two modes: inside `<kai-conversations>` it
|
|
45
|
+
* lives in the CONTAINER (`createConversationItemsController` in
|
|
46
|
+
* conversation-list.tsx — this row renders no handler and the
|
|
47
|
+
* `data-kai-item-menu` marker on the menu region is what the container's
|
|
48
|
+
* activation guard keys off so a click in the consumer's menu never also
|
|
49
|
+
* selects the row); STANDALONE, `onActivate` makes the row body
|
|
50
|
+
* its own tabbable button-role control — click / Enter / Space — with the menu
|
|
51
|
+
* still outside the control as the body's sibling.
|
|
52
|
+
*
|
|
53
|
+
* ARIA contract for direct Solid use: the row renders `role="listitem"` holding
|
|
54
|
+
* a `role="button"` body (`aria-current` marks the active row, the same dialect
|
|
55
|
+
* as the batteries-mode row above) with the menu as the body's tabbable SIBLING
|
|
56
|
+
* (axe `nested-interactive` bans focusable descendants of
|
|
57
|
+
* a control, and `aria-required-children` outlaws non-option content anywhere
|
|
58
|
+
* inside a listbox, which is why the vocabulary is not listbox/option). Render
|
|
59
|
+
* your loop's rows inside a `role="list"` ancestor; element mode has no such
|
|
60
|
+
* burden: `<kai-conversations>`' items region provides it for slotted items.
|
|
61
|
+
*/
|
|
62
|
+
export interface SlottedConversationItemProps {
|
|
63
|
+
/** The row's identity, handed to the container's selection contract. In the
|
|
64
|
+
* element this is the `conversation-id` attribute (host `id` is the fallback). */
|
|
65
|
+
conversationId?: string;
|
|
66
|
+
/** Selected state. Reflected as `aria-current` on the row body and a
|
|
67
|
+
* `data-active` styling hook on the row; the container drives it from its
|
|
68
|
+
* `activeId`. */
|
|
69
|
+
active?: boolean;
|
|
70
|
+
/** Dense single-line row padding. */
|
|
71
|
+
compact?: boolean;
|
|
72
|
+
/** Leading region before the title (an icon or avatar). */
|
|
73
|
+
leading?: JSX.Element;
|
|
74
|
+
/** Meta region under the title (a timestamp or status line). */
|
|
75
|
+
meta?: JSX.Element;
|
|
76
|
+
/** Your own row menu (a popover trigger). Never selects the row. */
|
|
77
|
+
menu?: JSX.Element;
|
|
78
|
+
/** The title (the element's default slot). */
|
|
79
|
+
children?: JSX.Element;
|
|
80
|
+
/** Set when the element HOST carries the row-group semantics: the inner row
|
|
81
|
+
* then renders `role="presentation"` so the accessibility tree sees one
|
|
82
|
+
* group (the host), never two. The `kai-conversation-item` facade sets it;
|
|
83
|
+
* Solid consumers rendering the component directly leave it off and the row
|
|
84
|
+
* itself is the group. */
|
|
85
|
+
hostSemantics?: boolean;
|
|
86
|
+
/** STANDALONE activation: when set, the row body is itself the
|
|
87
|
+
* activation control — `tabindex="0"` on the `role="button"` body, and
|
|
88
|
+
* click / Enter / Space call this. The menu region never triggers it (it is
|
|
89
|
+
* the body's sibling, outside the control). Inside `<kai-conversations>`
|
|
90
|
+
* leave it UNSET: the container's controller owns activation (its delegated
|
|
91
|
+
* click/keydown → `kai-conversation-select`) plus roving tabindex, and a
|
|
92
|
+
* handler here would double-fire. The `kai-conversation-item` facade passes
|
|
93
|
+
* it only when the item is standalone. */
|
|
94
|
+
onActivate?: () => void;
|
|
95
|
+
class?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function SlottedConversationItem(props: SlottedConversationItemProps) {
|
|
99
|
+
const [local] = splitProps(props, ['conversationId', 'active', 'compact', 'leading', 'meta', 'menu', 'children', 'hostSemantics', 'onActivate', 'class']);
|
|
100
|
+
return (
|
|
101
|
+
// The sibling restructure: axe nested-interactive
|
|
102
|
+
// bans focusable descendants of an activation control, so the control role
|
|
103
|
+
// sits on the row BODY and the consumer's menu is its tabbable SIBLING
|
|
104
|
+
// (the nav.tsx TrailingActions precedent). The vocabulary is
|
|
105
|
+
// list/listitem/button + aria-current — NOT listbox/option, because axe
|
|
106
|
+
// aria-required-children lets a listbox subtree own nothing but options
|
|
107
|
+
// (and groups of options), which outlaws a sibling menu ANYWHERE inside
|
|
108
|
+
// it; button + aria-current is also exactly what the batteries-mode
|
|
109
|
+
// ConversationItem row above renders, so the two modes speak one dialect.
|
|
110
|
+
<div
|
|
111
|
+
part="row"
|
|
112
|
+
role={local.hostSemantics ? 'presentation' : 'listitem'}
|
|
113
|
+
data-active={local.active ? '' : undefined}
|
|
114
|
+
data-conversation-id={local.conversationId}
|
|
115
|
+
class={cn(
|
|
116
|
+
'flex w-full items-center gap-2.5 rounded-lg text-left transition-colors',
|
|
117
|
+
local.compact ? 'px-2.5 py-1.5' : 'px-2.5 py-2',
|
|
118
|
+
local.active ? 'bg-muted' : 'hover:bg-muted/50',
|
|
119
|
+
local.class,
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
part="body"
|
|
124
|
+
role="button"
|
|
125
|
+
data-kai-item-body
|
|
126
|
+
aria-current={local.active ? 'true' : 'false'}
|
|
127
|
+
// Standalone activation only: with `onActivate` unset —
|
|
128
|
+
// the inside-a-container case — no tabindex and no handlers render, so
|
|
129
|
+
// the container's delegated activation stays the single path.
|
|
130
|
+
tabindex={local.onActivate ? 0 : undefined}
|
|
131
|
+
onClick={() => local.onActivate?.()}
|
|
132
|
+
onKeyDown={(e: KeyboardEvent) => {
|
|
133
|
+
if (!local.onActivate) return;
|
|
134
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
135
|
+
e.preventDefault(); // Space must not scroll the page
|
|
136
|
+
local.onActivate();
|
|
137
|
+
}
|
|
138
|
+
}}
|
|
139
|
+
class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
140
|
+
>
|
|
141
|
+
<Show when={local.leading}>
|
|
142
|
+
<span part="leading" class="flex shrink-0 items-center text-muted-foreground">{local.leading}</span>
|
|
143
|
+
</Show>
|
|
144
|
+
<div class="min-w-0 flex-1">
|
|
145
|
+
<div part="title" class={cn('truncate text-sm', local.active ? 'font-medium text-foreground' : 'text-foreground/80')}>
|
|
146
|
+
{local.children}
|
|
147
|
+
</div>
|
|
148
|
+
<Show when={local.meta}>
|
|
149
|
+
<div part="meta" class="mt-0.5 truncate text-xs text-muted-foreground">{local.meta}</div>
|
|
150
|
+
</Show>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<Show when={local.menu}>
|
|
154
|
+
<span part="menu" data-kai-item-menu class="ml-auto flex shrink-0 items-center">{local.menu}</span>
|
|
155
|
+
</Show>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
34
160
|
export function ConversationItem(props: ConversationItemProps) {
|
|
35
161
|
const [local] = splitProps(props, ['conversation', 'isActive', 'onSelect', 'compact', 'class']);
|
|
36
162
|
// The trailing text: the consumer's own `trailing` field, else an auto relative
|
|
37
|
-
// time from updatedAt (fallback lastMessageAt).
|
|
38
|
-
// time
|
|
39
|
-
//
|
|
163
|
+
// time from updatedAt (fallback lastMessageAt). Never an internal clock — it is a
|
|
164
|
+
// render-time snapshot.
|
|
165
|
+
//
|
|
166
|
+
// REACTIVITY, and the weaker version of this note is what #224 was filed against:
|
|
167
|
+
// a new `conversations` array reference is NOT sufficient. `ConversationList` renders
|
|
168
|
+
// these rows through a reference-keyed `<For>` that captures `conv` as a VALUE, so a
|
|
169
|
+
// row whose item object is unchanged is never re-invoked and never re-reads anything
|
|
170
|
+
// below. The consumer needs BOTH — a new array (which is what notifies at all) and a
|
|
171
|
+
// new object for the item that changed (which is what this row can see). Adds,
|
|
172
|
+
// removes and reorders are fine on a fresh array alone, since those rows' identities
|
|
173
|
+
// already differ. Pinned by `src/components/reactivity-contract.test.tsx`.
|
|
40
174
|
const trailing = createMemo(
|
|
41
175
|
() => local.conversation.trailing ?? relativeTimeShort(local.conversation.updatedAt ?? local.conversation.lastMessageAt),
|
|
42
176
|
);
|
|
@@ -30,6 +30,175 @@ export function CollapsedRail(props: CollapsedRailProps) {
|
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Read a conversation item's identity the way the container does: the
|
|
34
|
+
* `conversationId` property, else the `conversation-id` attribute, else the
|
|
35
|
+
* host `id`. The property is what a framework loop sets; the attributes are
|
|
36
|
+
* the plain-HTML spellings (host `id` matches the `<kai-conversation>`
|
|
37
|
+
* data-carrier precedent). */
|
|
38
|
+
export function readConversationItemId(el: Element): string {
|
|
39
|
+
const prop = (el as Element & { conversationId?: unknown }).conversationId;
|
|
40
|
+
if (typeof prop === 'string' && prop) return prop;
|
|
41
|
+
return el.getAttribute('conversation-id') ?? el.id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether a `<kai-conversation-item>` is STANDALONE — outside the management
|
|
45
|
+
* of a `<kai-conversations>` container — and therefore activates ITSELF:
|
|
46
|
+
* the facade makes its row body a
|
|
47
|
+
* tabbable button and fires `kai-select` on click / Enter / Space. Derived
|
|
48
|
+
* from the container's own membership rule, not from mere ancestry: item mode
|
|
49
|
+
* queries `:scope > kai-conversation-item` (direct children only), so an item
|
|
50
|
+
* wrapped in another element inside a container is standalone too — the
|
|
51
|
+
* container's controller never stamps or activates it. Inside a container
|
|
52
|
+
* (a direct child), the parent-item contract is the ONLY activation
|
|
53
|
+
* path: the container dispatches `kai-conversation-select` and owns roving
|
|
54
|
+
* tabindex, and the item fires nothing of its own. */
|
|
55
|
+
export function isStandaloneConversationItem(el: Element): boolean {
|
|
56
|
+
return el.parentElement?.localName !== 'kai-conversations';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ConversationItemsControllerOptions {
|
|
60
|
+
/** The current slotted item hosts, in DOM order. */
|
|
61
|
+
getItems: () => HTMLElement[];
|
|
62
|
+
/** The container's active conversation id. */
|
|
63
|
+
getActiveId: () => string | undefined;
|
|
64
|
+
/** Item activation (click / Enter / Space). Surfaces as
|
|
65
|
+
* `kai-conversation-select` on the element. */
|
|
66
|
+
onSelect: (id: string) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ConversationItemsController {
|
|
70
|
+
/** Re-derive the ARIA/roving-tabindex bookkeeping over the current items.
|
|
71
|
+
* Call on slotchange / child mutation and whenever `activeId` changes. */
|
|
72
|
+
sync(): void;
|
|
73
|
+
/** The container's items-region click handler (composed-path aware). */
|
|
74
|
+
handleClick(e: MouseEvent): void;
|
|
75
|
+
/** The container's items-region keydown handler (arrows, Home/End,
|
|
76
|
+
* Enter/Space). */
|
|
77
|
+
handleKeyDown(e: KeyboardEvent): void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The parent-item contract of item mode, as a pure-DOM
|
|
82
|
+
* controller so it is host-agnostic: the `kai-conversations` facade wires it over
|
|
83
|
+
* its slotted `kai-conversation-item` children, and the jsdom contract tests
|
|
84
|
+
* drive it over plain nodes. Solid context cannot cross the element boundary
|
|
85
|
+
* (each facade is its own Solid root), so the channel is DOM traversal by
|
|
86
|
+
* construction:
|
|
87
|
+
*
|
|
88
|
+
* - selection flows container to item — exactly one item's BODY node (the
|
|
89
|
+
* shadow body of a `kai-conversation-item`, else the node itself; see
|
|
90
|
+
* `bodyOf`) is `aria-current="true"`, plus the `active` property on the
|
|
91
|
+
* host for the item's own styling hook;
|
|
92
|
+
* - `role="button"` is ensured on each item's body node (an authored role is
|
|
93
|
+
* left alone);
|
|
94
|
+
* - roving tabindex — exactly one body node `tabindex="0"` (the active
|
|
95
|
+
* item's, else the first's), the rest `-1`, re-derived on every `sync()`;
|
|
96
|
+
* menu content keeps its natural tab order (it is the body's SIBLING, not a
|
|
97
|
+
* descendant);
|
|
98
|
+
* - activation (click / Enter / Space) calls `onSelect` with the item's id, and
|
|
99
|
+
* is SUPPRESSED when the composed path crosses the item's `menu` region
|
|
100
|
+
* (light-DOM `slot="menu"` content or the shadow `data-kai-item-menu`
|
|
101
|
+
* wrapper), so the consumer's own popover never also selects the row;
|
|
102
|
+
* - ArrowUp/ArrowDown/Home/End move focus item-to-item, tabindex following the
|
|
103
|
+
* focused item.
|
|
104
|
+
*/
|
|
105
|
+
export function createConversationItemsController(
|
|
106
|
+
opts: ConversationItemsControllerOptions,
|
|
107
|
+
): ConversationItemsController {
|
|
108
|
+
const itemFromEvent = (e: Event): HTMLElement | undefined => {
|
|
109
|
+
const items = opts.getItems();
|
|
110
|
+
return e.composedPath().find((n): n is HTMLElement => items.includes(n as HTMLElement));
|
|
111
|
+
};
|
|
112
|
+
/** The item's ACTIVATION node — the target of role/aria-current/tabindex/
|
|
113
|
+
* focus. For a `kai-conversation-item` host that is its shadow body (the
|
|
114
|
+
* sibling restructure: the host is the row listitem
|
|
115
|
+
* wrapping the body AND the consumer's tabbable menu, so the control
|
|
116
|
+
* semantics must sit below it). A bare node with no such body is its own
|
|
117
|
+
* control. */
|
|
118
|
+
const bodyOf = (item: HTMLElement): HTMLElement =>
|
|
119
|
+
(item.shadowRoot?.querySelector('[data-kai-item-body]') as HTMLElement | null) ?? item;
|
|
120
|
+
const menuInPath = (e: Event): boolean =>
|
|
121
|
+
e.composedPath().some(
|
|
122
|
+
(n) =>
|
|
123
|
+
n instanceof Element &&
|
|
124
|
+
(n.hasAttribute('data-kai-item-menu') || n.getAttribute('slot') === 'menu'),
|
|
125
|
+
);
|
|
126
|
+
// Write-on-change only. `setAttribute` records a mutation even when the value
|
|
127
|
+
// is identical, and the facade re-syncs from a MutationObserver over these very
|
|
128
|
+
// nodes — unconditional writes would feed the observer forever.
|
|
129
|
+
const setAttr = (el: Element, name: string, value: string) => {
|
|
130
|
+
if (el.getAttribute(name) !== value) el.setAttribute(name, value);
|
|
131
|
+
};
|
|
132
|
+
/** An item whose shadow body has not RENDERED yet must not be stamped: the
|
|
133
|
+
* fallback would write control semantics onto the HOST, they would stick
|
|
134
|
+
* (the item facade defers to an authored role), and axe then sees exactly
|
|
135
|
+
* the role="button"-host-with-focusable-menu shape the restructure removed.
|
|
136
|
+
* Measured in the focus-order probe: the container's first sync can run
|
|
137
|
+
* before the item elements upgrade. A later sync catches them — the
|
|
138
|
+
* facade's own mount mutates host attributes, which re-runs sync through
|
|
139
|
+
* the container's MutationObserver read(). Bare nodes (no dash: the jsdom
|
|
140
|
+
* stand-ins) are always ready. */
|
|
141
|
+
const readyBodies = (items: HTMLElement[]) => {
|
|
142
|
+
const out = new Map<HTMLElement, HTMLElement>();
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
const body = bodyOf(item);
|
|
145
|
+
if (body === item && item.localName.includes('-')) continue;
|
|
146
|
+
out.set(item, body);
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const rove = (items: HTMLElement[], target: HTMLElement | undefined) => {
|
|
152
|
+
for (const [item, body] of readyBodies(items)) setAttr(body, 'tabindex', item === target ? '0' : '-1');
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const sync = () => {
|
|
156
|
+
const items = opts.getItems();
|
|
157
|
+
const activeId = opts.getActiveId();
|
|
158
|
+
const bodies = readyBodies(items);
|
|
159
|
+
let anchor: HTMLElement | undefined;
|
|
160
|
+
for (const [item, body] of bodies) {
|
|
161
|
+
const isActive = activeId !== undefined && readConversationItemId(item) === activeId;
|
|
162
|
+
if (!body.hasAttribute('role')) body.setAttribute('role', 'button');
|
|
163
|
+
setAttr(body, 'aria-current', isActive ? 'true' : 'false');
|
|
164
|
+
const host = item as HTMLElement & { active?: boolean };
|
|
165
|
+
if (host.active !== isActive) host.active = isActive;
|
|
166
|
+
if (isActive) anchor = item;
|
|
167
|
+
}
|
|
168
|
+
rove(items, anchor ?? (bodies.keys().next().value as HTMLElement | undefined));
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
sync,
|
|
173
|
+
handleClick(e) {
|
|
174
|
+
const item = itemFromEvent(e);
|
|
175
|
+
if (!item || menuInPath(e)) return;
|
|
176
|
+
opts.onSelect(readConversationItemId(item));
|
|
177
|
+
},
|
|
178
|
+
handleKeyDown(e) {
|
|
179
|
+
const items = opts.getItems();
|
|
180
|
+
if (items.length === 0) return;
|
|
181
|
+
const item = itemFromEvent(e);
|
|
182
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
183
|
+
if (!item || menuInPath(e)) return;
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
opts.onSelect(readConversationItemId(item));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
let next: HTMLElement | undefined;
|
|
189
|
+
const idx = item ? items.indexOf(item) : items.findIndex((i) => bodyOf(i).getAttribute('tabindex') === '0');
|
|
190
|
+
if (e.key === 'ArrowDown') next = items[Math.min(idx + 1, items.length - 1)];
|
|
191
|
+
else if (e.key === 'ArrowUp') next = items[Math.max(idx - 1, 0)];
|
|
192
|
+
else if (e.key === 'Home') next = items[0];
|
|
193
|
+
else if (e.key === 'End') next = items[items.length - 1];
|
|
194
|
+
if (!next) return;
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
rove(items, next);
|
|
197
|
+
bodyOf(next).focus();
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
33
202
|
export interface ConversationListProps {
|
|
34
203
|
groups: ConversationGroup[];
|
|
35
204
|
conversations: ConversationSummary[];
|
|
@@ -51,6 +220,20 @@ export interface ConversationListProps {
|
|
|
51
220
|
/** Receive the imperative controller once mounted. The `kai-conversations`
|
|
52
221
|
* facade uses it to focus / clear the internal search input. */
|
|
53
222
|
controllerRef?: (controller: ConversationListController) => void;
|
|
223
|
+
/** Item mode: your OWN rows, rendered inside a list region in place of the
|
|
224
|
+
* data rows. When set, the built-in search
|
|
225
|
+
* filter, grouping and empty/no-match states do not apply — the consumer's
|
|
226
|
+
* loop owns them — while the chrome (header, search box, new-chat, footer)
|
|
227
|
+
* still renders and `onSearchChange` still reports queries. The
|
|
228
|
+
* `kai-conversations` facade passes its default `<slot>` here when it detects
|
|
229
|
+
* `kai-conversation-item` children. */
|
|
230
|
+
items?: JSX.Element;
|
|
231
|
+
/** Keydown handler for the item-mode list region (the facade wires
|
|
232
|
+
* `createConversationItemsController.handleKeyDown`). */
|
|
233
|
+
itemsKeyDown?: (e: KeyboardEvent) => void;
|
|
234
|
+
/** Click handler for the item-mode list region (the facade wires
|
|
235
|
+
* `createConversationItemsController.handleClick`). */
|
|
236
|
+
itemsClick?: (e: MouseEvent) => void;
|
|
54
237
|
class?: string;
|
|
55
238
|
}
|
|
56
239
|
|
|
@@ -64,8 +247,10 @@ export interface ConversationListController {
|
|
|
64
247
|
}
|
|
65
248
|
|
|
66
249
|
export function ConversationList(props: ConversationListProps) {
|
|
67
|
-
const [local] = splitProps(props, ['groups', 'conversations', 'activeId', 'onSelect', 'onNewChat', 'onToggleSidebar', 'header', 'footer', 'empty', 'compact', 'onSearchChange', 'controllerRef', 'class']);
|
|
250
|
+
const [local] = splitProps(props, ['groups', 'conversations', 'activeId', 'onSelect', 'onNewChat', 'onToggleSidebar', 'header', 'footer', 'empty', 'compact', 'onSearchChange', 'controllerRef', 'items', 'itemsKeyDown', 'itemsClick', 'class']);
|
|
68
251
|
const [searchQuery, setSearchQuery] = createSignal('');
|
|
252
|
+
// Item mode: the consumer's own rows replace the data rendering wholesale.
|
|
253
|
+
const itemMode = createMemo(() => local.items != null);
|
|
69
254
|
const isEmpty = createMemo(() => local.conversations.length === 0);
|
|
70
255
|
// The search query is owned here; setQuery is the single mutation point so both
|
|
71
256
|
// typing and the imperative clearSearch() notify the facade (→ kai-search).
|
|
@@ -133,32 +318,61 @@ export function ConversationList(props: ConversationListProps) {
|
|
|
133
318
|
>
|
|
134
319
|
{local.header}
|
|
135
320
|
</Show>
|
|
136
|
-
<Show when={!isEmpty()}>
|
|
321
|
+
<Show when={itemMode() || !isEmpty()}>
|
|
137
322
|
<div class="px-3 pb-2">
|
|
138
323
|
<div class="flex items-center gap-2 rounded-md bg-surface-strong px-2.5 py-1.5">
|
|
139
324
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-muted-foreground"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
140
325
|
<input ref={searchInput} type="text" value={searchQuery()} onInput={(e) => setQuery(e.currentTarget.value)} placeholder="Search chats..."
|
|
141
326
|
aria-label="Search chats"
|
|
142
|
-
class="bg-transparent text-
|
|
327
|
+
class="bg-transparent text-compact text-foreground placeholder:text-muted-foreground rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring w-full" />
|
|
143
328
|
</div>
|
|
144
329
|
</div>
|
|
145
330
|
</Show>
|
|
331
|
+
{/* Item mode: the consumer's own rows in a list region. The container's
|
|
332
|
+
filter/grouping/empty states do not apply — the consumer's loop owns
|
|
333
|
+
them. */}
|
|
334
|
+
<Show when={itemMode()}>
|
|
335
|
+
<ScrollArea class="flex-1 px-2">
|
|
336
|
+
<div
|
|
337
|
+
role="list"
|
|
338
|
+
aria-label="Conversations"
|
|
339
|
+
part="items"
|
|
340
|
+
class="space-y-0.5 py-1"
|
|
341
|
+
onKeyDown={(e) => local.itemsKeyDown?.(e)}
|
|
342
|
+
onClick={(e) => local.itemsClick?.(e)}
|
|
343
|
+
>
|
|
344
|
+
{local.items}
|
|
345
|
+
</div>
|
|
346
|
+
</ScrollArea>
|
|
347
|
+
</Show>
|
|
146
348
|
{/* list, or the empty state (replace) when there are no conversations. */}
|
|
147
349
|
<Show
|
|
148
|
-
when={!isEmpty()}
|
|
350
|
+
when={!itemMode() && !isEmpty()}
|
|
149
351
|
fallback={
|
|
150
|
-
<Show
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
352
|
+
<Show when={!itemMode()}>
|
|
353
|
+
<Show
|
|
354
|
+
when={local.empty}
|
|
355
|
+
fallback={
|
|
356
|
+
<div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
|
|
357
|
+
No conversations yet
|
|
358
|
+
</div>
|
|
359
|
+
}
|
|
360
|
+
>
|
|
361
|
+
{local.empty}
|
|
362
|
+
</Show>
|
|
159
363
|
</Show>
|
|
160
364
|
}
|
|
161
365
|
>
|
|
366
|
+
{/* F-04, decide loudly: a query matching nothing renders a VISIBLE
|
|
367
|
+
no-match state, keyed off the FILTERED count — distinct from the
|
|
368
|
+
zero-conversations empty state above, which keys off the unfiltered
|
|
369
|
+
list (and still owns the `empty` override). */}
|
|
370
|
+
<Show when={filteredConversations().length === 0}>
|
|
371
|
+
<div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
|
|
372
|
+
No conversations match your search
|
|
373
|
+
</div>
|
|
374
|
+
</Show>
|
|
375
|
+
<Show when={filteredConversations().length > 0}>
|
|
162
376
|
<ScrollArea class="flex-1 px-2">
|
|
163
377
|
<For each={local.groups}>
|
|
164
378
|
{(group) => {
|
|
@@ -183,6 +397,7 @@ export function ConversationList(props: ConversationListProps) {
|
|
|
183
397
|
</Show>
|
|
184
398
|
</Show>
|
|
185
399
|
</ScrollArea>
|
|
400
|
+
</Show>
|
|
186
401
|
</Show>
|
|
187
402
|
{/* footer (inject): a row below the list (account / settings / …). */}
|
|
188
403
|
<Show when={local.footer}>
|
|
@@ -196,11 +411,11 @@ function GroupSection(props: { name: string; count: number; conversations: Conve
|
|
|
196
411
|
const [open, setOpen] = createSignal(true);
|
|
197
412
|
return (
|
|
198
413
|
<Collapsible open={open()} onOpenChange={setOpen}>
|
|
199
|
-
<CollapsibleTrigger class="flex items-center gap-1.5 w-full px-1.5 py-1 rounded-md text-
|
|
414
|
+
<CollapsibleTrigger class="flex items-center gap-1.5 w-full px-1.5 py-1 rounded-md text-compact text-muted-foreground font-medium hover:bg-muted/30 transition-colors cursor-pointer mt-1.5">
|
|
200
415
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
201
416
|
class={cn('transition-transform', !open() && '-rotate-90')}><polyline points="6 9 12 15 18 9"/></svg>
|
|
202
417
|
<span>{props.name}</span>
|
|
203
|
-
<Badge variant="count" class="ml-auto text-
|
|
418
|
+
<Badge variant="count" class="ml-auto text-caption">{props.count}</Badge>
|
|
204
419
|
</CollapsibleTrigger>
|
|
205
420
|
<CollapsibleContent>
|
|
206
421
|
<div class="pl-2 mt-0.5 space-y-0.5">
|