@kitn.ai/ui 0.28.0 → 0.30.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 +11 -7
- package/dist/{Icon-DObhsRtm.js → Icon-BzBET3E-.js} +1 -1
- package/dist/{action-icons-CcgNN2oT.js → action-icons-DaW2pU6m.js} +1 -1
- package/dist/agent-tooling/construct/public.d.ts +15 -0
- package/dist/agent-tooling/construct/schema.d.ts +111 -0
- package/dist/arrow-down-D783iMJj.js +1 -0
- package/dist/arrow-left-0VLM5kyg.js +1 -0
- package/dist/artifact-DGjzHYUT.js +1 -0
- package/dist/{attachments-Qr4kg-2b.js → attachments-YvXHlg88.js} +1 -1
- package/dist/{audio-visualizer-DToYdfSc.js → audio-visualizer-9lz5IHsL.js} +1 -1
- package/dist/{badge-CdEu_jn2.js → badge-Dh_vp8A5.js} +1 -1
- package/dist/{bell-DQAAOpDz.js → bell-CT7_WUwS.js} +1 -1
- package/dist/{button-DkuOnGLy.js → button-C_3te6fm.js} +1 -1
- package/dist/{card-renderer-Jx70m6_l.js → card-renderer-DZVaGH2e.js} +1 -1
- package/dist/check-CiEOVnEN.js +1 -0
- package/dist/{checkbox-D7iRB3nL.js → checkbox-Bu2T2e0U.js} +1 -1
- package/dist/{checkbox-group-B2_rpoPM.js → checkbox-group-BiryEXPQ.js} +1 -1
- package/dist/chevron-down-CSobuZRG.js +1 -0
- package/dist/chevron-right-DR9ZAP-2.js +1 -0
- package/dist/{choice-card-m0BYqqOK.js → choice-card-CM6IjkmL.js} +1 -1
- package/dist/circle-BC66EYF7.js +1 -0
- package/dist/circle-check-BqmahW0O.js +1 -0
- package/dist/{circle-x-BPG0iPst.js → circle-x-Bk-dA5Wf.js} +1 -1
- package/dist/{code-block-ClMLKb7C.js → code-block-B26t-Q7q.js} +1 -1
- package/dist/{collapsible-B4DlWicL.js → collapsible-Bhc636Cf.js} +1 -1
- package/dist/components/builder-composer-triggers.d.ts +62 -0
- package/dist/components/builder-layout.d.ts +54 -0
- package/dist/components/builder-message-actions.d.ts +72 -0
- package/dist/components/builder-panel.d.ts +189 -0
- package/dist/components/builder-preview.d.ts +11 -0
- package/dist/components/builder-shell-controls.d.ts +69 -0
- package/dist/components/builder-skeleton.d.ts +58 -0
- package/dist/components/builder-start.d.ts +75 -0
- package/dist/components/builder-workspace-variants.d.ts +23 -0
- package/dist/components/captions.d.ts +66 -0
- package/dist/{composer-sQ0HcvyG.js → composer-DMI85B6j.js} +1 -1
- package/dist/{confirm-card-rQWDQsnC.js → confirm-card-B0YIAwXp.js} +1 -1
- package/dist/construct-cli.es.js +3 -3
- package/dist/construct.d.ts +5 -0
- package/dist/construct.js +327 -0
- package/dist/{context-0kg4ZX1J.js → context-DWgfhqWp.js} +1 -1
- package/dist/controllable-BagRpyHv.js +1 -0
- package/dist/{conversation-item-BxwQLaFJ.js → conversation-item-BsOsVGmT.js} +1 -1
- package/dist/{conversation-list-BCfGN1JU.js → conversation-list-aX3Vtc7L.js} +1 -1
- package/dist/{copy-n9u9rqpi.js → copy-BmEwsNhJ.js} +1 -1
- package/dist/core-C8fzo39E.js +1 -12
- package/dist/{create-tween-uPkArjIG.js → create-tween-CNOAU3Fx.js} +1 -1
- package/dist/{create-tween-C-q-JCM-.js → create-tween-WfqiENls.js} +1 -1
- package/dist/{create-tween-CRrJXxBd.js → create-tween-wlFtW9Kh.js} +1 -1
- package/dist/{create-tween-DZ3RRDHD.js → create-tween-zt79ba32.js} +1 -1
- package/dist/{default-input-CaMrJkgH.js → default-input-DMV5Eaze.js} +1 -1
- package/dist/define-CI0y3eE4.js +1 -0
- package/dist/define.js +40 -40
- package/dist/define.server.js +45 -45
- package/dist/{disclosure-WUImBbDv.js → disclosure-NnACBxsC.js} +1 -1
- package/dist/{dropdown-wZVeJOMd.js → dropdown-DT7G0RGR.js} +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/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.js +1 -1
- package/dist/elements/checkbox.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.js +1 -1
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- 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.js +1 -1
- 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.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +1 -1
- 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.js +1 -1
- 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.js +1 -1
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/{ellipsis-CbOpJnU3.js → ellipsis-6pJ7SxYT.js} +1 -1
- package/dist/{embed-CrfjkbUG.js → embed-BMje-nxj.js} +1 -1
- package/dist/engine-javascript-C1x7zo1_.js +1 -141
- package/dist/{external-link-ByXAwBBQ.js → external-link-Beh9DKeR.js} +1 -1
- package/dist/{file-text-DMib2qwt.js → file-text-D1GPfZ8Q.js} +1 -1
- package/dist/{file-tree-CJqM2aP7.js → file-tree-Dwj2lqg-.js} +1 -1
- package/dist/{folder-_BAb929C.js → folder-b6D8x1DY.js} +1 -1
- package/dist/{form-DSZgZQZk.js → form-DWgdxDXu.js} +1 -1
- package/dist/{hover-card-BQbul9eq.js → hover-card-CWRkqm40.js} +1 -1
- package/dist/{icon-CdcfRhYI.js → icon-CL0jdhsf.js} +1 -1
- package/dist/index.js +1741 -1725
- package/dist/index.server.js +1582 -1566
- package/dist/{info-DsFjGuI_.js → info-0cOUherQ.js} +1 -1
- package/dist/{input-DKbmkMos.js → input-BH6Ru91Q.js} +1 -1
- package/dist/kai.es.js +1 -1
- package/dist/{kbd-PvdGhX4s.js → kbd-DRAnVtNX.js} +1 -1
- package/dist/{link-zF8PzWfF.js → link-Dlr0vssB.js} +1 -1
- package/dist/{link-preview-DbM1866N.js → link-preview-DODgRyV9.js} +1 -1
- package/dist/loader-KvJB6NrT.js +1 -0
- package/dist/{markdown-LMvmAyBk.js → markdown-DX9tuJVj.js} +1 -1
- package/dist/mcp.es.js +9 -5
- package/dist/{message-BYZECyek.js → message-BqQ7Ot5q.js} +1 -1
- package/dist/{message-BSsgvHvf.js → message-DkaGfBd8.js} +1 -1
- package/dist/message-circle-BPpp5QKv.js +1 -0
- package/dist/message-square-BLbsd9BJ.js +1 -0
- package/dist/{minimize-2-BQRoBV1V.js → minimize-2-CyOHJwcN.js} +1 -1
- package/dist/{model-switcher-CMpCOYcB.js → model-switcher-0hrKDiK2.js} +1 -1
- package/dist/{overlay-Cb-MkyDa.js → overlay-DBMFXie6.js} +1 -1
- package/dist/{panel-right-i8warFxT.js → panel-right-t8SWJL6e.js} +1 -1
- package/dist/{paperclip-ByY3UVO6.js → paperclip-CdzuOjbU.js} +1 -1
- package/dist/play-BXy1u7rW.js +1 -0
- package/dist/primitives/use-auto-resize.d.ts +11 -0
- package/dist/{progress-bar-jVAHvMUf.js → progress-bar-CiBtxMWT.js} +1 -1
- package/dist/{prompt-suggestion-CD-qtNgC.js → prompt-suggestion-I_DHP21i.js} +1 -1
- package/dist/{radio-BsoBMhkF.js → radio-C_p3n39l.js} +1 -1
- package/dist/{reasoning-4kBPtlTx.js → reasoning-BDwy_tIe.js} +1 -1
- package/dist/register-impl-DvEnhIxx.js +424 -0
- package/dist/{resizable-uPmY9VY-.js → resizable-SYC-fStM.js} +1 -1
- package/dist/{rotate-ccw-B2xn93py.js → rotate-ccw-B1nwmwuY.js} +1 -1
- package/dist/{rotate-cw-CMVLoCyH.js → rotate-cw-CLAnBcm1.js} +1 -1
- package/dist/{scroll-area-BqLUo8ZZ.js → scroll-area-zRyXoLhI.js} +1 -1
- package/dist/{scroll-button-DslXiJyL.js → scroll-button-jzrbYCG9.js} +1 -1
- package/dist/{select-2cLRtPGT.js → select-DM-0nEe0.js} +1 -1
- package/dist/{separator-BWQtNJcM.js → separator-BToOOoOt.js} +1 -1
- package/dist/{settings-BBR63jLg.js → settings-PaGo_S4U.js} +1 -1
- package/dist/{settings-group-B2ciy-Iw.js → settings-group-BFXGIfXq.js} +1 -1
- package/dist/{skeleton-CkHI-HgC.js → skeleton-DYpmFnnm.js} +1 -1
- package/dist/{slider-DDR9U-i0.js → slider-ebutAmkQ.js} +1 -1
- package/dist/{solid-DruVOXnf.js → solid-15v_3WVL.js} +3563 -3412
- package/dist/{solid-BzCFdL25.js → solid-5P-otgdi.js} +4502 -4296
- package/dist/solid.d.ts +6 -0
- package/dist/solid.js +259 -254
- package/dist/solid.server.js +259 -254
- package/dist/{source-GEnFF528.js → source-c1gpRl9h.js} +1 -1
- package/dist/{star-CilHq8GP.js → star-pcV_3LqF.js} +1 -1
- package/dist/{store-Ciyk6eZG.js → store-CSgbFFCg.js} +1 -1
- package/dist/{switch-KkeQIbuH.js → switch-Bh2OdLV2.js} +1 -1
- package/dist/{tasks-card-BrN4bggJ.js → tasks-card-CbqQAn2S.js} +1 -1
- package/dist/text-shimmer-BSzgEnDH.js +1 -0
- package/dist/textarea-DrYNt_0z.js +1 -0
- package/dist/theme.tokens.css +22 -21
- package/dist/{thumbs-up-CsT9Wzb9.js → thumbs-up-BL3-NenK.js} +1 -1
- package/dist/{toast-store-tgK6MuMv.js → toast-store-DkOlYczt.js} +1 -1
- package/dist/{tool-DzUjIBT8.js → tool-CeIWUVUe.js} +1 -1
- package/dist/{tooltip-Bkkg_4t0.js → tooltip-GA-nmg06.js} +1 -1
- package/dist/{trash-2-jBKxjGGQ.js → trash-2-CT66QWlm.js} +1 -1
- package/dist/{triangle-alert-DlQ68ppf.js → triangle-alert-CeHLWxhD.js} +1 -1
- package/dist/ui/color-field.d.ts +48 -0
- package/dist/ui/textarea.d.ts +4 -0
- package/dist/ui/toggle-chip.d.ts +40 -0
- package/dist/{upload-TcAHXv9W.js → upload-9Csb0F0J.js} +1 -1
- package/dist/{use-card-resolution-DaZqAdWf.js → use-card-resolution-Zg0a08Z-.js} +1 -1
- package/dist/{variant-aurora-DbW_OWL2.js → variant-aurora-4VBQHIKV.js} +1 -1
- package/dist/{variant-aurora-CNmfHhcx.js → variant-aurora-DBeCAQNk.js} +2 -2
- package/dist/{variant-aurora-D6GsdTPA.js → variant-aurora-FzHVXHby.js} +2 -2
- package/dist/{variant-aurora-BhLuYxJR.js → variant-aurora-LE3fqxgw.js} +1 -1
- package/dist/{variant-custom-BvYHR62b.js → variant-custom-AFRGcOBS.js} +1 -1
- package/dist/{variant-custom-DV2x254s.js → variant-custom-BQkNuPpE.js} +2 -2
- package/dist/{variant-custom-BRvaNHSi.js → variant-custom-DI5R0rln.js} +1 -1
- package/dist/{variant-custom-DMTrEO5w.js → variant-custom-WWnrrRHS.js} +2 -2
- package/dist/{variant-wave-CyBueXj9.js → variant-wave-DUCbH5oI.js} +2 -2
- package/dist/{variant-wave-BNMGrDfj.js → variant-wave-DuQV7DhQ.js} +2 -2
- package/dist/{variant-wave-BpDd8t3Z.js → variant-wave-nSI2sfII.js} +1 -1
- package/dist/{variant-wave-sp-qwsdI.js → variant-wave-wTP5v_jU.js} +1 -1
- package/dist/x-eiO9V6J6.js +1 -0
- package/{src/elements/styles.css → kit-base.css} +27 -44
- package/package.json +32 -17
- package/solid.css +33 -0
- package/theme.css +39 -23
- package/bin/route.test.js +0 -58
- package/dist/arrow-down-CjRU3JgV.js +0 -1
- package/dist/arrow-left-Bnpb1UEW.js +0 -1
- package/dist/artifact-Bwt7OgcR.js +0 -1
- package/dist/check-njkVdZu2.js +0 -1
- package/dist/chevron-down-B72s6PXR.js +0 -1
- package/dist/chevron-right-BatuHzUm.js +0 -1
- package/dist/circle-check-B62pODB3.js +0 -1
- package/dist/circle-zx0ByzY9.js +0 -1
- package/dist/controllable-CnGpMF6r.js +0 -1
- package/dist/define-By-uL588.js +0 -1
- package/dist/loader-BM4CEz4G.js +0 -1
- package/dist/message-circle-DruwObfX.js +0 -1
- package/dist/message-square-BG6q6mlm.js +0 -1
- package/dist/play-BvKKq3fX.js +0 -1
- package/dist/register-impl-B1hLkrI8.js +0 -424
- package/dist/text-shimmer-Dcj1MHuq.js +0 -1
- package/dist/textarea-Dpvbngbg.js +0 -1
- package/dist/x-Z5UntkIJ.js +0 -1
- package/src/agent-tooling/README.md +0 -192
- package/src/agent-tooling/archetypes.ts +0 -205
- package/src/agent-tooling/catalog/README.md +0 -676
- package/src/agent-tooling/catalog/catalog-types.ts +0 -231
- package/src/agent-tooling/catalog/fabrications.ts +0 -96
- package/src/agent-tooling/catalog/invariants.ts +0 -284
- package/src/agent-tooling/catalog/labs-titles.ts +0 -114
- package/src/agent-tooling/catalog/scenarios.ts +0 -87
- package/src/agent-tooling/catalog/surfaces.ts +0 -347
- package/src/agent-tooling/construct/cli-entry.ts +0 -8
- package/src/agent-tooling/construct/cli.ts +0 -147
- package/src/agent-tooling/construct/codegen.ts +0 -1635
- package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
- package/src/agent-tooling/construct/dev.ts +0 -148
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
- package/src/agent-tooling/construct/schema.ts +0 -452
- package/src/agent-tooling/integrations/anthropic.ts +0 -442
- package/src/agent-tooling/integrations/cloudflare.ts +0 -218
- package/src/agent-tooling/integrations/langgraph.ts +0 -133
- package/src/agent-tooling/integrations/mastra.ts +0 -182
- package/src/agent-tooling/integrations/mock.ts +0 -148
- package/src/agent-tooling/integrations/ollama.ts +0 -97
- package/src/agent-tooling/integrations/openai.ts +0 -95
- package/src/agent-tooling/integrations/openrouter.ts +0 -82
- package/src/agent-tooling/integrations/pi.ts +0 -176
- package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
- package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
- package/src/agent-tooling/mcp/manifest.ts +0 -422
- package/src/agent-tooling/mcp/server.ts +0 -116
- package/src/agent-tooling/mcp/stdio.ts +0 -18
- package/src/agent-tooling/mcp/tools/construct.ts +0 -130
- package/src/agent-tooling/mcp/tools/debug.ts +0 -621
- package/src/agent-tooling/mcp/tools/reference.ts +0 -980
- package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
- package/src/agent-tooling/mcp/tools/theme.ts +0 -456
- package/src/agent-tooling/mcp/tools/types.ts +0 -14
- package/src/agent-tooling/mcp/validate-args.ts +0 -141
- package/src/agent-tooling/recipes/composed-thread.ts +0 -714
- package/src/agent-tooling/recipes/index.ts +0 -22
- package/src/agent-tooling/recipes/types.ts +0 -32
- package/src/agent-tooling/registry.ts +0 -132
- package/src/agent-tooling/route-emit.ts +0 -305
- package/src/agent-tooling/types.ts +0 -390
- package/src/components/artifact-card.tsx +0 -120
- package/src/components/artifact.tsx +0 -914
- package/src/components/attachment-types.ts +0 -66
- package/src/components/attachments.tsx +0 -536
- package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
- package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
- package/src/components/audio-visualizer/fit-scale.ts +0 -112
- package/src/components/audio-visualizer/index.tsx +0 -528
- package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
- package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
- package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
- package/src/components/audio-visualizer/sizes.ts +0 -54
- package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
- package/src/components/audio-visualizer/variant-bar.tsx +0 -175
- package/src/components/audio-visualizer/variant-custom.tsx +0 -344
- package/src/components/audio-visualizer/variant-grid.tsx +0 -175
- package/src/components/audio-visualizer/variant-radial.tsx +0 -163
- package/src/components/audio-visualizer/variant-wave.tsx +0 -130
- package/src/components/audio-visualizer/wave.glsl.ts +0 -130
- package/src/components/card-fallback.tsx +0 -60
- package/src/components/card-renderer.tsx +0 -220
- package/src/components/card.tsx +0 -212
- package/src/components/chain-of-thought.tsx +0 -348
- package/src/components/chat-container.tsx +0 -89
- package/src/components/chat-scope-picker.tsx +0 -57
- package/src/components/chat-thread.tsx +0 -1014
- package/src/components/checkpoint.tsx +0 -88
- package/src/components/choice-card.tsx +0 -743
- package/src/components/coachmark.tsx +0 -249
- package/src/components/code-block.tsx +0 -181
- package/src/components/composer-dom.ts +0 -159
- package/src/components/composer-highlight.ts +0 -242
- package/src/components/composer-history.ts +0 -82
- package/src/components/composer.tsx +0 -1075
- package/src/components/confirm-card.tsx +0 -402
- package/src/components/context.tsx +0 -377
- package/src/components/conversation-item.tsx +0 -241
- package/src/components/conversation-list.tsx +0 -429
- package/src/components/conversation-panel.tsx +0 -118
- package/src/components/dismissed-stub.tsx +0 -80
- package/src/components/embed.tsx +0 -200
- package/src/components/empty.tsx +0 -177
- package/src/components/feedback-bar.tsx +0 -166
- package/src/components/file-tree.tsx +0 -530
- package/src/components/file-upload.tsx +0 -167
- package/src/components/form-widgets.tsx +0 -525
- package/src/components/form.tsx +0 -1296
- package/src/components/home-panel.tsx +0 -159
- package/src/components/image.tsx +0 -65
- package/src/components/link-preview.tsx +0 -195
- package/src/components/loader.tsx +0 -367
- package/src/components/markdown.tsx +0 -162
- package/src/components/message-skills.tsx +0 -40
- package/src/components/message.tsx +0 -765
- package/src/components/model-switcher.tsx +0 -132
- package/src/components/prompt-input.tsx +0 -188
- package/src/components/prompt-suggestion.tsx +0 -150
- package/src/components/reasoning.tsx +0 -293
- package/src/components/response-compare-types.ts +0 -152
- package/src/components/response-compare.tsx +0 -455
- package/src/components/response-stream.tsx +0 -109
- package/src/components/screen.tsx +0 -181
- package/src/components/scroll-button.tsx +0 -93
- package/src/components/source.tsx +0 -192
- package/src/components/tasks-card.tsx +0 -801
- package/src/components/text-shimmer.tsx +0 -37
- package/src/components/thinking-bar.tsx +0 -50
- package/src/components/thread.tsx +0 -228
- package/src/components/toast.tsx +0 -503
- package/src/components/tool-classify.ts +0 -26
- package/src/components/tool-types.ts +0 -49
- package/src/components/tool.tsx +0 -190
- package/src/components/use-card-resolution.ts +0 -44
- package/src/components/voice-input.tsx +0 -222
- package/src/components/voice-output.tsx +0 -242
- package/src/components/widget-tab-bar.tsx +0 -73
- package/src/components/workspace-shell.tsx +0 -285
- package/src/diagnostics/hook.ts +0 -352
- package/src/diagnostics/index.ts +0 -82
- package/src/diagnostics/report-request.ts +0 -296
- package/src/elements/agent-card.tsx +0 -67
- package/src/elements/artifact.tsx +0 -194
- package/src/elements/attachments.tsx +0 -140
- package/src/elements/audio-visualizer.tsx +0 -227
- package/src/elements/autoloader.ts +0 -89
- package/src/elements/avatar.tsx +0 -33
- package/src/elements/badge.tsx +0 -32
- package/src/elements/button.tsx +0 -149
- package/src/elements/card.tsx +0 -133
- package/src/elements/cards.tsx +0 -266
- package/src/elements/chain-of-thought.tsx +0 -141
- package/src/elements/chat-scope-picker.tsx +0 -65
- package/src/elements/chat-types.ts +0 -102
- package/src/elements/chat-workspace.tsx +0 -188
- package/src/elements/chat.tsx +0 -304
- package/src/elements/checkbox-group.tsx +0 -164
- package/src/elements/checkbox.tsx +0 -134
- package/src/elements/checkpoint.tsx +0 -47
- package/src/elements/choice.tsx +0 -100
- package/src/elements/coachmark.tsx +0 -90
- package/src/elements/code-block.tsx +0 -60
- package/src/elements/command.tsx +0 -197
- package/src/elements/compare.tsx +0 -111
- package/src/elements/compiled.css +0 -2
- package/src/elements/composer.tsx +0 -162
- package/src/elements/confirm-card.tsx +0 -76
- package/src/elements/context-meter.tsx +0 -107
- package/src/elements/conversation-item.tsx +0 -120
- package/src/elements/conversation-list.tsx +0 -203
- package/src/elements/css.ts +0 -5
- package/src/elements/default-input.tsx +0 -392
- package/src/elements/define-entry.ts +0 -12
- package/src/elements/define.tsx +0 -563
- package/src/elements/diagnostic-events.ts +0 -114
- package/src/elements/dialog.tsx +0 -145
- package/src/elements/disclosure.ts +0 -95
- package/src/elements/dock.tsx +0 -215
- package/src/elements/dropdown.tsx +0 -123
- package/src/elements/editable-label.tsx +0 -140
- package/src/elements/element-data-types.ts +0 -152
- package/src/elements/element-diagnostics.ts +0 -392
- package/src/elements/element-manifest.json +0 -354
- package/src/elements/element-nonscalar.json +0 -187
- package/src/elements/element-types.d.ts +0 -4353
- package/src/elements/embed.tsx +0 -35
- package/src/elements/empty.tsx +0 -29
- package/src/elements/feedback-bar.tsx +0 -66
- package/src/elements/file-tree.tsx +0 -62
- package/src/elements/file-upload.tsx +0 -44
- package/src/elements/form.tsx +0 -94
- package/src/elements/hover-card.tsx +0 -80
- package/src/elements/icon.tsx +0 -41
- package/src/elements/image.tsx +0 -32
- package/src/elements/input.tsx +0 -492
- package/src/elements/kbd.tsx +0 -45
- package/src/elements/link-preview.tsx +0 -34
- package/src/elements/loader.tsx +0 -25
- package/src/elements/markdown.tsx +0 -38
- package/src/elements/menu.tsx +0 -241
- package/src/elements/message-skills.tsx +0 -83
- package/src/elements/message.tsx +0 -361
- package/src/elements/model-switcher.tsx +0 -121
- package/src/elements/nav.tsx +0 -87
- package/src/elements/notice.tsx +0 -53
- package/src/elements/pane-grid.tsx +0 -111
- package/src/elements/pane-group.tsx +0 -119
- package/src/elements/pane.tsx +0 -129
- package/src/elements/popover.tsx +0 -81
- package/src/elements/progress-bar.tsx +0 -39
- package/src/elements/prompt-dock.tsx +0 -82
- package/src/elements/prompt-input.tsx +0 -240
- package/src/elements/prompt-suggestions.tsx +0 -113
- package/src/elements/radio-group.tsx +0 -129
- package/src/elements/reasoning.tsx +0 -71
- package/src/elements/register-impl.ts +0 -148
- package/src/elements/register.ts +0 -76
- package/src/elements/remote.tsx +0 -225
- package/src/elements/resizable.globals.d.ts +0 -36
- package/src/elements/resizable.tsx +0 -704
- package/src/elements/response-stream.tsx +0 -40
- package/src/elements/screen.tsx +0 -90
- package/src/elements/scroll-area.tsx +0 -33
- package/src/elements/scroll-button.tsx +0 -179
- package/src/elements/search.tsx +0 -185
- package/src/elements/segmented.tsx +0 -129
- package/src/elements/select.tsx +0 -165
- package/src/elements/separator.tsx +0 -36
- package/src/elements/setting-item.tsx +0 -50
- package/src/elements/settings-group.tsx +0 -37
- package/src/elements/skeleton.tsx +0 -45
- package/src/elements/slider.tsx +0 -171
- package/src/elements/slot-text.ts +0 -73
- package/src/elements/slots.ts +0 -787
- package/src/elements/source.tsx +0 -126
- package/src/elements/status.tsx +0 -42
- package/src/elements/switch.tsx +0 -118
- package/src/elements/tabs.tsx +0 -95
- package/src/elements/tasks.tsx +0 -103
- package/src/elements/text-shimmer.tsx +0 -28
- package/src/elements/thinking-bar.tsx +0 -43
- package/src/elements/thread.tsx +0 -139
- package/src/elements/toast.tsx +0 -106
- package/src/elements/tool.tsx +0 -78
- package/src/elements/tooltip.tsx +0 -85
- package/src/elements/validate-messages.ts +0 -76
- package/src/elements/voice-input.tsx +0 -100
- package/src/elements/voice-output.tsx +0 -82
- package/src/index.ts +0 -430
- package/src/primitives/audio-bands.ts +0 -174
- package/src/primitives/card-component-types.ts +0 -63
- package/src/primitives/card-contract.ts +0 -80
- package/src/primitives/card-data-types.ts +0 -361
- package/src/primitives/card-host.tsx +0 -37
- package/src/primitives/card-recovery.ts +0 -160
- package/src/primitives/card-registry.tsx +0 -78
- package/src/primitives/card-resolution.ts +0 -36
- package/src/primitives/card-routing.ts +0 -85
- package/src/primitives/card-schemas/artifact.schema.json +0 -92
- package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
- package/src/primitives/card-schemas/card-event.schema.json +0 -12
- package/src/primitives/card-schemas/choice.schema.json +0 -75
- package/src/primitives/card-schemas/confirm.schema.json +0 -65
- package/src/primitives/card-schemas/embed.schema.json +0 -63
- package/src/primitives/card-schemas/form.result.schema.json +0 -7
- package/src/primitives/card-schemas/form.schema.json +0 -53
- package/src/primitives/card-schemas/link.schema.json +0 -54
- package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
- package/src/primitives/card-schemas/tasks.schema.json +0 -83
- package/src/primitives/card-tags.ts +0 -61
- package/src/primitives/card-validate-cards.ts +0 -255
- package/src/primitives/card-validate-schemas.ts +0 -58
- package/src/primitives/card-validate.ts +0 -131
- package/src/primitives/chat-config.tsx +0 -103
- package/src/primitives/composer-model.ts +0 -49
- package/src/primitives/composer-triggers.ts +0 -35
- package/src/primitives/controllable.ts +0 -21
- package/src/primitives/conversation-store.ts +0 -248
- package/src/primitives/create-kai-chat.ts +0 -66
- package/src/primitives/create-tween.ts +0 -266
- package/src/primitives/embed-providers.ts +0 -254
- package/src/primitives/field-mask.ts +0 -256
- package/src/primitives/field-semantics.ts +0 -115
- package/src/primitives/highlighter.ts +0 -157
- package/src/primitives/input-mask.ts +0 -853
- package/src/primitives/link-preview.ts +0 -87
- package/src/primitives/message-feedback.ts +0 -124
- package/src/primitives/pdf-preview.ts +0 -121
- package/src/primitives/toast-store.ts +0 -338
- package/src/primitives/url-scheme-policy.ts +0 -70
- package/src/primitives/use-audio-analysis.ts +0 -340
- package/src/primitives/use-auto-resize.ts +0 -31
- package/src/primitives/use-resize-observer.ts +0 -45
- package/src/primitives/use-sequencer.ts +0 -72
- package/src/primitives/use-speech-recognition.ts +0 -146
- package/src/primitives/use-stick-to-bottom.ts +0 -75
- package/src/primitives/use-text-stream.ts +0 -143
- package/src/primitives/use-voice-recorder.ts +0 -62
- package/src/primitives/visualizer-sequences.ts +0 -350
- package/src/remote/host-embed.ts +0 -345
- package/src/remote/index.ts +0 -2
- package/src/remote/origin.ts +0 -30
- package/src/remote/provider-runtime.ts +0 -262
- package/src/remote/provider.ts +0 -2
- package/src/remote/validate.ts +0 -22
- package/src/remote/version.ts +0 -12
- package/src/remote/wire.ts +0 -48
- package/src/schemas/from-tool-call.ts +0 -151
- package/src/schemas/index.ts +0 -341
- package/src/schemas/provider-subsets.ts +0 -538
- package/src/schemas/registry.ts +0 -355
- package/src/schemas/tool-defs.ts +0 -793
- package/src/solid.ts +0 -173
- package/src/state/index.ts +0 -44
- package/src/state/messages.ts +0 -53
- package/src/state/mock.ts +0 -297
- package/src/state/parts.ts +0 -295
- package/src/state/persistence.ts +0 -180
- package/src/state/stream.ts +0 -246
- package/src/state/suggestions.ts +0 -9
- package/src/state/threads.ts +0 -142
- package/src/test-utils/fake-clock.ts +0 -88
- package/src/types.ts +0 -95
- package/src/ui/action-icons.ts +0 -53
- package/src/ui/agent-card.tsx +0 -218
- package/src/ui/avatar.tsx +0 -23
- package/src/ui/badge.tsx +0 -21
- package/src/ui/button.tsx +0 -49
- package/src/ui/card.tsx +0 -245
- package/src/ui/checkbox-group.tsx +0 -153
- package/src/ui/checkbox.tsx +0 -63
- package/src/ui/collapsible.tsx +0 -163
- package/src/ui/command.tsx +0 -103
- package/src/ui/dialog.tsx +0 -223
- package/src/ui/dock.tsx +0 -630
- package/src/ui/dropdown.tsx +0 -636
- package/src/ui/editable-label.tsx +0 -112
- package/src/ui/hover-card.tsx +0 -345
- package/src/ui/icon.tsx +0 -167
- package/src/ui/input.tsx +0 -391
- package/src/ui/kbd.tsx +0 -118
- package/src/ui/nav.tsx +0 -282
- package/src/ui/notice.tsx +0 -84
- package/src/ui/overlay.tsx +0 -277
- package/src/ui/pane-grid.tsx +0 -117
- package/src/ui/pane-group.tsx +0 -291
- package/src/ui/pane.tsx +0 -193
- package/src/ui/popover.tsx +0 -106
- package/src/ui/progress-bar.tsx +0 -83
- package/src/ui/prompt-dock.tsx +0 -145
- package/src/ui/radio.tsx +0 -150
- package/src/ui/resizable.tsx +0 -693
- package/src/ui/scroll-area.tsx +0 -26
- package/src/ui/segmented.tsx +0 -109
- package/src/ui/select.tsx +0 -168
- package/src/ui/separator.tsx +0 -10
- package/src/ui/settings-group.tsx +0 -67
- package/src/ui/skeleton.tsx +0 -73
- package/src/ui/slider.tsx +0 -178
- package/src/ui/status.tsx +0 -48
- package/src/ui/switch.tsx +0 -119
- package/src/ui/tabs.tsx +0 -168
- package/src/ui/textarea.tsx +0 -21
- package/src/ui/tooltip.tsx +0 -118
- package/src/utils/cn.ts +0 -30
- package/src/wire/chunk.ts +0 -248
- package/src/wire/consume.ts +0 -599
- package/src/wire/diagnostics.ts +0 -727
- package/src/wire/encode-probe.ts +0 -214
- package/src/wire/encode.ts +0 -947
- package/src/wire/files.ts +0 -342
- package/src/wire/formats/anthropic.ts +0 -274
- package/src/wire/formats/openai.ts +0 -228
- package/src/wire/index.ts +0 -95
- package/src/wire/media-types.ts +0 -344
- package/src/wire/read.ts +0 -396
- package/src/wire/sink-helpers.ts +0 -50
- package/src/wire/sse.ts +0 -153
package/src/ui/dock.tsx
DELETED
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createEffect, createSignal, createUniqueId, Show, untrack, type Accessor, type JSX,
|
|
3
|
-
} from 'solid-js';
|
|
4
|
-
import { MessageCircle, X } from 'lucide-solid';
|
|
5
|
-
|
|
6
|
-
/** Which corner the dock sits in. LOGICAL, so `-end` follows the writing direction
|
|
7
|
-
* and an RTL page docks on the left with no extra work (both reference
|
|
8
|
-
* implementations reached for `inset-inline-end` independently). */
|
|
9
|
-
export type DockPosition = 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start';
|
|
10
|
-
|
|
11
|
-
/** Where focus goes when the panel opens.
|
|
12
|
-
* - `content` (default) — `focus()` the first element assigned to the panel, falling
|
|
13
|
-
* back to the panel itself when that element cannot take focus. Content-AGNOSTIC:
|
|
14
|
-
* the dock never learns what it is holding.
|
|
15
|
-
* - `panel` — focus the `tabindex="-1"` panel container (what `kai-dialog` does).
|
|
16
|
-
* - `none` — never move focus. */
|
|
17
|
-
export type DockFocusOnOpen = 'content' | 'panel' | 'none';
|
|
18
|
-
|
|
19
|
-
/** Imperative open controller, handed to a parent (the `kai-dock` facade) via
|
|
20
|
-
* `controllerRef` so it can drive/observe open state with `wireDisclosure`. */
|
|
21
|
-
export interface DockController { open: Accessor<boolean>; setOpen: (v: boolean) => void }
|
|
22
|
-
|
|
23
|
-
export interface DockProps {
|
|
24
|
-
/** The panel body. ANY content — the dock never reads or types it. */
|
|
25
|
-
children?: JSX.Element;
|
|
26
|
-
/** Content inside the built-in button while CLOSED. Defaults to a chat glyph. */
|
|
27
|
-
launcher?: JSX.Element;
|
|
28
|
-
/** Content inside the button while OPEN. Defaults to a ✕; when only `launcher` is
|
|
29
|
-
* given, that content stays rather than morphing into a clashing built-in. */
|
|
30
|
-
launcherOpen?: JSX.Element;
|
|
31
|
-
/** The widget's NAME. Derives the panel's accessible name and both launcher names
|
|
32
|
-
* (`Open ${label}` / `Close ${label}`) — one string instead of three. */
|
|
33
|
-
label?: string;
|
|
34
|
-
/** i18n override for the launcher's name while closed. */
|
|
35
|
-
openLabel?: string;
|
|
36
|
-
/** i18n override for the launcher's name while open. */
|
|
37
|
-
closeLabel?: string;
|
|
38
|
-
/** Which corner. Logical, RTL-correct. Defaults to `bottom-end`. */
|
|
39
|
-
position?: DockPosition;
|
|
40
|
-
/** Render the unread dot. CONSUMER-OWNED: shown only while closed, and never
|
|
41
|
-
* written back by the dock (see the component doc). */
|
|
42
|
-
unread?: boolean;
|
|
43
|
-
/** Disable the launcher. */
|
|
44
|
-
disabled?: boolean;
|
|
45
|
-
/** Suppress the dock's own built-in mobile `[part="close"]` X. Off (rendered) by
|
|
46
|
-
* default — the built-in X is the FALLBACK close route for panel content with no
|
|
47
|
-
* header of its own, which is the general case the dock has to cover since it
|
|
48
|
-
* never reads what it holds. Set `true` when the slotted/composed content
|
|
49
|
-
* supplies its own close affordance in its own header row (e.g. `ChatThread`'s
|
|
50
|
-
* `headerEndContent`) — with both present the two X's stack, one floating over
|
|
51
|
-
* the other's row, which is exactly the "doesn't look intentional" feedback this
|
|
52
|
-
* prop exists to let a caller avoid. TRADEOFF: the mobile panel reserves a
|
|
53
|
-
* padding band above its content so the built-in X never paints over it; that
|
|
54
|
-
* band stays reserved unless you set `hideClose` true, so only set it once your
|
|
55
|
-
* own control is actually in place — otherwise you get the band with no X to
|
|
56
|
-
* justify it. */
|
|
57
|
-
hideClose?: boolean;
|
|
58
|
-
/** Where focus lands on open. Defaults to `content`. */
|
|
59
|
-
focusOnOpen?: DockFocusOnOpen;
|
|
60
|
-
/** Controlled open state. When set, the component never changes it itself — drive it
|
|
61
|
-
* from `onOpenChange`. Omit for uncontrolled (internal) state. */
|
|
62
|
-
open?: boolean;
|
|
63
|
-
/** Initial open state when uncontrolled. Does NOT move focus (see the focus contract). */
|
|
64
|
-
defaultOpen?: boolean;
|
|
65
|
-
/** Fires whenever the dock wants to open or close (launcher / Escape / a method). */
|
|
66
|
-
onOpenChange?: (open: boolean) => void;
|
|
67
|
-
/** Receive the open controller once mounted. */
|
|
68
|
-
controllerRef?: (api: DockController) => void;
|
|
69
|
-
/** Receive the focusable panel node so a facade's `focus()` can target it. */
|
|
70
|
-
panelRef?: (el: HTMLElement) => void;
|
|
71
|
-
/** Receive the launcher button so a facade's `focus()` can target it. */
|
|
72
|
-
launcherRef?: (el: HTMLButtonElement) => void;
|
|
73
|
-
/**
|
|
74
|
-
* Resolve the element `focusOnOpen: 'content'` should focus.
|
|
75
|
-
*
|
|
76
|
-
* A PROP, because the answer is "the first element assigned to the panel slot" and
|
|
77
|
-
* only the web-component facade can see slot assignments — a Solid caller passes
|
|
78
|
-
* its own ref instead. Returning nothing falls back to focusing the panel.
|
|
79
|
-
*/
|
|
80
|
-
contentTarget?: () => HTMLElement | null | undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** The built-in closed glyph. Exported so the facade can use it as its `launcher`
|
|
84
|
-
* slot's FALLBACK content — one definition, two users, rather than a second copy
|
|
85
|
-
* of the same icon choice living in the element layer. */
|
|
86
|
-
export function DockLauncherGlyph(): JSX.Element {
|
|
87
|
-
return <MessageCircle size={24} />;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** The built-in open glyph. Exported for the same reason as {@link DockLauncherGlyph}. */
|
|
91
|
-
export function DockCloseGlyph(): JSX.Element {
|
|
92
|
-
return <X size={24} />;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface DockLauncherImageProps {
|
|
96
|
-
/** The icon URL. */
|
|
97
|
-
src: string;
|
|
98
|
-
/** Alt text — usually left empty: the launcher BUTTON already carries the
|
|
99
|
-
* accessible name (Dock derives it from `label`/`openLabel`/`closeLabel`),
|
|
100
|
-
* so a second name on the icon inside it would be redundant, not missing. */
|
|
101
|
-
alt?: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* A branded launcher icon with a built-in fallback to {@link DockLauncherGlyph}
|
|
106
|
-
* on load failure — a dead link, or a placeholder URL nobody swapped for a
|
|
107
|
-
* real asset yet (exactly what shipped in `kai dev`'s own `owner-widget`
|
|
108
|
-
* fixture: `launcherIcon: "https://example.com/logo.png"`, which never
|
|
109
|
-
* resolves, so the FAB rendered a permanently broken image). Decides loudly
|
|
110
|
-
* with one `console.warn` naming the failing URL, then swaps — never a
|
|
111
|
-
* silent broken-image icon sitting in the corner of someone's page.
|
|
112
|
-
*
|
|
113
|
-
* Lives here, not on {@link Dock} itself: `Dock`'s `launcher` prop is
|
|
114
|
-
* deliberately ANY content (an emoji, a "Support" text pill, this image,
|
|
115
|
-
* whatever a consumer slots in) and the dock never inspects what it's
|
|
116
|
-
* holding — teaching it to specifically understand "this might be an
|
|
117
|
-
* `<img>` that can 404" would be the one prop where that boundary breaks.
|
|
118
|
-
* This is a plain sibling component a `launcher` value can be built FROM,
|
|
119
|
-
* same relationship `DockLauncherGlyph`/`DockCloseGlyph` already have to
|
|
120
|
-
* `Dock`. The construct engine's `emitDockLauncher` (codegen.ts) renders
|
|
121
|
-
* `<DockLauncherImage src={...} />` for a construct-authored `launcherIcon`
|
|
122
|
-
* rather than hand-rolling the same onError dance in emitted output.
|
|
123
|
-
*/
|
|
124
|
-
export function DockLauncherImage(props: DockLauncherImageProps) {
|
|
125
|
-
const [failed, setFailed] = createSignal(false);
|
|
126
|
-
return (
|
|
127
|
-
<Show when={!failed()} fallback={<DockLauncherGlyph />}>
|
|
128
|
-
<img
|
|
129
|
-
src={props.src}
|
|
130
|
-
alt={props.alt ?? ''}
|
|
131
|
-
style={{ width: '24px', height: '24px', 'border-radius': '9999px' }}
|
|
132
|
-
onError={() => {
|
|
133
|
-
console.warn(`[kai] Dock launcher icon failed to load (${props.src}); falling back to the default glyph.`);
|
|
134
|
-
setFailed(true);
|
|
135
|
-
}}
|
|
136
|
-
/>
|
|
137
|
-
</Show>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* The dock's own stylesheet, scoped to its shadow root.
|
|
143
|
-
*
|
|
144
|
-
* IN A `<style>` RATHER THAN INLINE, and that is a decision. `kai-prompt-dock` puts
|
|
145
|
-
* its tokenized chrome in an inline `style={{}}`, which resolves `var()` fallbacks
|
|
146
|
-
* and lets an outside override win — but an inline declaration also beats every rule
|
|
147
|
-
* in the cascade, and this element ships TWO rules that have to be able to win: the
|
|
148
|
-
* narrow-viewport full-bleed default and the reduced-motion rule. Both are media
|
|
149
|
-
* queries, which cannot be expressed inline at all. So the geometry lives here and
|
|
150
|
-
* only the open/closed state (visibility / opacity / pointer-events) is inline, where
|
|
151
|
-
* it belongs: that one must beat any consumer rule, and it is also the half a jsdom
|
|
152
|
-
* test can actually read.
|
|
153
|
-
*
|
|
154
|
-
* EVERY RULE IS SCOPED UNDER `[data-kai-dock]`, including the `[part=...]` ones. Inside
|
|
155
|
-
* a shadow root that scoping is redundant; in a plain Solid page it is the difference
|
|
156
|
-
* between styling this dock and restyling every `[part="panel"]` on the document.
|
|
157
|
-
*
|
|
158
|
-
* DOCUMENTED CONSTRAINT, which the element cannot fix from inside: put `<kai-dock>`
|
|
159
|
-
* as a body-level child. A `transform` / `filter` / `contain` ancestor makes
|
|
160
|
-
* `position: fixed` resolve against that ancestor instead of the viewport.
|
|
161
|
-
*/
|
|
162
|
-
const DOCK_CSS = `
|
|
163
|
-
:host { display: contents; }
|
|
164
|
-
|
|
165
|
-
[data-kai-dock] {
|
|
166
|
-
position: fixed;
|
|
167
|
-
z-index: var(--kai-dock-z, 1000);
|
|
168
|
-
display: flex;
|
|
169
|
-
gap: var(--kai-dock-gap, 0.85rem);
|
|
170
|
-
/* Only a positioning frame: it must not swallow clicks on the page it floats
|
|
171
|
-
over. The launcher and the open panel opt back in. */
|
|
172
|
-
pointer-events: none;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/* Logical insets throughout, so an RTL page docks on the other side for free.
|
|
176
|
-
column-reverse for the bottom corners keeps the LAUNCHER FIRST IN DOM ORDER --
|
|
177
|
-
Tab reaches the control before the panel content -- while painting it below. */
|
|
178
|
-
[data-kai-dock][data-position^="bottom"] {
|
|
179
|
-
flex-direction: column-reverse;
|
|
180
|
-
inset-block-end: var(--kai-dock-inset, 1.5rem);
|
|
181
|
-
}
|
|
182
|
-
[data-kai-dock][data-position^="top"] {
|
|
183
|
-
flex-direction: column;
|
|
184
|
-
inset-block-start: var(--kai-dock-inset, 1.5rem);
|
|
185
|
-
}
|
|
186
|
-
[data-kai-dock][data-position$="end"] {
|
|
187
|
-
inset-inline-end: var(--kai-dock-inset, 1.5rem);
|
|
188
|
-
align-items: flex-end;
|
|
189
|
-
}
|
|
190
|
-
[data-kai-dock][data-position$="start"] {
|
|
191
|
-
inset-inline-start: var(--kai-dock-inset, 1.5rem);
|
|
192
|
-
align-items: flex-start;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
[data-kai-dock] [part="launcher"] {
|
|
196
|
-
pointer-events: auto;
|
|
197
|
-
position: relative;
|
|
198
|
-
/* ABOVE the panel, always. At <=480px the panel goes fixed/inset:0 and it comes
|
|
199
|
-
after the launcher in DOM order, so at z-index:auto it painted OVER the launcher
|
|
200
|
-
and a touch user lost every pointer route to closing. Both reference
|
|
201
|
-
implementations stacked the launcher over the panel for the same reason. The
|
|
202
|
-
launcher itself is now HIDDEN while the panel is open at that width (see the
|
|
203
|
-
<=480px block below) in favour of the in-panel [part="close"] X, but this
|
|
204
|
-
z-index still matters for the moment between "open" landing and the panel's
|
|
205
|
-
own transition finishing, and for any consumer CSS that reintroduces the
|
|
206
|
-
launcher there. */
|
|
207
|
-
z-index: 1;
|
|
208
|
-
flex: none;
|
|
209
|
-
display: inline-flex;
|
|
210
|
-
align-items: center;
|
|
211
|
-
justify-content: center;
|
|
212
|
-
gap: 0.5rem;
|
|
213
|
-
/* The size token is the BLOCK size and the inline MINIMUM, not a fixed square. An
|
|
214
|
-
icon-only launcher is still a perfect disc (the icon plus padding is narrower
|
|
215
|
-
than the minimum), but a slotted TEXT label — the reference app's "Support"
|
|
216
|
-
pill — widens the button instead of overflowing it. */
|
|
217
|
-
inline-size: auto;
|
|
218
|
-
min-inline-size: var(--kai-dock-launcher-size, 56px);
|
|
219
|
-
block-size: var(--kai-dock-launcher-size, 56px);
|
|
220
|
-
padding-block: 0;
|
|
221
|
-
padding-inline: 0.9rem;
|
|
222
|
-
white-space: nowrap;
|
|
223
|
-
border: 0;
|
|
224
|
-
border-radius: 9999px;
|
|
225
|
-
background: var(--color-primary);
|
|
226
|
-
color: var(--color-primary-foreground);
|
|
227
|
-
box-shadow: 0 10px 25px -6px rgb(0 0 0 / 0.35);
|
|
228
|
-
cursor: pointer;
|
|
229
|
-
transition: transform 150ms ease, box-shadow 150ms ease;
|
|
230
|
-
}
|
|
231
|
-
[data-kai-dock] [part="launcher"]:hover:not(:disabled) { transform: translateY(-1px); }
|
|
232
|
-
[data-kai-dock] [part="launcher"]:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
|
|
233
|
-
[data-kai-dock] [part="launcher"]:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
234
|
-
|
|
235
|
-
/* The mobile-only close affordance rendered INSIDE the panel. Hidden by default and
|
|
236
|
-
only switched on inside the <=480px media block below, alongside the rule that
|
|
237
|
-
hides the launcher while the panel is open at that width — same query drives both,
|
|
238
|
-
CSS-only, no JS viewport logic. Absolutely positioned against the panel, which is
|
|
239
|
-
the panel's own containing block once the narrow-viewport rule below sets it to
|
|
240
|
-
position: fixed.
|
|
241
|
-
|
|
242
|
-
The inset is a CONSUMER-OVERRIDABLE TOKEN, same idiom as every other geometry
|
|
243
|
-
value in this file (--kai-dock-width and friends). The dock is content-agnostic —
|
|
244
|
-
it never reads what is slotted into the panel — so it cannot know whether that
|
|
245
|
-
content renders its own trailing controls (a ChatThread header-end slot with
|
|
246
|
-
share/settings icons, say) in the same top-right corner.
|
|
247
|
-
|
|
248
|
-
The mobile block below reserves a BAND of the panel's own padding above whatever
|
|
249
|
-
is slotted, sized to this button's own footprint, so nothing paints under this X
|
|
250
|
-
by default -- verified fix (round 1) against a real ChatThread header-end slot
|
|
251
|
-
colliding with it at 375px. Owner feedback on the real widget (round 2): for a
|
|
252
|
-
construct with only a title in the header, that band read as a dead empty strip
|
|
253
|
-
with the X floating alone in it -- "that doesn't look like the empty component
|
|
254
|
-
either, lets do better." The actual fix composes instead of reserving space:
|
|
255
|
-
ChatThread now has its own headerEndContent escape hatch (see its prop doc) so a
|
|
256
|
-
caller puts the close control INSIDE the header row, sharing it with the title --
|
|
257
|
-
zero collision by construction, no band needed. hideClose on this component (see
|
|
258
|
-
its prop doc) then suppresses this built-in X for exactly that case, and the
|
|
259
|
-
band above is scoped to :not([data-hide-close]) so it comes off ONLY there --
|
|
260
|
-
every other consumer (anyone who hasn't opted into hideClose, including a
|
|
261
|
-
hand-authored dock with real header-end content) keeps the band, same as round 1
|
|
262
|
-
shipped, so the collision it fixed stays fixed for them. What remains here for
|
|
263
|
-
that default case is a plain absolute overlay, top-right of the panel, with
|
|
264
|
-
these consumer-overridable inset tokens for the rare case it should deliberately
|
|
265
|
-
sit in front of something (e.g. a single small badge). */
|
|
266
|
-
[data-kai-dock] [part="close"] {
|
|
267
|
-
display: none;
|
|
268
|
-
position: absolute;
|
|
269
|
-
inset-block-start: var(--kai-dock-close-inset-block, 0.75rem);
|
|
270
|
-
inset-inline-end: var(--kai-dock-close-inset-inline, 0.75rem);
|
|
271
|
-
z-index: 1;
|
|
272
|
-
align-items: center;
|
|
273
|
-
justify-content: center;
|
|
274
|
-
inline-size: 2.25rem;
|
|
275
|
-
block-size: 2.25rem;
|
|
276
|
-
padding: 0;
|
|
277
|
-
border: 0;
|
|
278
|
-
border-radius: 9999px;
|
|
279
|
-
background: var(--color-background);
|
|
280
|
-
color: var(--color-foreground);
|
|
281
|
-
box-shadow: 0 2px 8px -2px rgb(0 0 0 / 0.3);
|
|
282
|
-
cursor: pointer;
|
|
283
|
-
}
|
|
284
|
-
[data-kai-dock] [part="close"]:hover { background: var(--color-muted, var(--color-background)); }
|
|
285
|
-
[data-kai-dock] [part="close"]:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
|
|
286
|
-
|
|
287
|
-
[data-kai-dock] [part="badge"] {
|
|
288
|
-
position: absolute;
|
|
289
|
-
inset-block-start: 3px;
|
|
290
|
-
inset-inline-end: 3px;
|
|
291
|
-
inline-size: 0.75rem;
|
|
292
|
-
block-size: 0.75rem;
|
|
293
|
-
border-radius: 9999px;
|
|
294
|
-
background: var(--color-unread);
|
|
295
|
-
box-shadow: 0 0 0 2px var(--color-primary);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
[data-kai-dock] [part="panel"] {
|
|
299
|
-
pointer-events: auto;
|
|
300
|
-
display: flex;
|
|
301
|
-
flex-direction: column;
|
|
302
|
-
overflow: hidden;
|
|
303
|
-
inline-size: var(--kai-dock-width, 380px);
|
|
304
|
-
block-size: var(--kai-dock-height, 600px);
|
|
305
|
-
max-inline-size: calc(100vw - 2 * var(--kai-dock-inset, 1.5rem));
|
|
306
|
-
max-block-size: calc(
|
|
307
|
-
100dvh - 2 * var(--kai-dock-inset, 1.5rem)
|
|
308
|
-
- var(--kai-dock-launcher-size, 56px) - var(--kai-dock-gap, 0.85rem)
|
|
309
|
-
);
|
|
310
|
-
border: 1px solid var(--color-border);
|
|
311
|
-
border-radius: var(--kai-dock-radius, 16px);
|
|
312
|
-
background: var(--color-background);
|
|
313
|
-
color: var(--color-foreground);
|
|
314
|
-
box-shadow: 0 20px 45px -12px rgb(0 0 0 / 0.35);
|
|
315
|
-
/* VISIBILITY IS SWITCHED, NEVER ANIMATED, and the zero duration is the whole point.
|
|
316
|
-
visibility 180ms here meant the COMPUTED value stayed hidden for about two
|
|
317
|
-
frames after opening while the inline style already said visible — and a
|
|
318
|
-
focus() into a hidden subtree is dropped silently, so focusOnOpen never landed
|
|
319
|
-
in a real browser (it worked under reduced motion, which is what made it look like
|
|
320
|
-
a focus bug rather than a paint one).
|
|
321
|
-
This rule is the CLOSING direction: hold visibility for the length of the fade so
|
|
322
|
-
the panel does not vanish mid-transition, then switch. The opening direction sets
|
|
323
|
-
the delay to 0s below, so opening is synchronous and focus has somewhere to go. */
|
|
324
|
-
transition: opacity 180ms ease, transform 180ms ease, visibility 0s linear 180ms;
|
|
325
|
-
}
|
|
326
|
-
[data-kai-dock] [part="panel"][data-expanded] {
|
|
327
|
-
transition: opacity 180ms ease, transform 180ms ease, visibility 0s linear 0s;
|
|
328
|
-
}
|
|
329
|
-
/* The panel is a flex column and the slotted surface fills it -- the one layout
|
|
330
|
-
fact both reference implementations wrote by hand (flex:1; min-height:0). Both
|
|
331
|
-
spellings are here on purpose: ::slotted covers the shadow-DOM facade, > *
|
|
332
|
-
covers a plain Solid caller, and each is inert in the other's world (a <slot>
|
|
333
|
-
is display:contents, so flex properties do nothing to it). */
|
|
334
|
-
[data-kai-dock] [part="panel"] ::slotted(*) { flex: 1 1 auto; min-block-size: 0; }
|
|
335
|
-
[data-kai-dock] [part="panel"] > * { flex: 1 1 auto; min-block-size: 0; }
|
|
336
|
-
[data-kai-dock] [part="panel"]:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
|
|
337
|
-
[data-kai-dock][data-position^="bottom"] [part="panel"][data-closed] { transform: translateY(0.5rem); }
|
|
338
|
-
[data-kai-dock][data-position^="top"] [part="panel"][data-closed] { transform: translateY(-0.5rem); }
|
|
339
|
-
|
|
340
|
-
/* Narrow viewports take the panel full-bleed. A SHIPPED DEFAULT, not a prop: both
|
|
341
|
-
references wrote this rule independently, which makes it a HOW. While the panel is
|
|
342
|
-
OPEN at this width the launcher hides and the panel's own [part="close"] X becomes
|
|
343
|
-
the pointer close route instead — a FAB floating over a full-bleed panel read as
|
|
344
|
-
wrong (owner feedback) and Escape alone strands a touch user with no visible
|
|
345
|
-
affordance at all. Closed, the launcher is unaffected: it is the panel's own
|
|
346
|
-
[data-expanded] state that gates both rules below, not the media query alone. */
|
|
347
|
-
@media (max-width: 480px) {
|
|
348
|
-
[data-kai-dock] [part="panel"] {
|
|
349
|
-
position: fixed;
|
|
350
|
-
inset: 0;
|
|
351
|
-
inline-size: auto;
|
|
352
|
-
block-size: auto;
|
|
353
|
-
max-inline-size: none;
|
|
354
|
-
max-block-size: none;
|
|
355
|
-
border: 0;
|
|
356
|
-
border-radius: 0;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/* Reserve a band for the built-in [part="close"] X ABOVE whatever is slotted,
|
|
360
|
-
rather than overlaying it on top of the panel's own content — the fix round 1
|
|
361
|
-
collision protection (verified against a real ChatThread header-end slot at
|
|
362
|
-
375px: without this, the X painted directly over trailing icon buttons).
|
|
363
|
-
DERIVED from the button's own inset + size tokens, not a second hand-typed
|
|
364
|
-
number.
|
|
365
|
-
|
|
366
|
-
SCOPED to :not([data-hide-close]) — round 2 (owner feedback: the band read as
|
|
367
|
-
a dead strip when nothing but the X occupied it) removed this UNCONDITIONALLY
|
|
368
|
-
and reintroduced the very collision round 1 fixed, for every consumer who
|
|
369
|
-
never opted into hideClose. hideClose is only true for a caller that supplies
|
|
370
|
-
its OWN close control in its own header row (ChatThread's headerEndContent is
|
|
371
|
-
the one that does today) — the built-in X and this band both come off
|
|
372
|
-
together for that case, since there is nothing left for the band to protect.
|
|
373
|
-
Every other consumer -- including a hand-authored kai-dock with real
|
|
374
|
-
slot="header-end" content and no opinion on hideClose -- keeps the band, the
|
|
375
|
-
same as round 1 shipped. */
|
|
376
|
-
[data-kai-dock]:not([data-hide-close]) [part="panel"] {
|
|
377
|
-
padding-block-start: calc(2 * var(--kai-dock-close-inset-block, 0.75rem) + 2.25rem);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/* The full-bleed panel gets its own close affordance instead of relying on the
|
|
381
|
-
launcher, which is hidden below while the panel is open at this width — a FAB
|
|
382
|
-
floating over a full-bleed panel with no other visible close route is exactly
|
|
383
|
-
what the owner flagged. Escape and the X both still work; the launcher comes
|
|
384
|
-
back the instant the panel closes because data-expanded is gone by then.
|
|
385
|
-
Absolutely positioned, so the padding-block-start above (a content-box
|
|
386
|
-
concern) does not shift IT — only the in-flow slotted content. Content that
|
|
387
|
-
supplies its own close control passes hideClose, which drops the button from
|
|
388
|
-
the render tree entirely (see the component body) rather than being gated
|
|
389
|
-
here — so this rule stays a plain, unconditional width-query switch; the band
|
|
390
|
-
above is what actually varies with hideClose. */
|
|
391
|
-
[data-kai-dock] [part="close"] { display: inline-flex; }
|
|
392
|
-
[data-kai-dock]:has([part="panel"][data-expanded]) [part="launcher"] { display: none; }
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
@media (prefers-reduced-motion: reduce) {
|
|
396
|
-
/* [data-expanded] is listed explicitly because the open-direction rule above is
|
|
397
|
-
MORE SPECIFIC than a bare [part="panel"], so without it an opening panel kept
|
|
398
|
-
animating opacity and transform for exactly the users who asked it not to. */
|
|
399
|
-
[data-kai-dock] [part="launcher"],
|
|
400
|
-
[data-kai-dock] [part="panel"],
|
|
401
|
-
[data-kai-dock] [part="panel"][data-expanded] { transition: none; }
|
|
402
|
-
[data-kai-dock] [part="panel"][data-closed] { transform: none; }
|
|
403
|
-
}
|
|
404
|
-
`;
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* The deepest active element, drilling through nested shadow roots.
|
|
408
|
-
*
|
|
409
|
-
* A local copy of the helper `ui/dialog.tsx` already carries. Deliberately not
|
|
410
|
-
* refactored into a shared module by this change: dialog is not this task's file, and
|
|
411
|
-
* a four-line tree walk duplicated once is cheaper than a cross-file move nobody
|
|
412
|
-
* asked for. If a third caller appears, hoist it then.
|
|
413
|
-
*/
|
|
414
|
-
function deepActiveElement(): HTMLElement | null {
|
|
415
|
-
let el = document.activeElement as HTMLElement | null;
|
|
416
|
-
while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement as HTMLElement;
|
|
417
|
-
return el;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Is `node` inside `root` in the FLATTENED tree?
|
|
422
|
-
*
|
|
423
|
-
* `root.contains(node)` is the wrong question here and would answer `false` for every
|
|
424
|
-
* real case: the panel's content is light-DOM nodes ASSIGNED to a slot, so it lives in
|
|
425
|
-
* the host's tree, not the panel's. This walks up through `assignedSlot` at each slot
|
|
426
|
-
* and through `host` at each shadow boundary, which is the tree focus actually moves in.
|
|
427
|
-
*/
|
|
428
|
-
function isWithin(root: Node, node: Node | null): boolean {
|
|
429
|
-
let cur: Node | null = node;
|
|
430
|
-
while (cur) {
|
|
431
|
-
if (cur === root) return true;
|
|
432
|
-
const slot = cur instanceof Element ? cur.assignedSlot : null;
|
|
433
|
-
if (slot) { cur = slot; continue; }
|
|
434
|
-
if (cur.parentNode) { cur = cur.parentNode; continue; }
|
|
435
|
-
cur = cur instanceof ShadowRoot ? cur.host : null;
|
|
436
|
-
}
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Dock — a corner-docked launcher button with a panel that opens above it.
|
|
442
|
-
*
|
|
443
|
-
* The affordance behind a support widget: a circular button pinned to a corner of the
|
|
444
|
-
* viewport, and a floating panel holding whatever you slot into it. The panel is
|
|
445
|
-
* content-agnostic on purpose — a chat, a form, your own component — and the dock
|
|
446
|
-
* never reads, types or reaches into it.
|
|
447
|
-
*
|
|
448
|
-
* WHAT IT OWNS (all of it non-configurable, because these are facts about the medium
|
|
449
|
-
* rather than preferences):
|
|
450
|
-
*
|
|
451
|
-
* - **Hide semantics.** Closed means `visibility: hidden` + `opacity: 0` + `inert`,
|
|
452
|
-
* and the panel is NEVER unmounted. `display: none` would leave the panel with no
|
|
453
|
-
* layout box while closed, so anything inside that measures itself — a thread
|
|
454
|
-
* scroller, a `ResizeObserver` — measures zero and re-measures on every open. The
|
|
455
|
-
* cost of keeping the box is that a stream keeps rendering into a closed panel,
|
|
456
|
-
* which is precisely what makes an unread dot honest.
|
|
457
|
-
* - **Focus.** On open, focus moves per `focusOnOpen`; on close it returns to the
|
|
458
|
-
* launcher, always. `inert` is cleared and visibility restored BEFORE `focus()` runs
|
|
459
|
-
* — a `focus()` into an inert or hidden subtree is silently dropped, no error — via
|
|
460
|
-
* a `queueMicrotask`, the same shape `ui/dialog.tsx` uses. Mount moves no focus.
|
|
461
|
-
* - **Escape.** Closes only while the dock CONTAINS focus, and never stops the event.
|
|
462
|
-
* A background widget that ate every Escape on the page would break the host page's
|
|
463
|
-
* own menus; swallowing a key from a page you are a guest on is a decide-quietly move.
|
|
464
|
-
* - **No focus trap**, deliberately, unlike `kai-dialog`. The page staying usable is
|
|
465
|
-
* the whole point of "docked", which is also why the panel is `aria-modal="false"`.
|
|
466
|
-
* - **Mobile close route.** At <=480px the panel goes full-bleed and the launcher
|
|
467
|
-
* hides while it is open — a floating launcher over a full-bleed panel is not an
|
|
468
|
-
* obvious close affordance. The panel gets its own `[part="close"]` X, top-right,
|
|
469
|
-
* CSS-gated by the SAME media query and `[data-expanded]`, so it needs no viewport
|
|
470
|
-
* JS. Desktop is unaffected: the launcher keeps toggling and no X ever renders.
|
|
471
|
-
* This is the FALLBACK route, for panel content with no header of its own to hold
|
|
472
|
-
* a close control — content that does (a `ChatThread` using `headerEndContent`,
|
|
473
|
-
* say) passes `hideClose` to drop this X instead of stacking a second one over
|
|
474
|
-
* its own header row.
|
|
475
|
-
*
|
|
476
|
-
* WHAT IT REFUSES: it never aborts a request, never persists its own open state, never
|
|
477
|
-
* decides what "unread" means, and never clears `unread` on open — writing over a
|
|
478
|
-
* consumer's prop is the trap `kai-chat.loading` fell into. Those are the app's calls;
|
|
479
|
-
* `open` + `onOpenChange` is the seam.
|
|
480
|
-
*
|
|
481
|
-
* Geometry is CSS custom properties rather than props: `--kai-dock-width` ·
|
|
482
|
-
* `--kai-dock-height` · `--kai-dock-inset` · `--kai-dock-gap` · `--kai-dock-radius` ·
|
|
483
|
-
* `--kai-dock-z` · `--kai-dock-launcher-size`.
|
|
484
|
-
*
|
|
485
|
-
* @see `elements/dock.tsx` for the `<kai-dock>` web-component facade.
|
|
486
|
-
*/
|
|
487
|
-
export function Dock(props: DockProps) {
|
|
488
|
-
const panelId = `kai-dock-panel-${createUniqueId()}`;
|
|
489
|
-
const [internalOpen, setInternalOpen] = createSignal(props.defaultOpen ?? false);
|
|
490
|
-
|
|
491
|
-
const isControlled = () => props.open !== undefined;
|
|
492
|
-
const isOpen = () => (isControlled() ? !!props.open : internalOpen());
|
|
493
|
-
const setOpen = (v: boolean) => {
|
|
494
|
-
if (!isControlled()) setInternalOpen(v);
|
|
495
|
-
props.onOpenChange?.(v);
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
props.controllerRef?.({ open: isOpen, setOpen });
|
|
499
|
-
|
|
500
|
-
let panel: HTMLElement | undefined;
|
|
501
|
-
let launcher: HTMLButtonElement | undefined;
|
|
502
|
-
|
|
503
|
-
const label = () => props.label ?? 'Chat';
|
|
504
|
-
const launcherName = () => (isOpen()
|
|
505
|
-
? props.closeLabel ?? `Close ${label()}`
|
|
506
|
-
: props.openLabel ?? `Open ${label()}`);
|
|
507
|
-
|
|
508
|
-
/** The focus half of an open, deferred so the state flip above has already landed. */
|
|
509
|
-
const focusOnOpen = () => {
|
|
510
|
-
const mode = props.focusOnOpen ?? 'content';
|
|
511
|
-
if (mode === 'none') return;
|
|
512
|
-
if (mode === 'content') {
|
|
513
|
-
const target = props.contentTarget?.();
|
|
514
|
-
target?.focus();
|
|
515
|
-
// Content-agnostic fallback: a plain <div> is not focusable, so `focus()` was a
|
|
516
|
-
// silent no-op and the panel takes it instead. Asking where focus ACTUALLY
|
|
517
|
-
// landed is the only way to tell, since the dock is not allowed to inspect what
|
|
518
|
-
// it is holding.
|
|
519
|
-
if (target && isWithin(panel!, deepActiveElement())) return;
|
|
520
|
-
}
|
|
521
|
-
panel?.focus();
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
// The focus choreography, and the ONE ordering that is a hard invariant.
|
|
525
|
-
//
|
|
526
|
-
// SEEDED WITH THE INITIAL OPEN VALUE, not `false`. `kai-dialog` seeds `false` on
|
|
527
|
-
// purpose so a `defaultOpen` modal DOES take focus at mount; a docked widget is the
|
|
528
|
-
// opposite — it is a guest on someone's page and must not steal focus on load, from
|
|
529
|
-
// `default-open` OR from `open`. Same mechanism, opposite default, and the
|
|
530
|
-
// difference is exactly the modal/non-modal split.
|
|
531
|
-
createEffect((wasOpen: boolean) => {
|
|
532
|
-
const open = isOpen();
|
|
533
|
-
if (open !== wasOpen) {
|
|
534
|
-
// The attributes and inline styles below are written by render effects, which
|
|
535
|
-
// Solid runs before this one; the microtask is belt-and-braces for a state
|
|
536
|
-
// change driven from outside a Solid batch, and gives a freshly-slotted element
|
|
537
|
-
// a turn to upgrade before we call focus() on it.
|
|
538
|
-
if (open) queueMicrotask(focusOnOpen);
|
|
539
|
-
else queueMicrotask(() => launcher?.focus());
|
|
540
|
-
}
|
|
541
|
-
return open;
|
|
542
|
-
}, untrack(() => props.open ?? props.defaultOpen ?? false));
|
|
543
|
-
|
|
544
|
-
const onKeyDown = (e: KeyboardEvent) => {
|
|
545
|
-
if (e.key !== 'Escape' || !isOpen()) return;
|
|
546
|
-
// D-C. This listener is ON the dock rather than on `document`, so the only
|
|
547
|
-
// keydowns it can ever see are ones whose target is inside it — which IS the
|
|
548
|
-
// condition "the dock contains focus", with no ambiguity and no second check. It
|
|
549
|
-
// also composes: an overlay inside the panel that handles its own Escape and
|
|
550
|
-
// stops the event correctly prevents the dock from closing underneath it.
|
|
551
|
-
// No stopPropagation, no preventDefault: the host page keeps its key.
|
|
552
|
-
setOpen(false);
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
return (
|
|
556
|
-
<>
|
|
557
|
-
<style>{DOCK_CSS}</style>
|
|
558
|
-
<div
|
|
559
|
-
data-kai-dock
|
|
560
|
-
data-position={props.position ?? 'bottom-end'}
|
|
561
|
-
// Drives the mobile panel's reserved-band rule below: present only when the
|
|
562
|
-
// built-in [part="close"] X is ACTUALLY suppressed (hideClose true), so the
|
|
563
|
-
// band stays on for every consumer who hasn't opted into their own close
|
|
564
|
-
// control — see the [part="close"]/reserved-band comments for why removing
|
|
565
|
-
// it unconditionally regressed the collision it existed to prevent.
|
|
566
|
-
bool:data-hide-close={!!props.hideClose}
|
|
567
|
-
onKeyDown={onKeyDown}
|
|
568
|
-
>
|
|
569
|
-
<button
|
|
570
|
-
ref={(el) => { launcher = el; props.launcherRef?.(el); }}
|
|
571
|
-
part="launcher"
|
|
572
|
-
type="button"
|
|
573
|
-
aria-expanded={isOpen() ? 'true' : 'false'}
|
|
574
|
-
aria-controls={panelId}
|
|
575
|
-
// The NAME is what changes for assistive tech; the glyph morph is decorative.
|
|
576
|
-
aria-label={launcherName()}
|
|
577
|
-
disabled={props.disabled || undefined}
|
|
578
|
-
onClick={() => setOpen(!isOpen())}
|
|
579
|
-
>
|
|
580
|
-
<Show when={props.unread && !isOpen()}>
|
|
581
|
-
<span part="badge" aria-hidden="true" />
|
|
582
|
-
</Show>
|
|
583
|
-
<span aria-hidden="true" style={{ display: 'inline-flex' }}>
|
|
584
|
-
<Show when={isOpen()} fallback={props.launcher ?? <DockLauncherGlyph />}>
|
|
585
|
-
{props.launcherOpen ?? props.launcher ?? <DockCloseGlyph />}
|
|
586
|
-
</Show>
|
|
587
|
-
</span>
|
|
588
|
-
</button>
|
|
589
|
-
<div
|
|
590
|
-
ref={(el) => { panel = el; props.panelRef?.(el); }}
|
|
591
|
-
part="panel"
|
|
592
|
-
id={panelId}
|
|
593
|
-
role="dialog"
|
|
594
|
-
// Non-modal, and saying so is load-bearing given there is no focus trap:
|
|
595
|
-
// claiming modality without trapping is worse than claiming neither.
|
|
596
|
-
aria-modal="false"
|
|
597
|
-
aria-label={label()}
|
|
598
|
-
tabindex={-1}
|
|
599
|
-
data-expanded={isOpen() ? '' : undefined}
|
|
600
|
-
data-closed={isOpen() ? undefined : ''}
|
|
601
|
-
// `inert` is the half that actually stops Tab: a visibility:hidden panel is
|
|
602
|
-
// already skipped by most AT, but a focusable control inside it is not.
|
|
603
|
-
bool:inert={!isOpen()}
|
|
604
|
-
// INLINE, unlike the geometry: the closed state must beat any consumer rule,
|
|
605
|
-
// and it is the half a test can read without a layout engine.
|
|
606
|
-
style={{
|
|
607
|
-
visibility: isOpen() ? 'visible' : 'hidden',
|
|
608
|
-
opacity: isOpen() ? '1' : '0',
|
|
609
|
-
'pointer-events': isOpen() ? 'auto' : 'none',
|
|
610
|
-
}}
|
|
611
|
-
>
|
|
612
|
-
{/* Mobile-only (see the <=480px rule in DOCK_CSS): the full-bleed panel's
|
|
613
|
-
own close affordance, since the launcher is hidden by the same query
|
|
614
|
-
while the panel is open. Reuses the launcher's derived close name
|
|
615
|
-
rather than a new prop — same string, same i18n override. Dropped
|
|
616
|
-
from the render tree entirely (not just hidden) when `hideClose` is
|
|
617
|
-
set — the panel content supplies its own, in its own header, and a
|
|
618
|
-
second X floating over that row is the exact "doesn't look
|
|
619
|
-
intentional" feedback `hideClose` exists to avoid. */}
|
|
620
|
-
<Show when={!props.hideClose}>
|
|
621
|
-
<button type="button" part="close" aria-label={launcherName()} onClick={() => setOpen(false)}>
|
|
622
|
-
<DockCloseGlyph />
|
|
623
|
-
</button>
|
|
624
|
-
</Show>
|
|
625
|
-
{props.children}
|
|
626
|
-
</div>
|
|
627
|
-
</div>
|
|
628
|
-
</>
|
|
629
|
-
);
|
|
630
|
-
}
|