@usetheo/ui 0.12.0 → 0.13.1
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/CHANGELOG.md +634 -68
- package/DESIGN.md +18 -18
- package/NOTICE +2 -2
- package/README.md +69 -66
- package/dist/chunk-23YEYNDS.js +89 -0
- package/dist/chunk-23YEYNDS.js.map +1 -0
- package/dist/chunk-25KBUQEQ.js +95 -0
- package/dist/chunk-25KBUQEQ.js.map +1 -0
- package/dist/chunk-2A3E5Y72.js +165 -0
- package/dist/chunk-2A3E5Y72.js.map +1 -0
- package/dist/chunk-2EUKYGH5.js +98 -0
- package/dist/chunk-2EUKYGH5.js.map +1 -0
- package/dist/chunk-2NZYMQKT.js +56 -0
- package/dist/chunk-2NZYMQKT.js.map +1 -0
- package/dist/chunk-2OVFVPSZ.js +26 -0
- package/dist/chunk-2OVFVPSZ.js.map +1 -0
- package/dist/chunk-2UP7SECE.js +78 -0
- package/dist/chunk-2UP7SECE.js.map +1 -0
- package/dist/chunk-33ETHPT7.js +112 -0
- package/dist/chunk-33ETHPT7.js.map +1 -0
- package/dist/chunk-33IIDFSM.js +43 -0
- package/dist/chunk-33IIDFSM.js.map +1 -0
- package/dist/chunk-345HYADQ.js +88 -0
- package/dist/chunk-345HYADQ.js.map +1 -0
- package/dist/chunk-3ZXZGZOX.js +57 -0
- package/dist/chunk-3ZXZGZOX.js.map +1 -0
- package/dist/chunk-45FWKR23.js +62 -0
- package/dist/chunk-45FWKR23.js.map +1 -0
- package/dist/chunk-4DTLUBRW.js +122 -0
- package/dist/chunk-4DTLUBRW.js.map +1 -0
- package/dist/chunk-4EH6F54D.js +69 -0
- package/dist/chunk-4EH6F54D.js.map +1 -0
- package/dist/chunk-4EKF4EIE.js +140 -0
- package/dist/chunk-4EKF4EIE.js.map +1 -0
- package/dist/chunk-4SMYNLTA.js +83 -0
- package/dist/chunk-4SMYNLTA.js.map +1 -0
- package/dist/chunk-4UBFLXS3.js +35 -0
- package/dist/chunk-4UBFLXS3.js.map +1 -0
- package/dist/chunk-52J7SDYH.js +48 -0
- package/dist/chunk-52J7SDYH.js.map +1 -0
- package/dist/chunk-5XCTTXC3.js +180 -0
- package/dist/chunk-5XCTTXC3.js.map +1 -0
- package/dist/chunk-624AATQZ.js +77 -0
- package/dist/chunk-624AATQZ.js.map +1 -0
- package/dist/chunk-6IODJQWC.js +47 -0
- package/dist/chunk-6IODJQWC.js.map +1 -0
- package/dist/chunk-75IDWFYX.js +134 -0
- package/dist/chunk-75IDWFYX.js.map +1 -0
- package/dist/chunk-7BGCWJQR.js +113 -0
- package/dist/chunk-7BGCWJQR.js.map +1 -0
- package/dist/chunk-7BQXMG2A.js +93 -0
- package/dist/chunk-7BQXMG2A.js.map +1 -0
- package/dist/chunk-7RFWVNQA.js +47 -0
- package/dist/chunk-7RFWVNQA.js.map +1 -0
- package/dist/chunk-7T4NK6W6.js +192 -0
- package/dist/chunk-7T4NK6W6.js.map +1 -0
- package/dist/chunk-A3OU6ICP.js +91 -0
- package/dist/chunk-A3OU6ICP.js.map +1 -0
- package/dist/chunk-ABBXLAJW.js +74 -0
- package/dist/chunk-ABBXLAJW.js.map +1 -0
- package/dist/chunk-AENNHS3S.js +83 -0
- package/dist/chunk-AENNHS3S.js.map +1 -0
- package/dist/chunk-AXNJFBYX.js +136 -0
- package/dist/chunk-AXNJFBYX.js.map +1 -0
- package/dist/chunk-AXUAQM45.js +154 -0
- package/dist/chunk-AXUAQM45.js.map +1 -0
- package/dist/chunk-B2FL7KBJ.js +65 -0
- package/dist/chunk-B2FL7KBJ.js.map +1 -0
- package/dist/chunk-BE232OKN.js +42 -0
- package/dist/chunk-BE232OKN.js.map +1 -0
- package/dist/chunk-BP4RQSX7.js +200 -0
- package/dist/chunk-BP4RQSX7.js.map +1 -0
- package/dist/chunk-BPJ3RN3U.js +112 -0
- package/dist/chunk-BPJ3RN3U.js.map +1 -0
- package/dist/chunk-BSF4Y4UT.js +77 -0
- package/dist/chunk-BSF4Y4UT.js.map +1 -0
- package/dist/chunk-BWIDDAR7.js +86 -0
- package/dist/chunk-BWIDDAR7.js.map +1 -0
- package/dist/chunk-BYUWQ6OP.js +45 -0
- package/dist/chunk-BYUWQ6OP.js.map +1 -0
- package/dist/chunk-C5ULP2P5.js +140 -0
- package/dist/chunk-C5ULP2P5.js.map +1 -0
- package/dist/chunk-CCG7PXLX.js +24 -0
- package/dist/chunk-CCG7PXLX.js.map +1 -0
- package/dist/chunk-CED2LKHI.js +117 -0
- package/dist/chunk-CED2LKHI.js.map +1 -0
- package/dist/chunk-CGWIOIEO.js +27 -0
- package/dist/chunk-CGWIOIEO.js.map +1 -0
- package/dist/chunk-CSEGVTKO.js +110 -0
- package/dist/chunk-CSEGVTKO.js.map +1 -0
- package/dist/chunk-CVFSNA4K.js +716 -0
- package/dist/chunk-CVFSNA4K.js.map +1 -0
- package/dist/chunk-E26M7ATD.js +61 -0
- package/dist/chunk-E26M7ATD.js.map +1 -0
- package/dist/chunk-E63IRXZZ.js +78 -0
- package/dist/chunk-E63IRXZZ.js.map +1 -0
- package/dist/chunk-ESJUISWY.js +45 -0
- package/dist/chunk-ESJUISWY.js.map +1 -0
- package/dist/chunk-ET7A3TQZ.js +158 -0
- package/dist/chunk-ET7A3TQZ.js.map +1 -0
- package/dist/chunk-ETTL6XGU.js +139 -0
- package/dist/chunk-ETTL6XGU.js.map +1 -0
- package/dist/chunk-EWDN56AS.js +24 -0
- package/dist/chunk-EWDN56AS.js.map +1 -0
- package/dist/chunk-FJZKA2LV.js +165 -0
- package/dist/chunk-FJZKA2LV.js.map +1 -0
- package/dist/chunk-G2WCT6PJ.js +69 -0
- package/dist/chunk-G2WCT6PJ.js.map +1 -0
- package/dist/chunk-GC52HWIL.js +41 -0
- package/dist/chunk-GC52HWIL.js.map +1 -0
- package/dist/chunk-GCKFA7X7.js +83 -0
- package/dist/chunk-GCKFA7X7.js.map +1 -0
- package/dist/chunk-GDMCDW66.js +19 -0
- package/dist/chunk-GDMCDW66.js.map +1 -0
- package/dist/chunk-GIXHBTHH.js +59 -0
- package/dist/chunk-GIXHBTHH.js.map +1 -0
- package/dist/chunk-GLEOUWPN.js +108 -0
- package/dist/chunk-GLEOUWPN.js.map +1 -0
- package/dist/chunk-GLRUM43F.js +121 -0
- package/dist/chunk-GLRUM43F.js.map +1 -0
- package/dist/chunk-H3L7WZDZ.js +74 -0
- package/dist/chunk-H3L7WZDZ.js.map +1 -0
- package/dist/chunk-H55WXDME.js +45 -0
- package/dist/chunk-H55WXDME.js.map +1 -0
- package/dist/chunk-H5QPJNVD.js +98 -0
- package/dist/chunk-H5QPJNVD.js.map +1 -0
- package/dist/chunk-HB45JHMM.js +43 -0
- package/dist/chunk-HB45JHMM.js.map +1 -0
- package/dist/chunk-HETHTYEZ.js +80 -0
- package/dist/chunk-HETHTYEZ.js.map +1 -0
- package/dist/chunk-HHSKNB32.js +55 -0
- package/dist/chunk-HHSKNB32.js.map +1 -0
- package/dist/chunk-HILOUYES.js +151 -0
- package/dist/chunk-HILOUYES.js.map +1 -0
- package/dist/chunk-HTJVHFNW.js +178 -0
- package/dist/chunk-HTJVHFNW.js.map +1 -0
- package/dist/chunk-HZ7Z22VW.js +130 -0
- package/dist/chunk-HZ7Z22VW.js.map +1 -0
- package/dist/chunk-JFKBFQA5.js +129 -0
- package/dist/chunk-JFKBFQA5.js.map +1 -0
- package/dist/chunk-JIZKW3WC.js +74 -0
- package/dist/chunk-JIZKW3WC.js.map +1 -0
- package/dist/chunk-JQTCCKZA.js +154 -0
- package/dist/chunk-JQTCCKZA.js.map +1 -0
- package/dist/chunk-JR4H3FJ2.js +74 -0
- package/dist/chunk-JR4H3FJ2.js.map +1 -0
- package/dist/chunk-JRW53TVG.js +53 -0
- package/dist/chunk-JRW53TVG.js.map +1 -0
- package/dist/chunk-JXOCE27Z.js +82 -0
- package/dist/chunk-JXOCE27Z.js.map +1 -0
- package/dist/chunk-JYW5YNF7.js +188 -0
- package/dist/chunk-JYW5YNF7.js.map +1 -0
- package/dist/chunk-K72DJOXR.js +81 -0
- package/dist/chunk-K72DJOXR.js.map +1 -0
- package/dist/chunk-KDE3NYTI.js +144 -0
- package/dist/chunk-KDE3NYTI.js.map +1 -0
- package/dist/chunk-KFUFTZLS.js +111 -0
- package/dist/chunk-KFUFTZLS.js.map +1 -0
- package/dist/chunk-KHBXI6AV.js +149 -0
- package/dist/chunk-KHBXI6AV.js.map +1 -0
- package/dist/chunk-M74ZYBOK.js +93 -0
- package/dist/chunk-M74ZYBOK.js.map +1 -0
- package/dist/chunk-ML7WLNIK.js +11 -0
- package/dist/chunk-ML7WLNIK.js.map +1 -0
- package/dist/chunk-MXB2GVEQ.js +57 -0
- package/dist/chunk-MXB2GVEQ.js.map +1 -0
- package/dist/chunk-NEMXW2LB.js +102 -0
- package/dist/chunk-NEMXW2LB.js.map +1 -0
- package/dist/chunk-OJPPSJMF.js +65 -0
- package/dist/chunk-OJPPSJMF.js.map +1 -0
- package/dist/chunk-OMR6ZGME.js +63 -0
- package/dist/chunk-OMR6ZGME.js.map +1 -0
- package/dist/chunk-ORVYP73T.js +223 -0
- package/dist/chunk-ORVYP73T.js.map +1 -0
- package/dist/chunk-OZFUUO2Q.js +50 -0
- package/dist/chunk-OZFUUO2Q.js.map +1 -0
- package/dist/chunk-P44UAK45.js +115 -0
- package/dist/chunk-P44UAK45.js.map +1 -0
- package/dist/chunk-PJUP4BJD.js +116 -0
- package/dist/chunk-PJUP4BJD.js.map +1 -0
- package/dist/chunk-PJWYIOY4.js +3 -0
- package/dist/chunk-PJWYIOY4.js.map +1 -0
- package/dist/chunk-PVCW4O37.js +147 -0
- package/dist/chunk-PVCW4O37.js.map +1 -0
- package/dist/chunk-PXT47DRZ.js +59 -0
- package/dist/chunk-PXT47DRZ.js.map +1 -0
- package/dist/chunk-PZGUZHI7.js +142 -0
- package/dist/chunk-PZGUZHI7.js.map +1 -0
- package/dist/chunk-Q2BAL2PF.js +965 -0
- package/dist/chunk-Q2BAL2PF.js.map +1 -0
- package/dist/chunk-QBUTRD2M.js +58 -0
- package/dist/chunk-QBUTRD2M.js.map +1 -0
- package/dist/chunk-QCQTTOMD.js +236 -0
- package/dist/chunk-QCQTTOMD.js.map +1 -0
- package/dist/chunk-QNYOIVQ4.js +36 -0
- package/dist/chunk-QNYOIVQ4.js.map +1 -0
- package/dist/chunk-QPURZJJM.js +88 -0
- package/dist/chunk-QPURZJJM.js.map +1 -0
- package/dist/chunk-QRXIYPWP.js +121 -0
- package/dist/chunk-QRXIYPWP.js.map +1 -0
- package/dist/chunk-QV4BQRNH.js +29 -0
- package/dist/chunk-QV4BQRNH.js.map +1 -0
- package/dist/chunk-REFY5MLN.js +212 -0
- package/dist/chunk-REFY5MLN.js.map +1 -0
- package/dist/chunk-RJ6RAMKH.js +89 -0
- package/dist/chunk-RJ6RAMKH.js.map +1 -0
- package/dist/chunk-RJBCTMVW.js +59 -0
- package/dist/chunk-RJBCTMVW.js.map +1 -0
- package/dist/chunk-RLWULF5B.js +113 -0
- package/dist/chunk-RLWULF5B.js.map +1 -0
- package/dist/chunk-RPZMFGYI.js +31 -0
- package/dist/chunk-RPZMFGYI.js.map +1 -0
- package/dist/chunk-RQT5DWGG.js +34 -0
- package/dist/chunk-RQT5DWGG.js.map +1 -0
- package/dist/chunk-SBKVVVYY.js +89 -0
- package/dist/chunk-SBKVVVYY.js.map +1 -0
- package/dist/chunk-SOJW47EZ.js +70 -0
- package/dist/chunk-SOJW47EZ.js.map +1 -0
- package/dist/chunk-SQ66DCXY.js +35 -0
- package/dist/chunk-SQ66DCXY.js.map +1 -0
- package/dist/chunk-SXKGWHAM.js +179 -0
- package/dist/chunk-SXKGWHAM.js.map +1 -0
- package/dist/chunk-T2OKGV6M.js +13 -0
- package/dist/chunk-T2OKGV6M.js.map +1 -0
- package/dist/chunk-TJLULCZW.js +108 -0
- package/dist/chunk-TJLULCZW.js.map +1 -0
- package/dist/chunk-TY6NTWN5.js +29 -0
- package/dist/chunk-TY6NTWN5.js.map +1 -0
- package/dist/chunk-U3AEEFVB.js +152 -0
- package/dist/chunk-U3AEEFVB.js.map +1 -0
- package/dist/chunk-V3HFDVZ3.js +102 -0
- package/dist/chunk-V3HFDVZ3.js.map +1 -0
- package/dist/chunk-V6H2RUVP.js +77 -0
- package/dist/chunk-V6H2RUVP.js.map +1 -0
- package/dist/chunk-VB53UMAL.js +106 -0
- package/dist/chunk-VB53UMAL.js.map +1 -0
- package/dist/chunk-VBEMLZGX.js +86 -0
- package/dist/chunk-VBEMLZGX.js.map +1 -0
- package/dist/chunk-VBQFE5RV.js +92 -0
- package/dist/chunk-VBQFE5RV.js.map +1 -0
- package/dist/chunk-VHK2OUCW.js +38 -0
- package/dist/chunk-VHK2OUCW.js.map +1 -0
- package/dist/chunk-VHTAVROO.js +53 -0
- package/dist/chunk-VHTAVROO.js.map +1 -0
- package/dist/chunk-VZX4HLBM.js +84 -0
- package/dist/chunk-VZX4HLBM.js.map +1 -0
- package/dist/chunk-W47V2F3Q.js +115 -0
- package/dist/chunk-W47V2F3Q.js.map +1 -0
- package/dist/chunk-W7I3ZX66.js +76 -0
- package/dist/chunk-W7I3ZX66.js.map +1 -0
- package/dist/chunk-WD42UBGR.js +64 -0
- package/dist/chunk-WD42UBGR.js.map +1 -0
- package/dist/chunk-XC7SYZYR.js +86 -0
- package/dist/chunk-XC7SYZYR.js.map +1 -0
- package/dist/chunk-XJA4B3F5.js +68 -0
- package/dist/chunk-XJA4B3F5.js.map +1 -0
- package/dist/chunk-XSENM65D.js +128 -0
- package/dist/chunk-XSENM65D.js.map +1 -0
- package/dist/chunk-YD2QEVHO.js +68 -0
- package/dist/chunk-YD2QEVHO.js.map +1 -0
- package/dist/chunk-YDKRUSB5.js +35 -0
- package/dist/chunk-YDKRUSB5.js.map +1 -0
- package/dist/chunk-YES6SPDT.js +55 -0
- package/dist/chunk-YES6SPDT.js.map +1 -0
- package/dist/chunk-YNDHYMPI.js +46 -0
- package/dist/chunk-YNDHYMPI.js.map +1 -0
- package/dist/chunk-YO3WEMOH.js +130 -0
- package/dist/chunk-YO3WEMOH.js.map +1 -0
- package/dist/chunk-YUFFAWNB.js +90 -0
- package/dist/chunk-YUFFAWNB.js.map +1 -0
- package/dist/chunk-YVXMWUWD.js +118 -0
- package/dist/chunk-YVXMWUWD.js.map +1 -0
- package/dist/chunk-Z6HZMGO2.js +106 -0
- package/dist/chunk-Z6HZMGO2.js.map +1 -0
- package/dist/chunk-ZAMPCRFJ.js +121 -0
- package/dist/chunk-ZAMPCRFJ.js.map +1 -0
- package/dist/chunk-ZEFOXF2I.js +87 -0
- package/dist/chunk-ZEFOXF2I.js.map +1 -0
- package/dist/chunk-ZJXOHLQE.js +66 -0
- package/dist/chunk-ZJXOHLQE.js.map +1 -0
- package/dist/chunk-ZPURFK4C.js +203 -0
- package/dist/chunk-ZPURFK4C.js.map +1 -0
- package/dist/chunk-ZZZIOTLC.js +89 -0
- package/dist/chunk-ZZZIOTLC.js.map +1 -0
- package/dist/components/composites/account-menu/account-menu.d.ts +43 -0
- package/dist/components/composites/account-menu/index.d.ts +1 -0
- package/dist/components/composites/agent-composer/agent-composer.d.ts +41 -0
- package/dist/components/composites/agent-composer/index.d.ts +1 -0
- package/dist/components/composites/agent-editor/agent-editor.d.ts +31 -0
- package/dist/components/composites/agent-editor/index.d.ts +1 -0
- package/dist/components/composites/agent-stream/agent-stream.d.ts +72 -0
- package/dist/components/composites/agent-stream/index.d.ts +2 -0
- package/dist/components/composites/agent-stream/to-agent-stream-items.d.ts +32 -0
- package/dist/components/composites/agent-timeline/agent-timeline.d.ts +22 -0
- package/dist/components/composites/agent-timeline/index.d.ts +1 -0
- package/dist/components/composites/agent-tool-renderer/agent-tool-renderer.d.ts +60 -0
- package/dist/components/composites/agent-tool-renderer/index.d.ts +1 -0
- package/dist/components/composites/agent-tool-renderer/tool-call-part.d.ts +5 -0
- package/dist/components/composites/approval-card/approval-card.d.ts +35 -0
- package/dist/components/composites/approval-card/index.d.ts +1 -0
- package/dist/components/composites/chat-composer/chat-composer.d.ts +68 -0
- package/dist/components/composites/chat-composer/index.d.ts +1 -0
- package/dist/components/composites/chat-message/chat-message-actions.d.ts +22 -0
- package/dist/components/composites/chat-message/chat-message-branch.d.ts +17 -0
- package/dist/components/composites/chat-message/chat-message-response.d.ts +15 -0
- package/dist/components/composites/chat-message/chat-message-toolbar.d.ts +7 -0
- package/dist/components/composites/chat-message/chat-message.d.ts +94 -0
- package/dist/components/composites/chat-message/index.d.ts +23 -0
- package/dist/components/composites/chat-message/parts/data-part.d.ts +9 -0
- package/dist/components/composites/chat-message/parts/file-part.d.ts +5 -0
- package/dist/components/composites/chat-message/parts/reasoning-part.d.ts +7 -0
- package/dist/components/composites/chat-message/parts/source-part.d.ts +9 -0
- package/dist/components/composites/chat-message/parts/text-part.d.ts +11 -0
- package/dist/components/composites/choice-prompt/choice-prompt.d.ts +65 -0
- package/dist/components/composites/choice-prompt/index.d.ts +2 -0
- package/dist/components/composites/code-block/code-block.d.ts +29 -0
- package/dist/components/composites/code-block/index.d.ts +1 -0
- package/dist/components/composites/command-palette/command-palette.d.ts +41 -0
- package/dist/components/composites/command-palette/index.d.ts +1 -0
- package/dist/components/composites/confirm-dialog/confirm-dialog.d.ts +41 -0
- package/dist/components/composites/confirm-dialog/index.d.ts +1 -0
- package/dist/components/composites/confirm-prompt/confirm-prompt.d.ts +31 -0
- package/dist/components/composites/confirm-prompt/index.d.ts +1 -0
- package/dist/components/composites/cron-jobs-list/cron-jobs-list.d.ts +15 -0
- package/dist/components/composites/cron-jobs-list/index.d.ts +1 -0
- package/dist/components/composites/data-table/data-table.d.ts +63 -0
- package/dist/components/composites/data-table/index.d.ts +1 -0
- package/dist/components/composites/deployment-row/deployment-row.d.ts +28 -0
- package/dist/components/composites/deployment-row/index.d.ts +1 -0
- package/dist/components/composites/domain-config/domain-config.d.ts +34 -0
- package/dist/components/composites/domain-config/index.d.ts +1 -0
- package/dist/components/composites/env-var-editor/env-var-editor.d.ts +35 -0
- package/dist/components/composites/env-var-editor/index.d.ts +1 -0
- package/dist/components/composites/mcp-server-list/index.d.ts +1 -0
- package/dist/components/composites/mcp-server-list/mcp-server-list.d.ts +15 -0
- package/dist/components/composites/metric-card/index.d.ts +2 -0
- package/dist/components/composites/metric-card/metric-card.d.ts +46 -0
- package/dist/components/composites/multi-select-prompt/index.d.ts +1 -0
- package/dist/components/composites/multi-select-prompt/multi-select-prompt.d.ts +61 -0
- package/dist/components/composites/page-shell/index.d.ts +1 -0
- package/dist/components/composites/page-shell/page-shell.d.ts +68 -0
- package/dist/components/composites/permission-modal/index.d.ts +1 -0
- package/dist/components/composites/permission-modal/permission-modal.d.ts +48 -0
- package/dist/components/composites/preview-env-card/index.d.ts +1 -0
- package/dist/components/composites/preview-env-card/preview-env-card.d.ts +36 -0
- package/dist/components/composites/preview-panel/index.d.ts +1 -0
- package/dist/components/composites/preview-panel/preview-panel.d.ts +24 -0
- package/dist/components/composites/project-card/index.d.ts +1 -0
- package/dist/components/composites/project-card/project-card.d.ts +32 -0
- package/dist/components/composites/rollback-ui/index.d.ts +1 -0
- package/dist/components/composites/rollback-ui/rollback-ui.d.ts +32 -0
- package/dist/components/composites/rule-editor/index.d.ts +1 -0
- package/dist/components/composites/rule-editor/rule-editor.d.ts +18 -0
- package/dist/components/composites/skill-editor/index.d.ts +1 -0
- package/dist/components/composites/skill-editor/skill-editor.d.ts +19 -0
- package/dist/components/composites/skills-list/index.d.ts +1 -0
- package/dist/components/composites/skills-list/skills-list.d.ts +15 -0
- package/dist/components/composites/slide-deck/context.d.ts +23 -0
- package/dist/components/composites/slide-deck/controls.d.ts +8 -0
- package/dist/components/composites/slide-deck/fragments.d.ts +20 -0
- package/dist/components/composites/slide-deck/index.d.ts +21 -0
- package/dist/components/composites/slide-deck/notes.d.ts +14 -0
- package/dist/components/composites/slide-deck/presenter-view.d.ts +15 -0
- package/dist/components/composites/slide-deck/print-styles.d.ts +21 -0
- package/dist/components/composites/slide-deck/progress-bar.d.ts +8 -0
- package/dist/components/composites/slide-deck/schema.d.ts +31 -0
- package/dist/components/composites/slide-deck/slide-deck.d.ts +78 -0
- package/dist/components/composites/slide-deck/slide-number.d.ts +11 -0
- package/dist/components/composites/slide-deck/split-deck.d.ts +2 -0
- package/dist/components/composites/slide-deck/thumbnails.d.ts +19 -0
- package/dist/components/composites/slide-deck/use-deck-hash-routing.d.ts +23 -0
- package/dist/components/composites/slide-deck/use-deck-keyboard.d.ts +25 -0
- package/dist/components/composites/slide-deck/use-deck-state.d.ts +57 -0
- package/dist/components/composites/slide-deck/use-deck-swipe.d.ts +20 -0
- package/dist/components/composites/slide-deck/use-fullscreen.d.ts +18 -0
- package/dist/components/composites/stability-bundle-viewer/index.d.ts +1 -0
- package/dist/components/composites/stability-bundle-viewer/stability-bundle-viewer.d.ts +28 -0
- package/dist/components/composites/status-indicator/index.d.ts +2 -0
- package/dist/components/composites/status-indicator/status-indicator.d.ts +31 -0
- package/dist/components/composites/task-header/index.d.ts +1 -0
- package/dist/components/composites/task-header/task-header.d.ts +20 -0
- package/dist/components/composites/text-prompt/index.d.ts +1 -0
- package/dist/components/composites/text-prompt/text-prompt.d.ts +48 -0
- package/dist/components/composites/usage-meter/index.d.ts +1 -0
- package/dist/components/composites/usage-meter/usage-meter.d.ts +56 -0
- package/dist/components/primitives/action-bar/action-bar.d.ts +34 -0
- package/dist/components/primitives/action-bar/index.d.ts +1 -0
- package/dist/components/primitives/agent-error-card/agent-error-card.d.ts +47 -0
- package/dist/components/primitives/agent-error-card/index.d.ts +1 -0
- package/dist/components/primitives/agent-event/agent-event.d.ts +25 -0
- package/dist/components/primitives/agent-event/index.d.ts +1 -0
- package/dist/components/primitives/agent-handoff/agent-handoff.d.ts +24 -0
- package/dist/components/primitives/agent-handoff/index.d.ts +1 -0
- package/dist/components/primitives/agent-profile/agent-profile.d.ts +28 -0
- package/dist/components/primitives/agent-profile/index.d.ts +1 -0
- package/dist/components/primitives/agent-starting-state/agent-starting-state.d.ts +15 -0
- package/dist/components/primitives/agent-starting-state/index.d.ts +1 -0
- package/dist/components/primitives/agent-streaming/agent-streaming.d.ts +16 -0
- package/dist/components/primitives/agent-streaming/index.d.ts +1 -0
- package/dist/components/primitives/alert/alert.d.ts +31 -0
- package/dist/components/primitives/alert/index.d.ts +1 -0
- package/dist/components/primitives/artifact-preview/artifact-preview.d.ts +25 -0
- package/dist/components/primitives/artifact-preview/index.d.ts +1 -0
- package/dist/components/primitives/attachment-chip/attachment-chip.d.ts +14 -0
- package/dist/components/primitives/attachment-chip/index.d.ts +1 -0
- package/dist/components/primitives/audit-log-entry/audit-log-entry.d.ts +29 -0
- package/dist/components/primitives/audit-log-entry/index.d.ts +1 -0
- package/dist/components/primitives/auto-compact-notice/auto-compact-notice.d.ts +24 -0
- package/dist/components/primitives/auto-compact-notice/index.d.ts +1 -0
- package/dist/components/primitives/avatar/avatar.d.ts +26 -0
- package/dist/components/primitives/avatar/index.d.ts +1 -0
- package/dist/components/primitives/badge/badge.d.ts +33 -0
- package/dist/components/primitives/badge/index.d.ts +1 -0
- package/dist/components/primitives/branch-indicator/branch-indicator.d.ts +13 -0
- package/dist/components/primitives/branch-indicator/index.d.ts +1 -0
- package/dist/components/primitives/browser-controls/browser-controls.d.ts +19 -0
- package/dist/components/primitives/browser-controls/index.d.ts +1 -0
- package/dist/components/primitives/build-log-stream/build-log-stream.d.ts +51 -0
- package/dist/components/primitives/build-log-stream/index.d.ts +1 -0
- package/dist/components/primitives/button/button.d.ts +26 -0
- package/dist/components/primitives/button/index.d.ts +1 -0
- package/dist/components/primitives/capability-indicator/capability-indicator.d.ts +57 -0
- package/dist/components/primitives/capability-indicator/index.d.ts +1 -0
- package/dist/components/primitives/card/card.d.ts +40 -0
- package/dist/components/primitives/card/index.d.ts +1 -0
- package/dist/components/primitives/channel-card/channel-card.d.ts +28 -0
- package/dist/components/primitives/channel-card/index.d.ts +1 -0
- package/dist/components/primitives/chat-thread/chat-thread.d.ts +12 -0
- package/dist/components/primitives/chat-thread/index.d.ts +1 -0
- package/dist/components/primitives/checkbox/checkbox.d.ts +20 -0
- package/dist/components/primitives/checkbox/index.d.ts +1 -0
- package/dist/components/primitives/context-card/context-card.d.ts +18 -0
- package/dist/components/primitives/context-card/index.d.ts +1 -0
- package/dist/components/primitives/context-window-bar/context-window-bar.d.ts +27 -0
- package/dist/components/primitives/context-window-bar/index.d.ts +1 -0
- package/dist/components/primitives/copy-button/copy-button.d.ts +17 -0
- package/dist/components/primitives/copy-button/index.d.ts +1 -0
- package/dist/components/primitives/cost-meter/cost-meter.d.ts +23 -0
- package/dist/components/primitives/cost-meter/index.d.ts +1 -0
- package/dist/components/primitives/created-files-card/created-files-card.d.ts +30 -0
- package/dist/components/primitives/created-files-card/index.d.ts +1 -0
- package/dist/components/primitives/cron-job-card/cron-job-card.d.ts +30 -0
- package/dist/components/primitives/cron-job-card/index.d.ts +1 -0
- package/dist/components/primitives/danger-zone/danger-zone.d.ts +40 -0
- package/dist/components/primitives/danger-zone/index.d.ts +1 -0
- package/dist/components/primitives/dialog/dialog.d.ts +26 -0
- package/dist/components/primitives/dialog/index.d.ts +1 -0
- package/dist/components/primitives/diff-viewer/diff-viewer.d.ts +45 -0
- package/dist/components/primitives/diff-viewer/index.d.ts +1 -0
- package/dist/components/primitives/dropdown-menu/dropdown-menu.d.ts +68 -0
- package/dist/components/primitives/dropdown-menu/index.d.ts +1 -0
- package/dist/components/primitives/empty-state/empty-state.d.ts +22 -0
- package/dist/components/primitives/empty-state/index.d.ts +1 -0
- package/dist/components/primitives/export-chat-dialog/export-chat-dialog.d.ts +18 -0
- package/dist/components/primitives/export-chat-dialog/index.d.ts +1 -0
- package/dist/components/primitives/folder-context-card/folder-context-card.d.ts +40 -0
- package/dist/components/primitives/folder-context-card/index.d.ts +1 -0
- package/dist/components/primitives/folder-selector/folder-selector.d.ts +18 -0
- package/dist/components/primitives/folder-selector/index.d.ts +1 -0
- package/dist/components/primitives/form-field/form-field.d.ts +44 -0
- package/dist/components/primitives/form-field/index.d.ts +1 -0
- package/dist/components/primitives/gateway-status-indicator/gateway-status-indicator.d.ts +19 -0
- package/dist/components/primitives/gateway-status-indicator/index.d.ts +1 -0
- package/dist/components/primitives/hook-config/hook-config.d.ts +27 -0
- package/dist/components/primitives/hook-config/index.d.ts +1 -0
- package/dist/components/primitives/hook-event-log/hook-event-log.d.ts +27 -0
- package/dist/components/primitives/hook-event-log/index.d.ts +1 -0
- package/dist/components/primitives/input/index.d.ts +1 -0
- package/dist/components/primitives/input/input.d.ts +24 -0
- package/dist/components/primitives/intent-selector/index.d.ts +1 -0
- package/dist/components/primitives/intent-selector/intent-selector.d.ts +22 -0
- package/dist/components/primitives/label/index.d.ts +1 -0
- package/dist/components/primitives/label/label.d.ts +15 -0
- package/dist/components/primitives/lane-board/index.d.ts +1 -0
- package/dist/components/primitives/lane-board/lane-board.d.ts +26 -0
- package/dist/components/primitives/login-split/index.d.ts +1 -0
- package/dist/components/primitives/login-split/login-split.d.ts +24 -0
- package/dist/components/primitives/mcp-server-card/index.d.ts +1 -0
- package/dist/components/primitives/mcp-server-card/mcp-server-card.d.ts +32 -0
- package/dist/components/primitives/memory-editor/index.d.ts +1 -0
- package/dist/components/primitives/memory-editor/memory-editor.d.ts +29 -0
- package/dist/components/primitives/mention-menu/index.d.ts +1 -0
- package/dist/components/primitives/mention-menu/mention-menu.d.ts +44 -0
- package/dist/components/primitives/metrics-panel/index.d.ts +1 -0
- package/dist/components/primitives/metrics-panel/metrics-panel.d.ts +62 -0
- package/dist/components/primitives/model-card/index.d.ts +1 -0
- package/dist/components/primitives/model-card/model-card.d.ts +72 -0
- package/dist/components/primitives/model-selector/index.d.ts +1 -0
- package/dist/components/primitives/model-selector/model-selector.d.ts +21 -0
- package/dist/components/primitives/pagination/index.d.ts +1 -0
- package/dist/components/primitives/pagination/pagination.d.ts +44 -0
- package/dist/components/primitives/permission-matrix/index.d.ts +1 -0
- package/dist/components/primitives/permission-matrix/permission-matrix.d.ts +43 -0
- package/dist/components/primitives/pin-input/index.d.ts +1 -0
- package/dist/components/primitives/pin-input/pin-input.d.ts +41 -0
- package/dist/components/primitives/plan-badge/index.d.ts +1 -0
- package/dist/components/primitives/plan-badge/plan-badge.d.ts +35 -0
- package/dist/components/primitives/progress/index.d.ts +1 -0
- package/dist/components/primitives/progress/progress.d.ts +44 -0
- package/dist/components/primitives/progress-checklist/index.d.ts +1 -0
- package/dist/components/primitives/progress-checklist/progress-checklist.d.ts +18 -0
- package/dist/components/primitives/project-switcher/index.d.ts +1 -0
- package/dist/components/primitives/project-switcher/project-switcher.d.ts +32 -0
- package/dist/components/primitives/quick-action-chips/index.d.ts +1 -0
- package/dist/components/primitives/quick-action-chips/quick-action-chips.d.ts +22 -0
- package/dist/components/primitives/radio-group/index.d.ts +1 -0
- package/dist/components/primitives/radio-group/radio-group.d.ts +5 -0
- package/dist/components/primitives/recent-folders-list/index.d.ts +1 -0
- package/dist/components/primitives/recent-folders-list/recent-folders-list.d.ts +21 -0
- package/dist/components/primitives/rule-card/index.d.ts +1 -0
- package/dist/components/primitives/rule-card/rule-card.d.ts +15 -0
- package/dist/components/primitives/run-stats/index.d.ts +1 -0
- package/dist/components/primitives/run-stats/run-stats.d.ts +16 -0
- package/dist/components/primitives/run-status-pill/index.d.ts +1 -0
- package/dist/components/primitives/run-status-pill/run-status-pill.d.ts +13 -0
- package/dist/components/primitives/running-tasks-panel/index.d.ts +1 -0
- package/dist/components/primitives/running-tasks-panel/running-tasks-panel.d.ts +20 -0
- package/dist/components/primitives/scroll-area/index.d.ts +2 -0
- package/dist/components/primitives/scroll-area/scroll-area.d.ts +40 -0
- package/dist/components/primitives/scroll-area/use-stick-to-bottom.d.ts +20 -0
- package/dist/components/primitives/select/index.d.ts +1 -0
- package/dist/components/primitives/select/select.d.ts +50 -0
- package/dist/components/primitives/session-list-item/index.d.ts +1 -0
- package/dist/components/primitives/session-list-item/session-list-item.d.ts +35 -0
- package/dist/components/primitives/session-timeline/index.d.ts +1 -0
- package/dist/components/primitives/session-timeline/session-timeline.d.ts +32 -0
- package/dist/components/primitives/sheet/index.d.ts +1 -0
- package/dist/components/primitives/sheet/sheet.d.ts +57 -0
- package/dist/components/primitives/sidebar/index.d.ts +1 -0
- package/dist/components/primitives/sidebar/sidebar.d.ts +18 -0
- package/dist/components/primitives/skeleton/index.d.ts +1 -0
- package/dist/components/primitives/skeleton/skeleton.d.ts +21 -0
- package/dist/components/primitives/skill-card/index.d.ts +1 -0
- package/dist/components/primitives/skill-card/skill-card.d.ts +28 -0
- package/dist/components/primitives/slide/alerts.d.ts +31 -0
- package/dist/components/primitives/slide/frontmatter.d.ts +23 -0
- package/dist/components/primitives/slide/index.d.ts +16 -0
- package/dist/components/primitives/slide/json-schema.d.ts +9 -0
- package/dist/components/primitives/slide/marpit-bg.d.ts +36 -0
- package/dist/components/primitives/slide/parse.d.ts +59 -0
- package/dist/components/primitives/slide/plugin.d.ts +79 -0
- package/dist/components/primitives/slide/plugins/emoji/index.d.ts +8 -0
- package/dist/components/primitives/slide/plugins/emoji/map.d.ts +15 -0
- package/dist/components/primitives/slide/plugins/math/index.d.ts +6 -0
- package/dist/components/primitives/slide/plugins/mermaid/index.d.ts +48 -0
- package/dist/components/primitives/slide/plugins/shiki/index.d.ts +11 -0
- package/dist/components/primitives/slide/sanitize.d.ts +33 -0
- package/dist/components/primitives/slide/schema.d.ts +109 -0
- package/dist/components/primitives/slide/slide.d.ts +53 -0
- package/dist/components/primitives/slide/themes/index.d.ts +9 -0
- package/dist/components/primitives/slide/use-slide-fit.d.ts +21 -0
- package/dist/components/primitives/slide/validate.d.ts +33 -0
- package/dist/components/primitives/social-auth-row/index.d.ts +1 -0
- package/dist/components/primitives/social-auth-row/social-auth-row.d.ts +24 -0
- package/dist/components/primitives/stat-tile/index.d.ts +1 -0
- package/dist/components/primitives/stat-tile/stat-tile.d.ts +28 -0
- package/dist/components/primitives/status-dot/index.d.ts +1 -0
- package/dist/components/primitives/status-dot/status-dot.d.ts +31 -0
- package/dist/components/primitives/steps-rail/index.d.ts +1 -0
- package/dist/components/primitives/steps-rail/steps-rail.d.ts +24 -0
- package/dist/components/primitives/sub-agent-dispatch/index.d.ts +1 -0
- package/dist/components/primitives/sub-agent-dispatch/sub-agent-dispatch.d.ts +30 -0
- package/dist/components/primitives/switch/index.d.ts +1 -0
- package/dist/components/primitives/switch/switch.d.ts +20 -0
- package/dist/components/primitives/system-prompt-editor/index.d.ts +1 -0
- package/dist/components/primitives/system-prompt-editor/system-prompt-editor.d.ts +25 -0
- package/dist/components/primitives/table/index.d.ts +1 -0
- package/dist/components/primitives/table/table.d.ts +61 -0
- package/dist/components/primitives/tabs/index.d.ts +1 -0
- package/dist/components/primitives/tabs/tabs.d.ts +7 -0
- package/dist/components/primitives/task-plan/index.d.ts +1 -0
- package/dist/components/primitives/task-plan/task-plan.d.ts +36 -0
- package/dist/components/primitives/terminal-panel/index.d.ts +1 -0
- package/dist/components/primitives/terminal-panel/terminal-panel.d.ts +32 -0
- package/dist/components/primitives/textarea/index.d.ts +1 -0
- package/dist/components/primitives/textarea/textarea.d.ts +20 -0
- package/dist/components/primitives/thinking-level-selector/index.d.ts +1 -0
- package/dist/components/primitives/thinking-level-selector/thinking-level-selector.d.ts +24 -0
- package/dist/components/primitives/timestamp/index.d.ts +1 -0
- package/dist/components/primitives/timestamp/timestamp.d.ts +34 -0
- package/dist/components/primitives/toast/index.d.ts +2 -0
- package/dist/components/primitives/toast/toast.d.ts +34 -0
- package/dist/components/primitives/toast/toaster.d.ts +38 -0
- package/dist/components/primitives/token-usage-chart/index.d.ts +2 -0
- package/dist/components/primitives/token-usage-chart/token-usage-chart.d.ts +44 -0
- package/dist/components/primitives/token-usage-chart/usage-metrics.d.ts +23 -0
- package/dist/components/primitives/tool-call/index.d.ts +1 -0
- package/dist/components/primitives/tool-call/tool-call.d.ts +30 -0
- package/dist/components/primitives/tool-call-card/index.d.ts +1 -0
- package/dist/components/primitives/tool-call-card/tool-call-card.d.ts +30 -0
- package/dist/components/primitives/tool-result/index.d.ts +1 -0
- package/dist/components/primitives/tool-result/tool-result.d.ts +19 -0
- package/dist/components/primitives/tools-list/index.d.ts +1 -0
- package/dist/components/primitives/tools-list/tools-list.d.ts +32 -0
- package/dist/components/primitives/tooltip/index.d.ts +1 -0
- package/dist/components/primitives/tooltip/tooltip.d.ts +33 -0
- package/dist/components/primitives/topnav/index.d.ts +1 -0
- package/dist/components/primitives/topnav/topnav.d.ts +29 -0
- package/dist/components/primitives/update-banner/index.d.ts +1 -0
- package/dist/components/primitives/update-banner/update-banner.d.ts +16 -0
- package/dist/components/primitives/whiteboard/index.d.ts +2 -0
- package/dist/components/primitives/whiteboard/render/freedraw.d.ts +2 -0
- package/dist/components/primitives/whiteboard/render/line.d.ts +4 -0
- package/dist/components/primitives/whiteboard/render/rough-paths.d.ts +16 -0
- package/dist/components/primitives/whiteboard/render/scene.d.ts +3 -0
- package/dist/components/primitives/whiteboard/render/shape.d.ts +5 -0
- package/dist/components/primitives/whiteboard/render/style.d.ts +24 -0
- package/dist/components/primitives/whiteboard/render/text.d.ts +2 -0
- package/dist/components/primitives/whiteboard/schema.d.ts +596 -0
- package/dist/components/primitives/whiteboard/seed.d.ts +22 -0
- package/dist/components/primitives/whiteboard/validate.d.ts +26 -0
- package/dist/components/primitives/whiteboard/viewport/use-pointer-pan.d.ts +11 -0
- package/dist/components/primitives/whiteboard/viewport/use-viewport.d.ts +33 -0
- package/dist/components/primitives/whiteboard/whiteboard.d.ts +21 -0
- package/dist/components.css +1 -1
- package/dist/composites/account-menu/index.js +4 -4
- package/dist/composites/agent-composer/index.js +6 -5
- package/dist/composites/agent-editor/index.js +8 -7
- package/dist/composites/agent-stream/index.js +21 -9
- package/dist/composites/agent-timeline/index.js +3 -3
- package/dist/composites/agent-tool-renderer/index.js +17 -0
- package/dist/composites/agent-tool-renderer/index.js.map +1 -0
- package/dist/composites/approval-card/index.js +3 -3
- package/dist/composites/chat-composer/index.js +3 -3
- package/dist/composites/chat-message/index.js +18 -4
- package/dist/composites/choice-prompt/index.js +11 -0
- package/dist/composites/choice-prompt/index.js.map +1 -0
- package/dist/composites/code-block/index.js +3 -3
- package/dist/composites/command-palette/index.js +4 -3
- package/dist/composites/confirm-dialog/index.js +6 -5
- package/dist/composites/confirm-prompt/index.js +7 -0
- package/dist/composites/confirm-prompt/index.js.map +1 -0
- package/dist/composites/cron-jobs-list/index.js +3 -3
- package/dist/composites/data-table/index.js +8 -7
- package/dist/composites/deployment-row/index.js +3 -3
- package/dist/composites/domain-config/index.js +6 -5
- package/dist/composites/env-var-editor/index.js +7 -5
- package/dist/composites/mcp-server-list/index.js +4 -3
- package/dist/composites/metric-card/index.js +5 -0
- package/dist/composites/metric-card/index.js.map +1 -0
- package/dist/composites/multi-select-prompt/index.js +11 -0
- package/dist/composites/multi-select-prompt/index.js.map +1 -0
- package/dist/composites/page-shell/index.js +6 -5
- package/dist/composites/permission-modal/index.js +5 -4
- package/dist/composites/preview-env-card/index.js +4 -4
- package/dist/composites/preview-panel/index.js +3 -3
- package/dist/composites/project-card/index.js +4 -4
- package/dist/composites/rollback-ui/index.js +5 -4
- package/dist/composites/rule-editor/index.js +9 -8
- package/dist/composites/skill-editor/index.js +9 -8
- package/dist/composites/skills-list/index.js +4 -3
- package/dist/composites/stability-bundle-viewer/index.js +5 -0
- package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
- package/dist/composites/status-indicator/index.js +4 -0
- package/dist/composites/status-indicator/index.js.map +1 -0
- package/dist/composites/task-header/index.js +3 -3
- package/dist/composites/text-prompt/index.js +10 -0
- package/dist/composites/text-prompt/index.js.map +1 -0
- package/dist/composites/usage-meter/index.js +3 -3
- package/dist/fonts-cdn.css +4 -4
- package/dist/fonts.css +2 -2
- package/dist/index.d.ts +144 -4461
- package/dist/index.js +999 -731
- package/dist/index.js.map +1 -1
- package/dist/lib/cn.d.ts +6 -0
- package/dist/lib/env.d.ts +55 -0
- package/dist/lib/live-region-context.d.ts +2 -0
- package/dist/lib/markdown/code-block.d.ts +14 -0
- package/dist/lib/markdown/inline-code.d.ts +10 -0
- package/dist/lib/markdown/math.d.ts +12 -0
- package/dist/lib/markdown/mermaid.d.ts +6 -0
- package/dist/lib/markdown/parser.d.ts +47 -0
- package/dist/lib/markdown/streaming-preprocess.d.ts +39 -0
- package/dist/lib/prompt.d.ts +40 -0
- package/dist/lib/safe-href.d.ts +27 -0
- package/dist/lib/sdk-tools-adapters/index.d.ts +1 -0
- package/dist/lib/sdk-tools-adapters/index.js +3 -0
- package/dist/lib/sdk-tools-adapters/index.js.map +1 -0
- package/dist/lib/sdk-tools-adapters/sdk-tools-adapters.d.ts +60 -0
- package/dist/lib/types.d.ts +10 -0
- package/dist/preset-v3-legacy.d.ts +7 -10
- package/dist/preset-v3-legacy.js +23 -3
- package/dist/preset-v3-legacy.js.map +1 -1
- package/dist/preset.css +2 -2
- package/dist/primitives/action-bar/index.js +2 -2
- package/dist/primitives/agent-error-card/index.js +2 -2
- package/dist/primitives/agent-event/index.js +3 -2
- package/dist/primitives/agent-handoff/index.js +2 -2
- package/dist/primitives/agent-profile/index.js +2 -2
- package/dist/primitives/agent-starting-state/index.js +2 -2
- package/dist/primitives/agent-streaming/index.js +2 -2
- package/dist/primitives/alert/index.js +2 -2
- package/dist/primitives/artifact-preview/index.js +2 -2
- package/dist/primitives/attachment-chip/index.js +2 -2
- package/dist/primitives/audit-log-entry/index.js +2 -2
- package/dist/primitives/auto-compact-notice/index.js +2 -2
- package/dist/primitives/avatar/index.js +2 -2
- package/dist/primitives/badge/index.js +2 -2
- package/dist/primitives/branch-indicator/index.js +4 -0
- package/dist/primitives/branch-indicator/index.js.map +1 -0
- package/dist/primitives/browser-controls/index.js +2 -2
- package/dist/primitives/build-log-stream/index.js +4 -2
- package/dist/primitives/button/index.js +2 -2
- package/dist/primitives/capability-indicator/index.js +2 -2
- package/dist/primitives/card/index.js +3 -2
- package/dist/primitives/channel-card/index.js +4 -0
- package/dist/primitives/channel-card/index.js.map +1 -0
- package/dist/primitives/chat-thread/index.js +2 -2
- package/dist/primitives/checkbox/index.js +2 -2
- package/dist/primitives/context-card/index.js +2 -2
- package/dist/primitives/context-window-bar/index.js +2 -2
- package/dist/primitives/copy-button/index.js +3 -2
- package/dist/primitives/cost-meter/index.js +2 -2
- package/dist/primitives/created-files-card/index.js +2 -2
- package/dist/primitives/cron-job-card/index.js +2 -2
- package/dist/primitives/danger-zone/index.js +2 -2
- package/dist/primitives/dialog/index.js +2 -2
- package/dist/primitives/diff-viewer/index.js +2 -2
- package/dist/primitives/dropdown-menu/index.js +2 -2
- package/dist/primitives/empty-state/index.js +2 -2
- package/dist/primitives/export-chat-dialog/index.js +5 -0
- package/dist/primitives/export-chat-dialog/index.js.map +1 -0
- package/dist/primitives/folder-context-card/index.js +2 -2
- package/dist/primitives/folder-selector/index.js +2 -2
- package/dist/primitives/form-field/index.js +3 -2
- package/dist/primitives/gateway-status-indicator/index.js +4 -0
- package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
- package/dist/primitives/hook-config/index.js +3 -2
- package/dist/primitives/hook-event-log/index.js +2 -2
- package/dist/primitives/input/index.js +2 -2
- package/dist/primitives/intent-selector/index.js +2 -2
- package/dist/primitives/label/index.js +2 -2
- package/dist/primitives/lane-board/index.js +2 -2
- package/dist/primitives/login-split/index.js +2 -2
- package/dist/primitives/mcp-server-card/index.js +2 -2
- package/dist/primitives/memory-editor/index.js +2 -2
- package/dist/primitives/mention-menu/index.js +3 -2
- package/dist/primitives/metrics-panel/index.js +2 -2
- package/dist/primitives/model-card/index.js +2 -2
- package/dist/primitives/model-selector/index.js +2 -2
- package/dist/primitives/pagination/index.js +2 -2
- package/dist/primitives/permission-matrix/index.js +3 -2
- package/dist/primitives/pin-input/index.js +3 -2
- package/dist/primitives/plan-badge/index.js +2 -2
- package/dist/primitives/progress/index.js +2 -2
- package/dist/primitives/progress-checklist/index.js +2 -2
- package/dist/primitives/project-switcher/index.js +2 -2
- package/dist/primitives/quick-action-chips/index.js +2 -2
- package/dist/primitives/radio-group/index.js +2 -2
- package/dist/primitives/recent-folders-list/index.js +2 -2
- package/dist/primitives/rule-card/index.js +2 -2
- package/dist/primitives/run-stats/index.js +2 -2
- package/dist/primitives/run-status-pill/index.js +4 -0
- package/dist/primitives/run-status-pill/index.js.map +1 -0
- package/dist/primitives/running-tasks-panel/index.js +2 -2
- package/dist/primitives/scroll-area/index.js +3 -2
- package/dist/primitives/select/index.js +2 -2
- package/dist/primitives/session-list-item/index.js +2 -2
- package/dist/primitives/session-timeline/index.js +2 -2
- package/dist/primitives/sheet/index.js +2 -2
- package/dist/primitives/sidebar/index.js +2 -2
- package/dist/primitives/skeleton/index.js +2 -2
- package/dist/primitives/skill-card/index.js +2 -2
- package/dist/primitives/social-auth-row/index.js +2 -2
- package/dist/primitives/stat-tile/index.js +2 -2
- package/dist/primitives/status-dot/index.js +4 -2
- package/dist/primitives/steps-rail/index.js +2 -2
- package/dist/primitives/sub-agent-dispatch/index.js +2 -2
- package/dist/primitives/switch/index.js +2 -2
- package/dist/primitives/system-prompt-editor/index.js +3 -2
- package/dist/primitives/table/index.js +3 -2
- package/dist/primitives/tabs/index.js +2 -2
- package/dist/primitives/task-plan/index.js +2 -2
- package/dist/primitives/terminal-panel/index.js +2 -2
- package/dist/primitives/textarea/index.js +2 -2
- package/dist/primitives/thinking-level-selector/index.js +5 -0
- package/dist/primitives/thinking-level-selector/index.js.map +1 -0
- package/dist/primitives/timestamp/index.js +4 -2
- package/dist/primitives/toast/index.js +3 -2
- package/dist/primitives/token-usage-chart/index.js +2 -2
- package/dist/primitives/tool-call/index.js +3 -2
- package/dist/primitives/tool-call-card/index.js +3 -2
- package/dist/primitives/tool-result/index.js +2 -2
- package/dist/primitives/tools-list/index.js +2 -2
- package/dist/primitives/tooltip/index.js +2 -2
- package/dist/primitives/topnav/index.js +2 -2
- package/dist/primitives/update-banner/index.js +4 -0
- package/dist/primitives/update-banner/index.js.map +1 -0
- package/dist/screens/theo-code-shell.d.ts +9 -0
- package/dist/screens/theo-code-shell.data.d.ts +147 -0
- package/dist/slide/index.js +2 -1
- package/dist/slide/plugins/mermaid/index.js +1 -1
- package/dist/slide/plugins/mermaid/index.js.map +1 -1
- package/dist/slide/plugins/shiki/index.js.map +1 -1
- package/dist/slide/themes/violet-forge.css +1 -1
- package/dist/slide-deck/index.js +13 -2
- package/dist/slide-deck/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +29 -0
- package/dist/styles.css +3 -3
- package/dist/test/a11y.d.ts +13 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/themes/anthropic-style.d.ts +11 -0
- package/dist/themes/aurora-terminal.d.ts +9 -0
- package/dist/themes/classic-paper.d.ts +17 -0
- package/dist/themes/color-value-pattern.d.ts +22 -0
- package/dist/themes/color.d.ts +62 -0
- package/dist/themes/define.d.ts +65 -0
- package/dist/themes/density.d.ts +17 -0
- package/dist/themes/dracula.d.ts +14 -0
- package/dist/themes/github-dark.d.ts +11 -0
- package/dist/themes/index.d.ts +26 -0
- package/dist/themes/linear-glass.d.ts +11 -0
- package/dist/themes/one-dark.d.ts +11 -0
- package/dist/themes/openai-style.d.ts +10 -0
- package/dist/themes/schema.d.ts +112 -0
- package/dist/themes/theme-provider.d.ts +82 -0
- package/dist/themes/theme-script.d.ts +48 -0
- package/dist/themes/theme-switcher.d.ts +17 -0
- package/dist/themes/types.d.ts +88 -0
- package/dist/themes/vercel-mono.d.ts +11 -0
- package/dist/themes/violet-forge.d.ts +12 -0
- package/dist/theo-ui-provider.d.ts +50 -0
- package/dist/tokens-v4.css +77 -41
- package/dist/tokens.css +159 -74
- package/dist/types/agent.d.ts +19 -0
- package/dist/types/chat.d.ts +173 -0
- package/dist/types/mode.d.ts +16 -0
- package/dist/types/permission.d.ts +8 -0
- package/dist/types/rule.d.ts +28 -0
- package/dist/types/task.d.ts +9 -0
- package/dist/vite-plugin.d.ts +7 -11
- package/dist/vite-plugin.js +4 -4
- package/dist/vite-plugin.js.map +1 -1
- package/dist/whiteboard/index.js +3 -0
- package/dist/whiteboard/index.js.map +1 -1
- package/llms.txt +39 -39
- package/package.json +246 -174
- package/registry/index.json +58 -4
- package/registry/r/account-menu.json +1 -1
- package/registry/r/action-bar.json +1 -1
- package/registry/r/agent-composer.json +1 -1
- package/registry/r/agent-editor.json +1 -1
- package/registry/r/agent-error-card.json +1 -1
- package/registry/r/agent-event.json +1 -1
- package/registry/r/agent-handoff.json +1 -1
- package/registry/r/agent-profile.json +1 -1
- package/registry/r/agent-starting-state.json +1 -1
- package/registry/r/agent-stream.json +1 -1
- package/registry/r/agent-streaming.json +1 -1
- package/registry/r/agent-timeline.json +1 -1
- package/registry/r/agent-tool-renderer.json +40 -0
- package/registry/r/alert.json +1 -1
- package/registry/r/approval-card.json +1 -1
- package/registry/r/artifact-preview.json +1 -1
- package/registry/r/attachment-chip.json +1 -1
- package/registry/r/audit-log-entry.json +1 -1
- package/registry/r/auto-compact-notice.json +1 -1
- package/registry/r/avatar.json +1 -1
- package/registry/r/badge.json +1 -1
- package/registry/r/browser-controls.json +1 -1
- package/registry/r/build-log-stream.json +2 -1
- package/registry/r/button.json +1 -1
- package/registry/r/capability-indicator.json +1 -1
- package/registry/r/card.json +1 -1
- package/registry/r/chat-composer.json +1 -1
- package/registry/r/chat-message.json +11 -16
- package/registry/r/chat-thread.json +1 -1
- package/registry/r/chat-types.json +1 -1
- package/registry/r/checkbox.json +1 -1
- package/registry/r/choice-prompt.json +25 -0
- package/registry/r/cn.json +1 -1
- package/registry/r/code-block.json +1 -1
- package/registry/r/command-palette.json +1 -1
- package/registry/r/confirm-dialog.json +1 -1
- package/registry/r/confirm-prompt.json +21 -0
- package/registry/r/context-card.json +1 -1
- package/registry/r/context-window-bar.json +1 -1
- package/registry/r/copy-button.json +1 -1
- package/registry/r/cost-meter.json +1 -1
- package/registry/r/created-files-card.json +1 -1
- package/registry/r/cron-job-card.json +1 -1
- package/registry/r/cron-jobs-list.json +1 -1
- package/registry/r/danger-zone.json +1 -1
- package/registry/r/data-table.json +1 -1
- package/registry/r/deployment-row.json +1 -1
- package/registry/r/dialog.json +1 -1
- package/registry/r/diff-viewer.json +1 -1
- package/registry/r/domain-config.json +1 -1
- package/registry/r/dropdown-menu.json +2 -2
- package/registry/r/empty-state.json +1 -1
- package/registry/r/env-var-editor.json +2 -1
- package/registry/r/env.json +15 -0
- package/registry/r/folder-context-card.json +1 -1
- package/registry/r/folder-selector.json +1 -1
- package/registry/r/form-field.json +1 -1
- package/registry/r/hook-config.json +1 -1
- package/registry/r/hook-event-log.json +1 -1
- package/registry/r/input.json +1 -1
- package/registry/r/intent-selector.json +1 -1
- package/registry/r/label.json +1 -1
- package/registry/r/lane-board.json +1 -1
- package/registry/r/login-split.json +1 -1
- package/registry/r/mcp-server-card.json +1 -1
- package/registry/r/mcp-server-list.json +1 -1
- package/registry/r/memory-editor.json +1 -1
- package/registry/r/mention-menu.json +1 -1
- package/registry/r/metric-card.json +23 -0
- package/registry/r/metrics-panel.json +1 -1
- package/registry/r/model-card.json +1 -1
- package/registry/r/model-selector.json +1 -1
- package/registry/r/multi-select-prompt.json +25 -0
- package/registry/r/page-shell.json +1 -1
- package/registry/r/pagination.json +1 -1
- package/registry/r/permission-matrix.json +1 -1
- package/registry/r/permission-modal.json +1 -1
- package/registry/r/pin-input.json +1 -1
- package/registry/r/plan-badge.json +1 -1
- package/registry/r/preview-env-card.json +1 -1
- package/registry/r/preview-panel.json +1 -1
- package/registry/r/progress-checklist.json +1 -1
- package/registry/r/progress.json +1 -1
- package/registry/r/project-card.json +1 -1
- package/registry/r/project-switcher.json +1 -1
- package/registry/r/prompt.json +15 -0
- package/registry/r/quick-action-chips.json +1 -1
- package/registry/r/radio-group.json +1 -1
- package/registry/r/recent-folders-list.json +1 -1
- package/registry/r/rollback-ui.json +1 -1
- package/registry/r/rule-card.json +1 -1
- package/registry/r/rule-editor.json +1 -1
- package/registry/r/run-stats.json +1 -1
- package/registry/r/running-tasks-panel.json +1 -1
- package/registry/r/safe-href.json +1 -1
- package/registry/r/scroll-area.json +1 -1
- package/registry/r/select.json +1 -1
- package/registry/r/session-list-item.json +1 -1
- package/registry/r/session-timeline.json +1 -1
- package/registry/r/sheet.json +1 -1
- package/registry/r/sidebar.json +1 -1
- package/registry/r/skeleton.json +1 -1
- package/registry/r/skill-card.json +1 -1
- package/registry/r/skill-editor.json +1 -1
- package/registry/r/skills-list.json +1 -1
- package/registry/r/slide-deck.json +8 -8
- package/registry/r/slide-plugin-mermaid.json +1 -1
- package/registry/r/slide-plugin-shiki.json +1 -1
- package/registry/r/slide.json +6 -6
- package/registry/r/social-auth-row.json +1 -1
- package/registry/r/stat-tile.json +1 -1
- package/registry/r/status-dot.json +2 -1
- package/registry/r/status-indicator.json +20 -0
- package/registry/r/steps-rail.json +1 -1
- package/registry/r/sub-agent-dispatch.json +1 -1
- package/registry/r/switch.json +1 -1
- package/registry/r/system-prompt-editor.json +1 -1
- package/registry/r/table.json +1 -1
- package/registry/r/tabs.json +1 -1
- package/registry/r/tailwind-preset.json +1 -1
- package/registry/r/task-header.json +1 -1
- package/registry/r/task-plan.json +1 -1
- package/registry/r/terminal-panel.json +1 -1
- package/registry/r/text-prompt.json +24 -0
- package/registry/r/textarea.json +1 -1
- package/registry/r/theme-provider.json +7 -6
- package/registry/r/theo-ui-provider.json +1 -1
- package/registry/r/timestamp.json +2 -1
- package/registry/r/toast.json +2 -2
- package/registry/r/token-usage-chart.json +1 -1
- package/registry/r/tokens.json +2 -2
- package/registry/r/tool-call-card.json +1 -1
- package/registry/r/tool-call.json +1 -1
- package/registry/r/tool-result.json +1 -1
- package/registry/r/tools-list.json +1 -1
- package/registry/r/tooltip.json +1 -1
- package/registry/r/topnav.json +1 -1
- package/registry/r/usage-meter.json +1 -1
- package/registry/r/whiteboard.json +3 -3
- package/dist/chunk-2UJROWAG.js +0 -106
- package/dist/chunk-2UJROWAG.js.map +0 -1
- package/dist/chunk-2XPWOUEH.js +0 -68
- package/dist/chunk-2XPWOUEH.js.map +0 -1
- package/dist/chunk-3GHLNCM3.js +0 -42
- package/dist/chunk-3GHLNCM3.js.map +0 -1
- package/dist/chunk-3HOXC25T.js +0 -48
- package/dist/chunk-3HOXC25T.js.map +0 -1
- package/dist/chunk-3QGO5SB3.js +0 -46
- package/dist/chunk-3QGO5SB3.js.map +0 -1
- package/dist/chunk-47QJVWW2.js +0 -85
- package/dist/chunk-47QJVWW2.js.map +0 -1
- package/dist/chunk-4L63UW3I.js +0 -35
- package/dist/chunk-4L63UW3I.js.map +0 -1
- package/dist/chunk-4UUSJJFZ.js +0 -25
- package/dist/chunk-4UUSJJFZ.js.map +0 -1
- package/dist/chunk-4ZBZBRG5.js +0 -127
- package/dist/chunk-4ZBZBRG5.js.map +0 -1
- package/dist/chunk-57NXT3OX.js +0 -92
- package/dist/chunk-57NXT3OX.js.map +0 -1
- package/dist/chunk-5FF5EUZP.js +0 -44
- package/dist/chunk-5FF5EUZP.js.map +0 -1
- package/dist/chunk-5UGQXB2P.js +0 -714
- package/dist/chunk-5UGQXB2P.js.map +0 -1
- package/dist/chunk-62FT22CI.js +0 -85
- package/dist/chunk-62FT22CI.js.map +0 -1
- package/dist/chunk-673R3GSK.js +0 -19
- package/dist/chunk-673R3GSK.js.map +0 -1
- package/dist/chunk-6VINZJBV.js +0 -128
- package/dist/chunk-6VINZJBV.js.map +0 -1
- package/dist/chunk-74NZ5U3E.js +0 -145
- package/dist/chunk-74NZ5U3E.js.map +0 -1
- package/dist/chunk-755NWSNW.js +0 -36
- package/dist/chunk-755NWSNW.js.map +0 -1
- package/dist/chunk-7GLBWWMW.js +0 -70
- package/dist/chunk-7GLBWWMW.js.map +0 -1
- package/dist/chunk-7RXYW5VM.js +0 -88
- package/dist/chunk-7RXYW5VM.js.map +0 -1
- package/dist/chunk-AC4MGCXI.js +0 -92
- package/dist/chunk-AC4MGCXI.js.map +0 -1
- package/dist/chunk-AEVSVDT6.js +0 -67
- package/dist/chunk-AEVSVDT6.js.map +0 -1
- package/dist/chunk-AODIMN2N.js +0 -68
- package/dist/chunk-AODIMN2N.js.map +0 -1
- package/dist/chunk-ATHOPBCA.js +0 -61
- package/dist/chunk-ATHOPBCA.js.map +0 -1
- package/dist/chunk-AXKBNRZW.js +0 -173
- package/dist/chunk-AXKBNRZW.js.map +0 -1
- package/dist/chunk-B75MEYNR.js +0 -106
- package/dist/chunk-B75MEYNR.js.map +0 -1
- package/dist/chunk-BGKA6DI6.js +0 -34
- package/dist/chunk-BGKA6DI6.js.map +0 -1
- package/dist/chunk-BNQAJGEN.js +0 -88
- package/dist/chunk-BNQAJGEN.js.map +0 -1
- package/dist/chunk-BP2SETUC.js +0 -101
- package/dist/chunk-BP2SETUC.js.map +0 -1
- package/dist/chunk-BPUQWMBD.js +0 -79
- package/dist/chunk-BPUQWMBD.js.map +0 -1
- package/dist/chunk-BVDASR3Y.js +0 -74
- package/dist/chunk-BVDASR3Y.js.map +0 -1
- package/dist/chunk-BX7A5GUV.js +0 -78
- package/dist/chunk-BX7A5GUV.js.map +0 -1
- package/dist/chunk-CDA6RYOX.js +0 -115
- package/dist/chunk-CDA6RYOX.js.map +0 -1
- package/dist/chunk-CG7O3A42.js +0 -80
- package/dist/chunk-CG7O3A42.js.map +0 -1
- package/dist/chunk-CIYGNPKT.js +0 -76
- package/dist/chunk-CIYGNPKT.js.map +0 -1
- package/dist/chunk-CKXY4FTV.js +0 -59
- package/dist/chunk-CKXY4FTV.js.map +0 -1
- package/dist/chunk-CVOKZITR.js +0 -82
- package/dist/chunk-CVOKZITR.js.map +0 -1
- package/dist/chunk-CWFMFKDI.js +0 -82
- package/dist/chunk-CWFMFKDI.js.map +0 -1
- package/dist/chunk-CWVKSV7S.js +0 -124
- package/dist/chunk-CWVKSV7S.js.map +0 -1
- package/dist/chunk-CYOLRWOX.js +0 -63
- package/dist/chunk-CYOLRWOX.js.map +0 -1
- package/dist/chunk-D23LRJT6.js +0 -116
- package/dist/chunk-D23LRJT6.js.map +0 -1
- package/dist/chunk-DFADMEJK.js +0 -127
- package/dist/chunk-DFADMEJK.js.map +0 -1
- package/dist/chunk-DKQAHZG2.js +0 -83
- package/dist/chunk-DKQAHZG2.js.map +0 -1
- package/dist/chunk-DW247T3Q.js +0 -199
- package/dist/chunk-DW247T3Q.js.map +0 -1
- package/dist/chunk-E5A7HN6H.js +0 -32
- package/dist/chunk-E5A7HN6H.js.map +0 -1
- package/dist/chunk-EI63GTN7.js +0 -57
- package/dist/chunk-EI63GTN7.js.map +0 -1
- package/dist/chunk-EP25QJ4N.js +0 -146
- package/dist/chunk-EP25QJ4N.js.map +0 -1
- package/dist/chunk-ET44426Q.js +0 -80
- package/dist/chunk-ET44426Q.js.map +0 -1
- package/dist/chunk-ETEIDY34.js +0 -67
- package/dist/chunk-ETEIDY34.js.map +0 -1
- package/dist/chunk-EU55O4P7.js +0 -76
- package/dist/chunk-EU55O4P7.js.map +0 -1
- package/dist/chunk-F436537E.js +0 -104
- package/dist/chunk-F436537E.js.map +0 -1
- package/dist/chunk-FLBTGNQI.js +0 -86
- package/dist/chunk-FLBTGNQI.js.map +0 -1
- package/dist/chunk-FUT45NFW.js +0 -46
- package/dist/chunk-FUT45NFW.js.map +0 -1
- package/dist/chunk-G3LWNTVZ.js +0 -51
- package/dist/chunk-G3LWNTVZ.js.map +0 -1
- package/dist/chunk-GBJB5WLT.js +0 -58
- package/dist/chunk-GBJB5WLT.js.map +0 -1
- package/dist/chunk-GIEPEFRX.js +0 -110
- package/dist/chunk-GIEPEFRX.js.map +0 -1
- package/dist/chunk-GSO7MISR.js +0 -58
- package/dist/chunk-GSO7MISR.js.map +0 -1
- package/dist/chunk-GUQFYUIC.js +0 -61
- package/dist/chunk-GUQFYUIC.js.map +0 -1
- package/dist/chunk-H3VJMFJQ.js +0 -35
- package/dist/chunk-H3VJMFJQ.js.map +0 -1
- package/dist/chunk-HG4WEERE.js +0 -26
- package/dist/chunk-HG4WEERE.js.map +0 -1
- package/dist/chunk-HGPBGLNP.js +0 -51
- package/dist/chunk-HGPBGLNP.js.map +0 -1
- package/dist/chunk-HQFTW7SF.js +0 -141
- package/dist/chunk-HQFTW7SF.js.map +0 -1
- package/dist/chunk-I7WYM63C.js +0 -170
- package/dist/chunk-I7WYM63C.js.map +0 -1
- package/dist/chunk-IPEYGWA7.js +0 -186
- package/dist/chunk-IPEYGWA7.js.map +0 -1
- package/dist/chunk-JQXLPVWP.js +0 -74
- package/dist/chunk-JQXLPVWP.js.map +0 -1
- package/dist/chunk-K5ARID4S.js +0 -26
- package/dist/chunk-K5ARID4S.js.map +0 -1
- package/dist/chunk-K6RTLPIJ.js +0 -41
- package/dist/chunk-K6RTLPIJ.js.map +0 -1
- package/dist/chunk-KQNKKV2C.js +0 -56
- package/dist/chunk-KQNKKV2C.js.map +0 -1
- package/dist/chunk-KRN4NE4U.js +0 -155
- package/dist/chunk-KRN4NE4U.js.map +0 -1
- package/dist/chunk-L2BI762I.js +0 -82
- package/dist/chunk-L2BI762I.js.map +0 -1
- package/dist/chunk-LEEH63B2.js +0 -56
- package/dist/chunk-LEEH63B2.js.map +0 -1
- package/dist/chunk-LHRWVM3G.js +0 -42
- package/dist/chunk-LHRWVM3G.js.map +0 -1
- package/dist/chunk-LIGWMGXM.js +0 -117
- package/dist/chunk-LIGWMGXM.js.map +0 -1
- package/dist/chunk-LKYSX3QF.js +0 -104
- package/dist/chunk-LKYSX3QF.js.map +0 -1
- package/dist/chunk-MCIFB6VS.js +0 -54
- package/dist/chunk-MCIFB6VS.js.map +0 -1
- package/dist/chunk-MI5CXMZU.js +0 -171
- package/dist/chunk-MI5CXMZU.js.map +0 -1
- package/dist/chunk-NQZYY4LR.js +0 -84
- package/dist/chunk-NQZYY4LR.js.map +0 -1
- package/dist/chunk-O23LKHUR.js +0 -66
- package/dist/chunk-O23LKHUR.js.map +0 -1
- package/dist/chunk-PASI2U2R.js +0 -23
- package/dist/chunk-PASI2U2R.js.map +0 -1
- package/dist/chunk-PPH5NTHV.js +0 -34
- package/dist/chunk-PPH5NTHV.js.map +0 -1
- package/dist/chunk-PR6OZF6D.js +0 -28
- package/dist/chunk-PR6OZF6D.js.map +0 -1
- package/dist/chunk-PWXOXPFT.js +0 -142
- package/dist/chunk-PWXOXPFT.js.map +0 -1
- package/dist/chunk-QB6BNHO3.js +0 -112
- package/dist/chunk-QB6BNHO3.js.map +0 -1
- package/dist/chunk-QJGGTIUN.js +0 -110
- package/dist/chunk-QJGGTIUN.js.map +0 -1
- package/dist/chunk-QSOIJ6J3.js +0 -91
- package/dist/chunk-QSOIJ6J3.js.map +0 -1
- package/dist/chunk-R2PAGRDP.js +0 -152
- package/dist/chunk-R2PAGRDP.js.map +0 -1
- package/dist/chunk-R63ZKLQM.js +0 -45
- package/dist/chunk-R63ZKLQM.js.map +0 -1
- package/dist/chunk-RTYYJPPE.js +0 -77
- package/dist/chunk-RTYYJPPE.js.map +0 -1
- package/dist/chunk-RVOBP7PO.js +0 -116
- package/dist/chunk-RVOBP7PO.js.map +0 -1
- package/dist/chunk-SF6R5VMQ.js +0 -97
- package/dist/chunk-SF6R5VMQ.js.map +0 -1
- package/dist/chunk-SP4CP5HY.js +0 -57
- package/dist/chunk-SP4CP5HY.js.map +0 -1
- package/dist/chunk-SWJ4EUOI.js +0 -30
- package/dist/chunk-SWJ4EUOI.js.map +0 -1
- package/dist/chunk-TK24HQJJ.js +0 -128
- package/dist/chunk-TK24HQJJ.js.map +0 -1
- package/dist/chunk-TNBJ36XJ.js +0 -156
- package/dist/chunk-TNBJ36XJ.js.map +0 -1
- package/dist/chunk-TO3UAT6O.js +0 -221
- package/dist/chunk-TO3UAT6O.js.map +0 -1
- package/dist/chunk-UAYOOTRR.js +0 -77
- package/dist/chunk-UAYOOTRR.js.map +0 -1
- package/dist/chunk-UDTAMHXW.js +0 -55
- package/dist/chunk-UDTAMHXW.js.map +0 -1
- package/dist/chunk-UOXU7NDY.js +0 -120
- package/dist/chunk-UOXU7NDY.js.map +0 -1
- package/dist/chunk-V7OOTVK3.js +0 -106
- package/dist/chunk-V7OOTVK3.js.map +0 -1
- package/dist/chunk-VI5M7KJ2.js +0 -1022
- package/dist/chunk-VI5M7KJ2.js.map +0 -1
- package/dist/chunk-VMMATOPE.js +0 -64
- package/dist/chunk-VMMATOPE.js.map +0 -1
- package/dist/chunk-W2PVSIW3.js +0 -89
- package/dist/chunk-W2PVSIW3.js.map +0 -1
- package/dist/chunk-W3DUDZDU.js +0 -88
- package/dist/chunk-W3DUDZDU.js.map +0 -1
- package/dist/chunk-WKEUU2FU.js +0 -114
- package/dist/chunk-WKEUU2FU.js.map +0 -1
- package/dist/chunk-WKLW7RC6.js +0 -28
- package/dist/chunk-WKLW7RC6.js.map +0 -1
- package/dist/chunk-WSJGZNUH.js +0 -111
- package/dist/chunk-WSJGZNUH.js.map +0 -1
- package/dist/chunk-WVPDQMC2.js +0 -144
- package/dist/chunk-WVPDQMC2.js.map +0 -1
- package/dist/chunk-WWNH5ENT.js +0 -43
- package/dist/chunk-WWNH5ENT.js.map +0 -1
- package/dist/chunk-X5L62PXY.js +0 -112
- package/dist/chunk-X5L62PXY.js.map +0 -1
- package/dist/chunk-XGCV5E6W.js +0 -133
- package/dist/chunk-XGCV5E6W.js.map +0 -1
- package/dist/chunk-XRKIEL5M.js +0 -72
- package/dist/chunk-XRKIEL5M.js.map +0 -1
- package/dist/chunk-XUJYEADU.js +0 -80
- package/dist/chunk-XUJYEADU.js.map +0 -1
- package/dist/chunk-XVYNSIQC.js +0 -116
- package/dist/chunk-XVYNSIQC.js.map +0 -1
- package/dist/chunk-XWTISHXO.js +0 -54
- package/dist/chunk-XWTISHXO.js.map +0 -1
- package/dist/chunk-YOGHS4UU.js +0 -202
- package/dist/chunk-YOGHS4UU.js.map +0 -1
- package/dist/chunk-YRSKXEOD.js +0 -135
- package/dist/chunk-YRSKXEOD.js.map +0 -1
- package/dist/chunk-ZALLCR7X.js +0 -108
- package/dist/chunk-ZALLCR7X.js.map +0 -1
- package/dist/chunk-ZDAOHMCW.js +0 -46
- package/dist/chunk-ZDAOHMCW.js.map +0 -1
- package/dist/chunk-ZESICCKK.js +0 -37
- package/dist/chunk-ZESICCKK.js.map +0 -1
- package/dist/chunk-ZIKFOD6N.js +0 -87
- package/dist/chunk-ZIKFOD6N.js.map +0 -1
- package/dist/chunk-ZJRWCQEN.js +0 -76
- package/dist/chunk-ZJRWCQEN.js.map +0 -1
- package/dist/chunk-ZSRJCIWF.js +0 -24
- package/dist/chunk-ZSRJCIWF.js.map +0 -1
- package/dist/plugin-Atb0VKtr.d.ts +0 -172
- package/dist/slide/index.d.ts +0 -212
- package/dist/slide/plugins/emoji/index.d.ts +0 -29
- package/dist/slide/plugins/math/index.d.ts +0 -13
- package/dist/slide/plugins/mermaid/index.d.ts +0 -55
- package/dist/slide/plugins/shiki/index.d.ts +0 -18
- package/dist/slide-deck/index.d.ts +0 -377
- package/dist/whiteboard/index.d.ts +0 -258
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"path": "components/composites/skill-editor/skill-editor.tsx",
|
|
23
23
|
"type": "registry:block",
|
|
24
24
|
"target": "components/blocks/skill-editor.tsx",
|
|
25
|
-
"content": "
|
|
25
|
+
"content": "\"use client\";\n\nimport { useState } from \"react\";\nimport type { FormEvent, HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { ALL_MODES, MODE_LABEL, type Mode } from \"@/types/mode\";\nimport { Button } from \"@/components/ui/button\";\nimport { FormField } from \"@/components/ui/form-field\";\nimport { Input } from \"@/components/ui/input\";\nimport { Select } from \"@/components/ui/select\";\nimport type { Skill, SkillSource, SkillState } from \"@/components/ui/skill-card\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\n/**\n * SkillEditor — form for creating or editing a Skill.\n */\n\ntype SkillDraft = Omit<Skill, \"id\"> & {\n id?: string;\n instructions?: string;\n};\n\ninterface SkillEditorProps extends Omit<HTMLAttributes<HTMLFormElement>, \"onSubmit\" | \"onChange\"> {\n initial?: Partial<Skill> & { instructions?: string };\n onSave: (draft: SkillDraft) => void;\n onCancel?: () => void;\n onDelete?: () => void;\n}\n\nconst SOURCES: Array<{ id: SkillSource; label: string }> = [\n { id: \"user\", label: \"User — defined by you\" },\n { id: \"project\", label: \"Project — lives in this workspace\" },\n { id: \"plugin\", label: \"Plugin — imported from a package\" },\n { id: \"builtin\", label: \"Built-in — shipped with Theo\" },\n];\n\nexport function SkillEditor({\n className,\n initial,\n onSave,\n onCancel,\n onDelete,\n ...formProps\n}: SkillEditorProps) {\n const [name, setName] = useState(initial?.name ?? \"\");\n const [description, setDescription] = useState(\n typeof initial?.description === \"string\" ? initial.description : \"\",\n );\n const [instructions, setInstructions] = useState(initial?.instructions ?? \"\");\n const [source, setSource] = useState<SkillSource>(initial?.source ?? \"user\");\n const [allowedToolsRaw, setAllowedToolsRaw] = useState(initial?.allowedTools?.join(\", \") ?? \"\");\n const [triggersRaw, setTriggersRaw] = useState(initial?.triggers?.join(\", \") ?? \"\");\n const [enabled, setEnabled] = useState<SkillState>(initial?.state ?? \"enabled\");\n const [modes, setModes] = useState<Mode[]>(initial?.modes ?? []);\n\n // Note: state is only seeded once on mount. To reset the form when editing\n // a different skill, use the React `key` pattern at the call site:\n // <SkillEditor key={skill.id} initial={skill} ... />\n const toggleMode = (m: Mode) =>\n setModes((prev) => (prev.includes(m) ? prev.filter((x) => x !== m) : [...prev, m]));\n\n const canSave = name.trim().length > 0;\n const handleSubmit = (e: FormEvent) => {\n e.preventDefault();\n if (!canSave) return;\n onSave({\n id: initial?.id,\n name: name.trim(),\n description: description.trim() || undefined,\n instructions: instructions.trim() || undefined,\n source,\n state: enabled,\n allowedTools: allowedToolsRaw\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean),\n triggers: triggersRaw\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean),\n modes: modes.length > 0 ? modes : undefined,\n });\n };\n\n return (\n <form\n data-slot=\"skill-editor\"\n onSubmit={handleSubmit}\n className={cn(\"flex h-full flex-col gap-4\", className)}\n {...formProps}\n >\n <FormField>\n <FormField.Label>Name</FormField.Label>\n <FormField.Control>\n <Input\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"diff-explainer\"\n required\n />\n </FormField.Control>\n <FormField.Hint>Kebab-case identifier the agent uses to invoke this skill.</FormField.Hint>\n </FormField>\n\n <FormField>\n <FormField.Label>Description</FormField.Label>\n <FormField.Control>\n <Input\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"Explain a diff in plain English.\"\n />\n </FormField.Control>\n </FormField>\n\n <FormField className=\"flex-1\">\n <FormField.Label>Instructions</FormField.Label>\n <FormField.Control>\n <Textarea\n value={instructions}\n onChange={(e) => setInstructions(e.target.value)}\n rows={6}\n placeholder=\"When invoked, read the diff via Read or Grep and produce a concise summary of intent + risk.\"\n className=\"min-h-[10rem] flex-1 font-mono text-code-sm\"\n />\n </FormField.Control>\n <FormField.Hint>Sub-prompt loaded when the skill is invoked.</FormField.Hint>\n </FormField>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <FormField>\n <FormField.Label>Source</FormField.Label>\n <FormField.Control>\n <Select\n value={source}\n onValueChange={(v) => {\n // Re-audit Issue 7: narrow Select string value against\n // SOURCES.id before casting. Silent no-op for unknown.\n const next = SOURCES.find((s) => s.id === v);\n if (next) setSource(next.id);\n }}\n >\n <Select.Trigger aria-label=\"Select skill source\">\n <Select.Value />\n </Select.Trigger>\n <Select.Content>\n {SOURCES.map((s) => (\n <Select.Item key={s.id} value={s.id}>\n {s.label}\n </Select.Item>\n ))}\n </Select.Content>\n </Select>\n </FormField.Control>\n </FormField>\n <FormField>\n <FormField.Label>Allowed tools</FormField.Label>\n <FormField.Control>\n <Input\n value={allowedToolsRaw}\n onChange={(e) => setAllowedToolsRaw(e.target.value)}\n placeholder=\"Read, Grep, Bash\"\n />\n </FormField.Control>\n </FormField>\n </div>\n\n <FormField>\n <FormField.Label>Triggers</FormField.Label>\n <FormField.Control>\n <Input\n value={triggersRaw}\n onChange={(e) => setTriggersRaw(e.target.value)}\n placeholder=\"explain diff, summarize change\"\n />\n </FormField.Control>\n <FormField.Hint>Optional keywords / patterns for auto-discovery.</FormField.Hint>\n </FormField>\n\n <FormField>\n <FormField.Label>Active modes</FormField.Label>\n <div className=\"flex flex-wrap gap-1.5\">\n {ALL_MODES.map((m) => {\n const on = modes.includes(m);\n return (\n <button\n key={m}\n type=\"button\"\n onClick={() => toggleMode(m)}\n aria-pressed={on}\n className={cn(\n \"inline-flex h-7 items-center rounded-full border px-3 font-mono text-body-sm transition-colors\",\n on\n ? \"border-primary bg-primary/15 text-primary\"\n : \"border-border/60 bg-card text-muted-foreground hover:text-foreground\",\n )}\n >\n {MODE_LABEL[m]}\n </button>\n );\n })}\n </div>\n <FormField.Hint>\n {modes.length === 0\n ? \"Empty = global (available in every mode).\"\n : `Only visible in: ${modes.map((m) => MODE_LABEL[m]).join(\", \")}.`}\n </FormField.Hint>\n </FormField>\n\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={enabled === \"enabled\"}\n onCheckedChange={(v) => setEnabled(v ? \"enabled\" : \"disabled\")}\n aria-label=\"Enabled\"\n />\n <span className=\"text-body-sm text-muted-foreground\">\n {enabled === \"enabled\"\n ? \"Enabled — available to the agent.\"\n : \"Disabled — kept but hidden from the agent.\"}\n </span>\n </div>\n\n <footer className=\"flex items-center justify-between gap-2 border-border/40 border-t pt-4\">\n <div>\n {onDelete ? (\n <Button type=\"button\" variant=\"ghost\" onClick={onDelete}>\n Delete\n </Button>\n ) : null}\n </div>\n <div className=\"flex items-center gap-2\">\n {onCancel ? (\n <Button type=\"button\" variant=\"secondary\" onClick={onCancel}>\n Cancel\n </Button>\n ) : null}\n <Button type=\"submit\" disabled={!canSave}>\n {initial?.id ? \"Save changes\" : \"Create skill\"}\n </Button>\n </div>\n </footer>\n </form>\n );\n}\n"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"path": "components/composites/skills-list/skills-list.tsx",
|
|
18
18
|
"type": "registry:block",
|
|
19
19
|
"target": "components/blocks/skills-list.tsx",
|
|
20
|
-
"content": "
|
|
20
|
+
"content": "\"use client\";\n\nimport { Search } from \"lucide-react\";\nimport { forwardRef, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { type Skill, SkillCard, type SkillState } from \"@/components/ui/skill-card\";\n\ninterface SkillsListProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"onToggle\"> {\n skills: Skill[];\n title?: ReactNode;\n /** If true, shows a search input above the grid. */\n searchable?: boolean;\n onToggle?: (id: string, next: SkillState) => void;\n}\n\n/**\n * SkillsList — grid of SkillCards with optional search + source filter chips.\n * Pairs with claw-code's `claw skills` inventory but visual.\n */\nconst SkillsList = forwardRef<HTMLDivElement, SkillsListProps>(\n ({ className, skills, title = \"Skills\", searchable = true, onToggle, ...props }, ref) => {\n const [q, setQ] = useState(\"\");\n const filtered = useMemo(() => {\n if (!q.trim()) return skills;\n const needle = q.toLowerCase();\n return skills.filter((s) => {\n const hay = [\n s.name,\n typeof s.description === \"string\" ? s.description : \"\",\n ...(s.triggers ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return hay.includes(needle);\n });\n }, [skills, q]);\n\n return (\n <section\n data-slot=\"skills-list\"\n ref={ref}\n className={cn(\"grid gap-3\", className)}\n aria-label=\"Available skills\"\n {...props}\n >\n {title || searchable ? (\n <header className=\"flex flex-wrap items-center justify-between gap-3\">\n {title ? (\n <h3 className=\"font-display text-title-md tracking-tight\">{title}</h3>\n ) : (\n <span />\n )}\n {searchable ? (\n <div className=\"relative\">\n <Search\n className=\"-translate-y-1/2 absolute top-1/2 left-2 size-3.5 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <input\n type=\"search\"\n value={q}\n onChange={(e) => setQ(e.target.value)}\n placeholder=\"Filter skills…\"\n aria-label=\"Filter skills\"\n className=\"h-8 w-56 rounded-md border border-input bg-card pr-2 pl-7 font-mono text-code-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </div>\n ) : null}\n </header>\n ) : null}\n {filtered.length === 0 ? (\n <p className=\"rounded-xl border border-border/60 border-dashed bg-muted/30 px-4 py-8 text-center font-sans text-body-sm text-muted-foreground\">\n No skills match {q ? `\"${q}\"` : \"the current filter\"}.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {filtered.map((skill) => (\n <SkillCard key={skill.id} skill={skill} {...(onToggle ? { onToggle } : {})} />\n ))}\n </div>\n )}\n </section>\n );\n },\n);\nSkillsList.displayName = \"SkillsList\";\n\nexport { SkillsList };\n"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "slide-deck",
|
|
4
4
|
"type": "registry:block",
|
|
5
5
|
"title": "SlideDeck",
|
|
6
|
-
"description": "Composite engine that orchestrates N <Slide> primitives. Keyboard / touch / hash routing, thumbnails, presenter view, fullscreen, CSS transitions, Marpit-style fragments, PDF export. Subpath-isolated bundle in @
|
|
6
|
+
"description": "Composite engine that orchestrates N <Slide> primitives. Keyboard / touch / hash routing, thumbnails, presenter view, fullscreen, CSS transitions, Marpit-style fragments, PDF export. Subpath-isolated bundle in @theokit/ui.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"zod"
|
|
9
9
|
],
|
|
@@ -16,19 +16,19 @@
|
|
|
16
16
|
"path": "components/composites/slide-deck/slide-deck.tsx",
|
|
17
17
|
"type": "registry:block",
|
|
18
18
|
"target": "components/blocks/slide-deck/slide-deck.tsx",
|
|
19
|
-
"content": "import {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n/**\n * `<SlideDeck>` — composite engine orchestrating N `<Slide>` primitives.\n *\n * Subpath-isolated at `@usetheo/ui/slide-deck` (ADR D1). Imports `<Slide>` via\n * the public package path so consumer's installed peer-deps are reused.\n *\n * See RFC 0003 and the plan in\n * `.claude/knowledge-base/plans/slide-deck-composite-plan.md`.\n *\n * Two render modes:\n * - DEFAULT layout: no children → renders canonical chrome (Slides + Controls\n * + ProgressBar + SlideNumber + PresenterView + buttons).\n * - HEADLESS: children provided → consumer composes own layout from\n * `<SlideDeck.X>` sub-components and a shared DeckContext.\n *\n * SSR-safe: parses markdown only inside `useEffect`. Hash routing uses lazy\n * `initFromHash` (D17) to avoid hydration mismatch. Reducer clamps\n * `currentIndex` whenever `slides.length` changes (EC-4 reconciliation).\n */\nimport { Slide, type SlidePlugin } from \"@/components/ui/slide/index\";\nimport { DeckContext, type DeckContextValue, useDeckContext } from \"@/components/blocks/slide-deck/context\";\nimport { Controls } from \"@/components/blocks/slide-deck/controls\";\nimport { countFragmentsInMarkdown } from \"@/components/blocks/slide-deck/fragments\";\nimport { PresenterView } from \"@/components/blocks/slide-deck/presenter-view\";\nimport { printDeck } from \"@/components/blocks/slide-deck/print-styles\";\nimport { ProgressBar } from \"@/components/blocks/slide-deck/progress-bar\";\nimport type { SlideDeckInput, SlideDeckSlide, SlideDeckTransition } from \"@/components/blocks/slide-deck/schema\";\nimport { SlideNumber } from \"@/components/blocks/slide-deck/slide-number\";\nimport { splitDeck } from \"@/components/blocks/slide-deck/split-deck\";\nimport { Thumbnails } from \"@/components/blocks/slide-deck/thumbnails\";\nimport { readInitialHash, useDeckHashRouting } from \"@/components/blocks/slide-deck/use-deck-hash-routing\";\nimport { useDeckKeyboard } from \"@/components/blocks/slide-deck/use-deck-keyboard\";\nimport { useDeckState } from \"@/components/blocks/slide-deck/use-deck-state\";\nimport { useDeckSwipe } from \"@/components/blocks/slide-deck/use-deck-swipe\";\nimport { useFullscreen } from \"@/components/blocks/slide-deck/use-fullscreen\";\n\nimport \"./transitions.css\";\n\nexport interface SlideDeckProps {\n /** Markdown string (auto-split) or pre-parsed slide array. */\n slides: SlideDeckInput;\n /** Initial slide index (0-based). Default 0. */\n initialIndex?: number;\n /** Transition preset. Default \"fade\". */\n transition?: SlideDeckTransition;\n /** Enable keyboard navigation. Default true. */\n enableKeyboard?: boolean;\n /** Enable touch/pointer swipe navigation. Default true. */\n enableTouch?: boolean;\n /** Enable hash routing (#/N). Default true. */\n enableHashRouting?: boolean;\n /** Optional unique deck id (auto-generated UUID if absent). */\n deckId?: string;\n /** Called after each navigation. */\n onIndexChange?: (index: number, slide: SlideDeckSlide | undefined) => void;\n /** Headless mode: render custom chrome using `<SlideDeck.X>` sub-components. */\n children?: ReactNode;\n /** Outer className for the deck root. */\n className?: string;\n /** Accessible label for the deck region. Defaults to \"Slide deck\". */\n \"aria-label\"?: string;\n /**\n * Rich-content plugins relayed to every inner `<Slide>` (D15 / RFC 0004).\n * Pass MEMOIZED arrays to avoid re-parses on every render.\n */\n plugins?: SlidePlugin[];\n}\n\nfunction generateDeckId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `deck-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nconst SlideDeckBase: FC<SlideDeckProps> = ({\n slides: slidesInput,\n initialIndex = 0,\n transition = \"fade\",\n enableKeyboard = true,\n enableTouch = true,\n enableHashRouting = true,\n deckId: deckIdProp,\n onIndexChange,\n children,\n className,\n \"aria-label\": ariaLabel = \"Slide deck\",\n plugins,\n}) => {\n const generatedId = useId();\n const deckId = deckIdProp ?? generatedId ?? generateDeckId();\n const rootRef = useRef<HTMLDivElement>(null);\n\n const [parsedSlides, setParsedSlides] = useState<SlideDeckSlide[]>(() => {\n return Array.isArray(slidesInput) ? slidesInput : [];\n });\n\n // Async parse when string markdown is passed. Re-run when prop changes.\n useEffect(() => {\n if (Array.isArray(slidesInput)) {\n setParsedSlides(slidesInput);\n return;\n }\n let cancelled = false;\n splitDeck(slidesInput).then(\n (result) => {\n if (!cancelled) setParsedSlides(result);\n },\n () => {\n if (!cancelled) setParsedSlides([]);\n },\n );\n return () => {\n cancelled = true;\n };\n }, [slidesInput]);\n\n const [state, dispatch] = useDeckState({\n initialIndex,\n totalSlides: parsedSlides.length,\n initFromHash: enableHashRouting ? readInitialHash : undefined,\n });\n\n // EC-4: reconcile when slides.length changes.\n const previousLengthRef = useRef(0);\n useEffect(() => {\n const length = parsedSlides.length;\n dispatch({ type: \"UPDATE_TOTAL_SLIDES\", total: length });\n // First non-empty parse → honour `initialIndex` (or hash) which was clamped\n // to 0 during initial mount when totalSlides was still 0.\n if (previousLengthRef.current === 0 && length > 0) {\n let target = initialIndex;\n if (enableHashRouting) {\n const hashIdx = readInitialHash();\n if (typeof hashIdx === \"number\") target = hashIdx;\n }\n if (target > 0) {\n dispatch({ type: \"JUMP_TO\", index: target });\n }\n }\n previousLengthRef.current = length;\n }, [parsedSlides.length, dispatch, initialIndex, enableHashRouting]);\n\n // Update totalFragmentsInCurrent when slide content changes.\n useEffect(() => {\n const current = parsedSlides[state.currentIndex];\n const count = current ? countFragmentsInMarkdown(current.markdown) : 0;\n dispatch({ type: \"UPDATE_TOTAL_FRAGMENTS\", total: count });\n }, [parsedSlides, state.currentIndex, dispatch]);\n\n const fullscreen = useFullscreen(rootRef);\n\n // Sync state.fullscreen with browser API. When user presses Esc on native UI.\n useEffect(() => {\n dispatch({ type: \"SET_FULLSCREEN\", value: fullscreen.isFullscreen });\n }, [fullscreen.isFullscreen, dispatch]);\n\n // EC-3 / D16: transition timeout fallback so rapid nav doesn't leave state stuck.\n useEffect(() => {\n if (state.transitionDirection === \"none\") return;\n const t = setTimeout(() => dispatch({ type: \"TRANSITION_END\" }), 300);\n return () => clearTimeout(t);\n }, [state.transitionDirection, dispatch]);\n\n const onPrint = useCallback(() => {\n printDeck();\n }, []);\n\n useDeckKeyboard(dispatch, {\n enabled: enableKeyboard,\n totalSlides: parsedSlides.length,\n onToggleFullscreen: fullscreen.toggle,\n onPrint,\n });\n\n useDeckSwipe(rootRef, dispatch, { enabled: enableTouch });\n\n useDeckHashRouting(dispatch, {\n enabled: enableHashRouting,\n totalSlides: parsedSlides.length,\n currentIndex: state.currentIndex,\n });\n\n // Fire onIndexChange callback when currentIndex changes.\n const lastIndexRef = useRef(state.currentIndex);\n useEffect(() => {\n if (lastIndexRef.current !== state.currentIndex) {\n lastIndexRef.current = state.currentIndex;\n onIndexChange?.(state.currentIndex, parsedSlides[state.currentIndex]);\n }\n }, [state.currentIndex, parsedSlides, onIndexChange]);\n\n const contextValue: DeckContextValue = useMemo(\n () => ({\n state,\n dispatch,\n slides: parsedSlides,\n transition,\n deckId,\n plugins,\n toggleFullscreen: fullscreen.toggle,\n print: onPrint,\n }),\n [state, dispatch, parsedSlides, transition, deckId, plugins, fullscreen.toggle, onPrint],\n );\n\n return (\n <DeckContext.Provider value={contextValue}>\n <div\n ref={rootRef}\n aria-roledescription=\"slide deck\"\n aria-label={ariaLabel}\n data-theo-slide-deck\n data-theo-slide-deck-fullscreen={state.fullscreen ? \"true\" : undefined}\n className={[\"theo-slide-deck\", className].filter(Boolean).join(\" \")}\n style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}\n >\n {/* Live region for screen readers announcing slide changes. */}\n {/* biome-ignore lint/a11y/useSemanticElements: ARIA live region needs explicit role for screen reader announcement; no native HTML equivalent for status updates from non-form, non-output content. */}\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clip: \"rect(0,0,0,0)\",\n whiteSpace: \"nowrap\",\n border: 0,\n }}\n >\n {parsedSlides.length > 0\n ? `Slide ${state.currentIndex + 1} of ${parsedSlides.length}`\n : \"Empty deck\"}\n </div>\n {children ?? <DefaultDeckLayout />}\n {/* Hidden print container — visible only during @media print. */}\n <PrintContainer slides={parsedSlides} plugins={plugins} />\n </div>\n </DeckContext.Provider>\n );\n};\n\nconst SlidesView: FC<{ className?: string }> = ({ className }) => {\n const { state, slides, transition, plugins } = useDeckContext();\n const current = slides[state.currentIndex];\n return (\n <div\n className={[\"theo-slide-deck-slide-frame\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-transition={transition}\n data-theo-slide-deck-direction={state.transitionDirection}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n minHeight: 0,\n overflow: \"hidden\",\n }}\n >\n {current ? (\n <div\n key={state.currentIndex}\n data-theo-slide-deck-slide-state=\"incoming\"\n style={{ position: \"absolute\", inset: 0 }}\n >\n <Slide\n markdown={current.markdown}\n plugins={plugins}\n aria-label={`Slide ${state.currentIndex + 1}`}\n />\n </div>\n ) : (\n <div\n data-theo-slide-deck-empty\n style={{\n display: \"grid\",\n placeItems: \"center\",\n height: \"100%\",\n opacity: 0.6,\n fontSize: 14,\n }}\n >\n Empty deck\n </div>\n )}\n </div>\n );\n};\n\nconst PresenterButton: FC<{ className?: string }> = ({ className }) => {\n const { state, dispatch } = useDeckContext();\n return (\n <button\n type=\"button\"\n onClick={() => dispatch({ type: \"TOGGLE_PRESENTER\" })}\n aria-pressed={state.presenterMode}\n aria-label={state.presenterMode ? \"Close presenter view\" : \"Open presenter view\"}\n className={[\"theo-slide-deck-presenter-button\", className].filter(Boolean).join(\" \")}\n >\n {state.presenterMode ? \"Close presenter\" : \"Presenter\"}\n </button>\n );\n};\n\nconst FullscreenButton: FC<{ className?: string }> = ({ className }) => {\n const { state, toggleFullscreen } = useDeckContext();\n return (\n <button\n type=\"button\"\n onClick={() => void toggleFullscreen()}\n aria-pressed={state.fullscreen}\n aria-label={state.fullscreen ? \"Exit fullscreen\" : \"Enter fullscreen\"}\n className={[\"theo-slide-deck-fullscreen-button\", className].filter(Boolean).join(\" \")}\n >\n {state.fullscreen ? \"Exit fullscreen\" : \"Fullscreen\"}\n </button>\n );\n};\n\nconst PrintButton: FC<{ className?: string }> = ({ className }) => {\n const { print } = useDeckContext();\n return (\n <button\n type=\"button\"\n onClick={() => print()}\n aria-label=\"Print or save deck as PDF\"\n className={[\"theo-slide-deck-print-button\", className].filter(Boolean).join(\" \")}\n >\n Print\n </button>\n );\n};\n\nconst PrintContainer: FC<{ slides: SlideDeckSlide[]; plugins?: SlidePlugin[] }> = ({\n slides,\n plugins,\n}) => {\n return (\n <div\n className=\"theo-slide-deck-print-container\"\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n inset: 0,\n pointerEvents: \"none\",\n visibility: \"hidden\",\n }}\n >\n {slides.map((slide, index) => (\n <div\n key={`print-${slide.id ?? index}`}\n className=\"theo-slide-deck-print-slide\"\n style={{ width: 1280, height: 720 }}\n >\n <Slide\n markdown={slide.markdown}\n plugins={plugins}\n aria-label={`Print slide ${index + 1}`}\n />\n </div>\n ))}\n </div>\n );\n};\n\nconst DefaultDeckLayout: FC = () => {\n return (\n <div\n className=\"theo-slide-deck-default-layout\"\n style={{\n display: \"grid\",\n gridTemplateColumns: \"1fr\",\n gridTemplateRows: \"1fr auto\",\n gap: 8,\n height: \"100%\",\n }}\n >\n <SlidesView />\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: 8,\n padding: \"4px 8px\",\n flexWrap: \"wrap\",\n }}\n >\n <Controls />\n <ProgressBar />\n <div style={{ display: \"flex\", gap: 4 }}>\n <PresenterButton />\n <FullscreenButton />\n <PrintButton />\n </div>\n </div>\n <SlideNumber />\n <PresenterView />\n </div>\n );\n};\n\ntype SlideDeckComponent = FC<SlideDeckProps> & {\n Slides: FC<{ className?: string }>;\n Controls: typeof Controls;\n ProgressBar: typeof ProgressBar;\n SlideNumber: typeof SlideNumber;\n Thumbnails: typeof Thumbnails;\n PresenterView: typeof PresenterView;\n PresenterButton: FC<{ className?: string }>;\n FullscreenButton: FC<{ className?: string }>;\n PrintButton: FC<{ className?: string }>;\n};\n\nconst SlideDeck = SlideDeckBase as SlideDeckComponent;\nSlideDeck.Slides = SlidesView;\nSlideDeck.Controls = Controls;\nSlideDeck.ProgressBar = ProgressBar;\nSlideDeck.SlideNumber = SlideNumber;\nSlideDeck.Thumbnails = Thumbnails;\nSlideDeck.PresenterView = PresenterView;\nSlideDeck.PresenterButton = PresenterButton;\nSlideDeck.FullscreenButton = FullscreenButton;\nSlideDeck.PrintButton = PrintButton;\n\nexport { SlideDeck };\n"
|
|
19
|
+
"content": "\"use client\";\n\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n/**\n * `<SlideDeck>` — composite engine orchestrating N `<Slide>` primitives.\n *\n * Subpath-isolated at `@theokit/ui/slide-deck` (ADR D1). Imports `<Slide>` via\n * the public package path so consumer's installed peer-deps are reused.\n *\n * See RFC 0003 and the plan in\n * `.claude/knowledge-base/plans/slide-deck-composite-plan.md`.\n *\n * Two render modes:\n * - DEFAULT layout: no children → renders canonical chrome (Slides + Controls\n * + ProgressBar + SlideNumber + PresenterView + buttons).\n * - HEADLESS: children provided → consumer composes own layout from\n * `<SlideDeck.X>` sub-components and a shared DeckContext.\n *\n * SSR-safe: parses markdown only inside `useEffect`. Hash routing uses lazy\n * `initFromHash` (D17) to avoid hydration mismatch. Reducer clamps\n * `currentIndex` whenever `slides.length` changes (EC-4 reconciliation).\n */\nimport { Slide, type SlidePlugin } from \"@/components/ui/slide/index\";\nimport { DeckContext, type DeckContextValue, useDeckContext } from \"@/components/blocks/slide-deck/context\";\nimport { Controls } from \"@/components/blocks/slide-deck/controls\";\nimport { countFragmentsInMarkdown } from \"@/components/blocks/slide-deck/fragments\";\nimport { PresenterView } from \"@/components/blocks/slide-deck/presenter-view\";\nimport { printDeck } from \"@/components/blocks/slide-deck/print-styles\";\nimport { ProgressBar } from \"@/components/blocks/slide-deck/progress-bar\";\nimport type { SlideDeckInput, SlideDeckSlide, SlideDeckTransition } from \"@/components/blocks/slide-deck/schema\";\nimport { SlideNumber } from \"@/components/blocks/slide-deck/slide-number\";\nimport { splitDeck } from \"@/components/blocks/slide-deck/split-deck\";\nimport { Thumbnails } from \"@/components/blocks/slide-deck/thumbnails\";\nimport { readInitialHash, useDeckHashRouting } from \"@/components/blocks/slide-deck/use-deck-hash-routing\";\nimport { useDeckKeyboard } from \"@/components/blocks/slide-deck/use-deck-keyboard\";\nimport { useDeckState } from \"@/components/blocks/slide-deck/use-deck-state\";\nimport { useDeckSwipe } from \"@/components/blocks/slide-deck/use-deck-swipe\";\nimport { useFullscreen } from \"@/components/blocks/slide-deck/use-fullscreen\";\n\nimport \"./transitions.css\";\n\nexport interface SlideDeckProps {\n /** Markdown string (auto-split) or pre-parsed slide array. */\n slides: SlideDeckInput;\n /** Initial slide index (0-based). Default 0. */\n initialIndex?: number;\n /** Transition preset. Default \"fade\". */\n transition?: SlideDeckTransition;\n /** Enable keyboard navigation. Default true. */\n enableKeyboard?: boolean;\n /** Enable touch/pointer swipe navigation. Default true. */\n enableTouch?: boolean;\n /** Enable hash routing (#/N). Default true. */\n enableHashRouting?: boolean;\n /** Optional unique deck id (auto-generated UUID if absent). */\n deckId?: string;\n /** Called after each navigation. */\n onIndexChange?: (index: number, slide: SlideDeckSlide | undefined) => void;\n /** Headless mode: render custom chrome using `<SlideDeck.X>` sub-components. */\n children?: ReactNode;\n /** Outer className for the deck root. */\n className?: string;\n /** Accessible label for the deck region. Defaults to \"Slide deck\". */\n \"aria-label\"?: string;\n /**\n * Rich-content plugins relayed to every inner `<Slide>` (D15 / RFC 0004).\n * Pass MEMOIZED arrays to avoid re-parses on every render.\n */\n plugins?: SlidePlugin[];\n}\n\nfunction generateDeckId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `deck-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nconst SlideDeckBase: FC<SlideDeckProps> = ({\n slides: slidesInput,\n initialIndex = 0,\n transition = \"fade\",\n enableKeyboard = true,\n enableTouch = true,\n enableHashRouting = true,\n deckId: deckIdProp,\n onIndexChange,\n children,\n className,\n \"aria-label\": ariaLabel = \"Slide deck\",\n plugins,\n}) => {\n const generatedId = useId();\n const deckId = deckIdProp ?? generatedId ?? generateDeckId();\n const rootRef = useRef<HTMLDivElement>(null);\n\n const [parsedSlides, setParsedSlides] = useState<SlideDeckSlide[]>(() => {\n return Array.isArray(slidesInput) ? slidesInput : [];\n });\n\n // Async parse when string markdown is passed. Re-run when prop changes.\n useEffect(() => {\n if (Array.isArray(slidesInput)) {\n setParsedSlides(slidesInput);\n return;\n }\n let cancelled = false;\n splitDeck(slidesInput).then(\n (result) => {\n if (!cancelled) setParsedSlides(result);\n },\n () => {\n if (!cancelled) setParsedSlides([]);\n },\n );\n return () => {\n cancelled = true;\n };\n }, [slidesInput]);\n\n const [state, dispatch] = useDeckState({\n initialIndex,\n totalSlides: parsedSlides.length,\n initFromHash: enableHashRouting ? readInitialHash : undefined,\n });\n\n // EC-4: reconcile when slides.length changes.\n const previousLengthRef = useRef(0);\n useEffect(() => {\n const length = parsedSlides.length;\n dispatch({ type: \"UPDATE_TOTAL_SLIDES\", total: length });\n // First non-empty parse → honour `initialIndex` (or hash) which was clamped\n // to 0 during initial mount when totalSlides was still 0.\n if (previousLengthRef.current === 0 && length > 0) {\n let target = initialIndex;\n if (enableHashRouting) {\n const hashIdx = readInitialHash();\n if (typeof hashIdx === \"number\") target = hashIdx;\n }\n if (target > 0) {\n dispatch({ type: \"JUMP_TO\", index: target });\n }\n }\n previousLengthRef.current = length;\n }, [parsedSlides.length, dispatch, initialIndex, enableHashRouting]);\n\n // Update totalFragmentsInCurrent when slide content changes.\n useEffect(() => {\n const current = parsedSlides[state.currentIndex];\n const count = current ? countFragmentsInMarkdown(current.markdown) : 0;\n dispatch({ type: \"UPDATE_TOTAL_FRAGMENTS\", total: count });\n }, [parsedSlides, state.currentIndex, dispatch]);\n\n const fullscreen = useFullscreen(rootRef);\n\n // Sync state.fullscreen with browser API. When user presses Esc on native UI.\n useEffect(() => {\n dispatch({ type: \"SET_FULLSCREEN\", value: fullscreen.isFullscreen });\n }, [fullscreen.isFullscreen, dispatch]);\n\n // EC-3 / D16: transition timeout fallback so rapid nav doesn't leave state stuck.\n useEffect(() => {\n if (state.transitionDirection === \"none\") return;\n const t = setTimeout(() => dispatch({ type: \"TRANSITION_END\" }), 300);\n return () => clearTimeout(t);\n }, [state.transitionDirection, dispatch]);\n\n const onPrint = useCallback(() => {\n printDeck();\n }, []);\n\n useDeckKeyboard(dispatch, {\n enabled: enableKeyboard,\n totalSlides: parsedSlides.length,\n onToggleFullscreen: fullscreen.toggle,\n onPrint,\n });\n\n useDeckSwipe(rootRef, dispatch, { enabled: enableTouch });\n\n useDeckHashRouting(dispatch, {\n enabled: enableHashRouting,\n totalSlides: parsedSlides.length,\n currentIndex: state.currentIndex,\n });\n\n // Fire onIndexChange callback when currentIndex changes.\n const lastIndexRef = useRef(state.currentIndex);\n useEffect(() => {\n if (lastIndexRef.current !== state.currentIndex) {\n lastIndexRef.current = state.currentIndex;\n onIndexChange?.(state.currentIndex, parsedSlides[state.currentIndex]);\n }\n }, [state.currentIndex, parsedSlides, onIndexChange]);\n\n const contextValue: DeckContextValue = useMemo(\n () => ({\n state,\n dispatch,\n slides: parsedSlides,\n transition,\n deckId,\n plugins,\n toggleFullscreen: fullscreen.toggle,\n print: onPrint,\n }),\n [state, dispatch, parsedSlides, transition, deckId, plugins, fullscreen.toggle, onPrint],\n );\n\n return (\n <DeckContext.Provider data-slot=\"slide-deck-base\" value={contextValue}>\n <div\n ref={rootRef}\n aria-roledescription=\"slide deck\"\n aria-label={ariaLabel}\n data-theo-slide-deck\n data-theo-slide-deck-fullscreen={state.fullscreen ? \"true\" : undefined}\n className={[\"theo-slide-deck\", className].filter(Boolean).join(\" \")}\n style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}\n >\n {/* Live region for screen readers announcing slide changes. */}\n {/* biome-ignore lint/a11y/useSemanticElements: ARIA live region needs explicit role for screen reader announcement; no native HTML equivalent for status updates from non-form, non-output content. */}\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clip: \"rect(0,0,0,0)\",\n whiteSpace: \"nowrap\",\n border: 0,\n }}\n >\n {parsedSlides.length > 0\n ? `Slide ${state.currentIndex + 1} of ${parsedSlides.length}`\n : \"Empty deck\"}\n </div>\n {children ?? <DefaultDeckLayout />}\n {/* Hidden print container — visible only during @media print. */}\n <PrintContainer slides={parsedSlides} plugins={plugins} />\n </div>\n </DeckContext.Provider>\n );\n};\n\nconst SlidesView: FC<{ className?: string }> = ({ className }) => {\n const { state, slides, transition, plugins } = useDeckContext();\n const current = slides[state.currentIndex];\n return (\n <div\n data-slot=\"slides-view\"\n className={[\"theo-slide-deck-slide-frame\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-transition={transition}\n data-theo-slide-deck-direction={state.transitionDirection}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n minHeight: 0,\n overflow: \"hidden\",\n }}\n >\n {current ? (\n <div\n key={state.currentIndex}\n data-theo-slide-deck-slide-state=\"incoming\"\n style={{ position: \"absolute\", inset: 0 }}\n >\n <Slide\n markdown={current.markdown}\n plugins={plugins}\n aria-label={`Slide ${state.currentIndex + 1}`}\n />\n </div>\n ) : (\n <div\n data-theo-slide-deck-empty\n style={{\n display: \"grid\",\n placeItems: \"center\",\n height: \"100%\",\n opacity: 0.6,\n fontSize: 14,\n }}\n >\n Empty deck\n </div>\n )}\n </div>\n );\n};\n\nconst PresenterButton: FC<{ className?: string }> = ({ className }) => {\n const { state, dispatch } = useDeckContext();\n return (\n <button\n data-slot=\"presenter-button\"\n type=\"button\"\n onClick={() => dispatch({ type: \"TOGGLE_PRESENTER\" })}\n aria-pressed={state.presenterMode}\n aria-label={state.presenterMode ? \"Close presenter view\" : \"Open presenter view\"}\n className={[\"theo-slide-deck-presenter-button\", className].filter(Boolean).join(\" \")}\n >\n {state.presenterMode ? \"Close presenter\" : \"Presenter\"}\n </button>\n );\n};\n\nconst FullscreenButton: FC<{ className?: string }> = ({ className }) => {\n const { state, toggleFullscreen } = useDeckContext();\n return (\n <button\n data-slot=\"fullscreen-button\"\n type=\"button\"\n onClick={() => void toggleFullscreen()}\n aria-pressed={state.fullscreen}\n aria-label={state.fullscreen ? \"Exit fullscreen\" : \"Enter fullscreen\"}\n className={[\"theo-slide-deck-fullscreen-button\", className].filter(Boolean).join(\" \")}\n >\n {state.fullscreen ? \"Exit fullscreen\" : \"Fullscreen\"}\n </button>\n );\n};\n\nconst PrintButton: FC<{ className?: string }> = ({ className }) => {\n const { print } = useDeckContext();\n return (\n <button\n data-slot=\"print-button\"\n type=\"button\"\n onClick={() => print()}\n aria-label=\"Print or save deck as PDF\"\n className={[\"theo-slide-deck-print-button\", className].filter(Boolean).join(\" \")}\n >\n Print\n </button>\n );\n};\n\nconst PrintContainer: FC<{ slides: SlideDeckSlide[]; plugins?: SlidePlugin[] }> = ({\n slides,\n plugins,\n}) => {\n return (\n <div\n data-slot=\"print-container\"\n className=\"theo-slide-deck-print-container\"\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n inset: 0,\n pointerEvents: \"none\",\n visibility: \"hidden\",\n }}\n >\n {slides.map((slide, index) => (\n <div\n key={`print-${slide.id ?? index}`}\n className=\"theo-slide-deck-print-slide\"\n style={{ width: 1280, height: 720 }}\n >\n <Slide\n markdown={slide.markdown}\n plugins={plugins}\n aria-label={`Print slide ${index + 1}`}\n />\n </div>\n ))}\n </div>\n );\n};\n\nconst DefaultDeckLayout: FC = () => {\n return (\n <div\n data-slot=\"default-deck-layout\"\n className=\"theo-slide-deck-default-layout\"\n style={{\n display: \"grid\",\n gridTemplateColumns: \"1fr\",\n gridTemplateRows: \"1fr auto\",\n gap: 8,\n height: \"100%\",\n }}\n >\n <SlidesView />\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: 8,\n padding: \"4px 8px\",\n flexWrap: \"wrap\",\n }}\n >\n <Controls />\n <ProgressBar />\n <div style={{ display: \"flex\", gap: 4 }}>\n <PresenterButton />\n <FullscreenButton />\n <PrintButton />\n </div>\n </div>\n <SlideNumber />\n <PresenterView />\n </div>\n );\n};\n\ntype SlideDeckComponent = FC<SlideDeckProps> & {\n Slides: FC<{ className?: string }>;\n Controls: typeof Controls;\n ProgressBar: typeof ProgressBar;\n SlideNumber: typeof SlideNumber;\n Thumbnails: typeof Thumbnails;\n PresenterView: typeof PresenterView;\n PresenterButton: FC<{ className?: string }>;\n FullscreenButton: FC<{ className?: string }>;\n PrintButton: FC<{ className?: string }>;\n};\n\nconst SlideDeck = SlideDeckBase as SlideDeckComponent;\nSlideDeck.Slides = SlidesView;\nSlideDeck.Controls = Controls;\nSlideDeck.ProgressBar = ProgressBar;\nSlideDeck.SlideNumber = SlideNumber;\nSlideDeck.Thumbnails = Thumbnails;\nSlideDeck.PresenterView = PresenterView;\nSlideDeck.PresenterButton = PresenterButton;\nSlideDeck.FullscreenButton = FullscreenButton;\nSlideDeck.PrintButton = PrintButton;\n\nexport { SlideDeck };\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "components/composites/slide-deck/context.tsx",
|
|
23
23
|
"type": "registry:block",
|
|
24
24
|
"target": "components/blocks/slide-deck/context.tsx",
|
|
25
|
-
"content": "/**\n * DeckContext — internal Context shared between `<SlideDeck>` and its\n * dot-namespace sub-components (`<SlideDeck.Controls>`, etc.). ADR D14.\n */\nimport { type Dispatch, createContext, useContext } from \"react\";\nimport type { SlidePlugin } from \"@/components/ui/slide/index\";\nimport type { SlideDeckSlide, SlideDeckTransition } from \"@/components/blocks/slide-deck/schema\";\nimport type { DeckAction, DeckState } from \"@/components/blocks/slide-deck/use-deck-state\";\n\nexport interface DeckContextValue {\n state: DeckState;\n dispatch: Dispatch<DeckAction>;\n slides: SlideDeckSlide[];\n transition: SlideDeckTransition;\n deckId: string;\n /** Rich-content plugins relayed to every inner `<Slide>` (D15). */\n plugins?: SlidePlugin[];\n /** Toggle browser fullscreen on the deck root. Safe to call when unsupported. */\n toggleFullscreen: () => void | Promise<void>;\n /** Trigger native print dialog with deck-specific @page CSS. */\n print: () => void;\n}\n\nexport const DeckContext = createContext<DeckContextValue | null>(null);\n\nexport function useDeckContext(): DeckContextValue {\n const ctx = useContext(DeckContext);\n if (!ctx) {\n throw new Error(\n \"SlideDeck sub-components must be used inside <SlideDeck>. \" +\n \"Wrap them in <SlideDeck slides={...}>.\",\n );\n }\n return ctx;\n}\n"
|
|
25
|
+
"content": "\"use client\";\n\n/**\n * DeckContext — internal Context shared between `<SlideDeck>` and its\n * dot-namespace sub-components (`<SlideDeck.Controls>`, etc.). ADR D14.\n */\nimport { type Dispatch, createContext, useContext } from \"react\";\nimport type { SlidePlugin } from \"@/components/ui/slide/index\";\nimport type { SlideDeckSlide, SlideDeckTransition } from \"@/components/blocks/slide-deck/schema\";\nimport type { DeckAction, DeckState } from \"@/components/blocks/slide-deck/use-deck-state\";\n\nexport interface DeckContextValue {\n state: DeckState;\n dispatch: Dispatch<DeckAction>;\n slides: SlideDeckSlide[];\n transition: SlideDeckTransition;\n deckId: string;\n /** Rich-content plugins relayed to every inner `<Slide>` (D15). */\n plugins?: SlidePlugin[];\n /** Toggle browser fullscreen on the deck root. Safe to call when unsupported. */\n toggleFullscreen: () => void | Promise<void>;\n /** Trigger native print dialog with deck-specific @page CSS. */\n print: () => void;\n}\n\nexport const DeckContext = createContext<DeckContextValue | null>(null);\n\nexport function useDeckContext(): DeckContextValue {\n const ctx = useContext(DeckContext);\n if (!ctx) {\n throw new Error(\n \"SlideDeck sub-components must be used inside <SlideDeck>. \" +\n \"Wrap them in <SlideDeck slides={...}>.\",\n );\n }\n return ctx;\n}\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "components/composites/slide-deck/controls.tsx",
|
|
29
29
|
"type": "registry:block",
|
|
30
30
|
"target": "components/blocks/slide-deck/controls.tsx",
|
|
31
|
-
"content": "/**\n * `<SlideDeck.Controls>` — prev/next buttons + slide indicator (\"3 / 12\").\n */\nimport type { FC } from \"react\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface ControlsProps {\n className?: string;\n}\n\nexport const Controls: FC<ControlsProps> = ({ className }) => {\n const { state, dispatch } = useDeckContext();\n const atStart = state.currentIndex <= 0;\n const atEnd = state.currentIndex >= state.totalSlides - 1;\n return (\n <div\n className={[\"theo-slide-deck-controls\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-controls\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n }}\n >\n <button\n type=\"button\"\n aria-label=\"Previous slide\"\n disabled={atStart}\n onClick={() => dispatch({ type: \"PREV_SLIDE\" })}\n className=\"theo-slide-deck-controls-prev\"\n >\n ←\n </button>\n <span\n aria-live=\"polite\"\n className=\"theo-slide-deck-controls-indicator\"\n style={{ fontVariantNumeric: \"tabular-nums\", minWidth: 64, textAlign: \"center\" }}\n >\n {state.totalSlides === 0 ? \"0 / 0\" : `${state.currentIndex + 1} / ${state.totalSlides}`}\n </span>\n <button\n type=\"button\"\n aria-label=\"Next slide\"\n disabled={atEnd}\n onClick={() => dispatch({ type: \"NEXT_SLIDE\" })}\n className=\"theo-slide-deck-controls-next\"\n >\n →\n </button>\n </div>\n );\n};\n"
|
|
31
|
+
"content": "/**\n * `<SlideDeck.Controls>` — prev/next buttons + slide indicator (\"3 / 12\").\n */\nimport type { FC } from \"react\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface ControlsProps {\n className?: string;\n}\n\nexport const Controls: FC<ControlsProps> = ({ className }) => {\n const { state, dispatch } = useDeckContext();\n const atStart = state.currentIndex <= 0;\n const atEnd = state.currentIndex >= state.totalSlides - 1;\n return (\n <div\n data-slot=\"controls\"\n className={[\"theo-slide-deck-controls\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-controls\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n }}\n >\n <button\n type=\"button\"\n aria-label=\"Previous slide\"\n disabled={atStart}\n onClick={() => dispatch({ type: \"PREV_SLIDE\" })}\n className=\"theo-slide-deck-controls-prev\"\n >\n ←\n </button>\n <span\n aria-live=\"polite\"\n className=\"theo-slide-deck-controls-indicator\"\n style={{ fontVariantNumeric: \"tabular-nums\", minWidth: 64, textAlign: \"center\" }}\n >\n {state.totalSlides === 0 ? \"0 / 0\" : `${state.currentIndex + 1} / ${state.totalSlides}`}\n </span>\n <button\n type=\"button\"\n aria-label=\"Next slide\"\n disabled={atEnd}\n onClick={() => dispatch({ type: \"NEXT_SLIDE\" })}\n className=\"theo-slide-deck-controls-next\"\n >\n →\n </button>\n </div>\n );\n};\n"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "components/composites/slide-deck/fragments.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"path": "components/composites/slide-deck/presenter-view.tsx",
|
|
47
47
|
"type": "registry:block",
|
|
48
48
|
"target": "components/blocks/slide-deck/presenter-view.tsx",
|
|
49
|
-
"content": "/**\n * `<SlideDeck.PresenterView>` — inline split-screen panel.\n *\n * Pragmatic v0.4 scope: renders an in-page panel (current slide + next slide +\n * speaker notes + timer) when `state.presenterMode === true`. The separate\n * window via `window.open` + `BroadcastChannel` is deferred to v0.5 (consumer\n * demand will trigger the upgrade — D6 of the plan is reduced to inline panel).\n *\n * Toggling presenter mode is dispatched via hotkey (n/N/p/P, see useDeckKeyboard).\n */\nimport { type FC, useEffect, useRef, useState } from \"react\";\nimport { Slide } from \"@/components/ui/slide/index\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface PresenterViewProps {\n className?: string;\n}\n\nfunction formatElapsed(ms: number): string {\n const s = Math.floor(ms / 1000);\n const mm = Math.floor(s / 60)\n .toString()\n .padStart(2, \"0\");\n const ss = (s % 60).toString().padStart(2, \"0\");\n return `${mm}:${ss}`;\n}\n\nexport const PresenterView: FC<PresenterViewProps> = ({ className }) => {\n const { state, slides } = useDeckContext();\n const startedAt = useRef<number | null>(null);\n const [elapsed, setElapsed] = useState(0);\n\n // Initialize the timer when presenter mode first opens; reset when closed.\n useEffect(() => {\n if (state.presenterMode) {\n if (startedAt.current === null) {\n startedAt.current = Date.now();\n }\n const interval = window.setInterval(() => {\n if (startedAt.current !== null) {\n setElapsed(Date.now() - startedAt.current);\n }\n }, 1000);\n return () => window.clearInterval(interval);\n }\n startedAt.current = null;\n setElapsed(0);\n return undefined;\n }, [state.presenterMode]);\n\n if (!state.presenterMode) return null;\n\n const current = slides[state.currentIndex];\n const next = slides[state.currentIndex + 1];\n const notes = current?.notes;\n\n return (\n <aside\n className={[\"theo-slide-deck-presenter\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-presenter\n aria-label=\"Presenter view\"\n style={{\n display: \"grid\",\n gridTemplateColumns: \"1fr 1fr\",\n gridTemplateRows: \"auto 1fr\",\n gap: 16,\n padding: 16,\n background: \"color-mix(in srgb, currentColor 6%, transparent)\",\n borderRadius: 8,\n }}\n >\n <header\n style={{\n gridColumn: \"1 / -1\",\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n }}\n >\n <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>Presenter view</h3>\n <span\n aria-label=\"Elapsed time\"\n style={{ fontVariantNumeric: \"tabular-nums\", fontSize: 14 }}\n >\n {formatElapsed(elapsed)}\n </span>\n </header>\n <section aria-label=\"Current slide preview\">\n <h4 style={{ margin: \"0 0 8px\", fontSize: 12, opacity: 0.7 }}>Current</h4>\n <div\n style={{\n aspectRatio: \"16 / 9\",\n overflow: \"hidden\",\n border: \"1px solid rgba(127,127,127,0.3)\",\n borderRadius: 6,\n }}\n >\n {current ? <Slide markdown={current.markdown} aria-label=\"Current slide\" /> : null}\n </div>\n </section>\n <section aria-label=\"Next slide preview\">\n <h4 style={{ margin: \"0 0 8px\", fontSize: 12, opacity: 0.7 }}>Next</h4>\n <div\n style={{\n aspectRatio: \"16 / 9\",\n overflow: \"hidden\",\n border: \"1px solid rgba(127,127,127,0.3)\",\n borderRadius: 6,\n opacity: next ? 1 : 0.4,\n }}\n >\n {next ? (\n <Slide markdown={next.markdown} aria-label=\"Next slide\" />\n ) : (\n <div style={{ padding: 16, fontSize: 14, opacity: 0.6 }}>End of deck</div>\n )}\n </div>\n </section>\n {notes ? (\n <section\n aria-label=\"Speaker notes\"\n style={{\n gridColumn: \"1 / -1\",\n background: \"color-mix(in srgb, currentColor 8%, transparent)\",\n padding: 12,\n borderRadius: 6,\n fontSize: 14,\n whiteSpace: \"pre-wrap\",\n }}\n >\n <strong>Notes: </strong>\n {notes}\n </section>\n ) : null}\n </aside>\n );\n};\n"
|
|
49
|
+
"content": "\"use client\";\n\n/**\n * `<SlideDeck.PresenterView>` — inline split-screen panel.\n *\n * Pragmatic v0.4 scope: renders an in-page panel (current slide + next slide +\n * speaker notes + timer) when `state.presenterMode === true`. The separate\n * window via `window.open` + `BroadcastChannel` is deferred to v0.5 (consumer\n * demand will trigger the upgrade — D6 of the plan is reduced to inline panel).\n *\n * Toggling presenter mode is dispatched via hotkey (n/N/p/P, see useDeckKeyboard).\n */\nimport { type FC, useEffect, useRef, useState } from \"react\";\nimport { Slide } from \"@/components/ui/slide/index\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface PresenterViewProps {\n className?: string;\n}\n\nfunction formatElapsed(ms: number): string {\n const s = Math.floor(ms / 1000);\n const mm = Math.floor(s / 60)\n .toString()\n .padStart(2, \"0\");\n const ss = (s % 60).toString().padStart(2, \"0\");\n return `${mm}:${ss}`;\n}\n\nexport const PresenterView: FC<PresenterViewProps> = ({ className }) => {\n const { state, slides } = useDeckContext();\n const startedAt = useRef<number | null>(null);\n const [elapsed, setElapsed] = useState(0);\n\n // Initialize the timer when presenter mode first opens; reset when closed.\n useEffect(() => {\n if (state.presenterMode) {\n if (startedAt.current === null) {\n startedAt.current = Date.now();\n }\n const interval = window.setInterval(() => {\n if (startedAt.current !== null) {\n setElapsed(Date.now() - startedAt.current);\n }\n }, 1000);\n return () => window.clearInterval(interval);\n }\n startedAt.current = null;\n setElapsed(0);\n return undefined;\n }, [state.presenterMode]);\n\n if (!state.presenterMode) return null;\n\n const current = slides[state.currentIndex];\n const next = slides[state.currentIndex + 1];\n const notes = current?.notes;\n\n return (\n <aside\n className={[\"theo-slide-deck-presenter\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-presenter\n aria-label=\"Presenter view\"\n style={{\n display: \"grid\",\n gridTemplateColumns: \"1fr 1fr\",\n gridTemplateRows: \"auto 1fr\",\n gap: 16,\n padding: 16,\n background: \"color-mix(in srgb, currentColor 6%, transparent)\",\n borderRadius: 8,\n }}\n >\n <header\n style={{\n gridColumn: \"1 / -1\",\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n }}\n >\n <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>Presenter view</h3>\n <span\n aria-label=\"Elapsed time\"\n style={{ fontVariantNumeric: \"tabular-nums\", fontSize: 14 }}\n >\n {formatElapsed(elapsed)}\n </span>\n </header>\n <section aria-label=\"Current slide preview\">\n <h4 style={{ margin: \"0 0 8px\", fontSize: 12, opacity: 0.7 }}>Current</h4>\n <div\n style={{\n aspectRatio: \"16 / 9\",\n overflow: \"hidden\",\n border: \"1px solid rgba(127,127,127,0.3)\",\n borderRadius: 6,\n }}\n >\n {current ? <Slide markdown={current.markdown} aria-label=\"Current slide\" /> : null}\n </div>\n </section>\n <section aria-label=\"Next slide preview\">\n <h4 style={{ margin: \"0 0 8px\", fontSize: 12, opacity: 0.7 }}>Next</h4>\n <div\n style={{\n aspectRatio: \"16 / 9\",\n overflow: \"hidden\",\n border: \"1px solid rgba(127,127,127,0.3)\",\n borderRadius: 6,\n opacity: next ? 1 : 0.4,\n }}\n >\n {next ? (\n <Slide markdown={next.markdown} aria-label=\"Next slide\" />\n ) : (\n <div style={{ padding: 16, fontSize: 14, opacity: 0.6 }}>End of deck</div>\n )}\n </div>\n </section>\n {notes ? (\n <section\n aria-label=\"Speaker notes\"\n style={{\n gridColumn: \"1 / -1\",\n background: \"color-mix(in srgb, currentColor 8%, transparent)\",\n padding: 12,\n borderRadius: 6,\n fontSize: 14,\n whiteSpace: \"pre-wrap\",\n }}\n >\n <strong>Notes: </strong>\n {notes}\n </section>\n ) : null}\n </aside>\n );\n};\n"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"path": "components/composites/slide-deck/print-styles.ts",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"path": "components/composites/slide-deck/progress-bar.tsx",
|
|
59
59
|
"type": "registry:block",
|
|
60
60
|
"target": "components/blocks/slide-deck/progress-bar.tsx",
|
|
61
|
-
"content": "/**\n * `<SlideDeck.ProgressBar>` — horizontal HTML5 progress element.\n */\nimport type { FC } from \"react\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface ProgressBarProps {\n className?: string;\n}\n\nexport const ProgressBar: FC<ProgressBarProps> = ({ className }) => {\n const { state } = useDeckContext();\n // Edge case: totalSlides=0 → 0% safely.\n const value = state.totalSlides === 0 ? 0 : state.currentIndex + 1;\n const max = Math.max(1, state.totalSlides);\n return (\n <progress\n className={[\"theo-slide-deck-progress\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-progress\n value={value}\n max={max}\n aria-label=\"Slide progress\"\n />\n );\n};\n"
|
|
61
|
+
"content": "/**\n * `<SlideDeck.ProgressBar>` — horizontal HTML5 progress element.\n */\nimport type { FC } from \"react\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface ProgressBarProps {\n className?: string;\n}\n\nexport const ProgressBar: FC<ProgressBarProps> = ({ className }) => {\n const { state } = useDeckContext();\n // Edge case: totalSlides=0 → 0% safely.\n const value = state.totalSlides === 0 ? 0 : state.currentIndex + 1;\n const max = Math.max(1, state.totalSlides);\n return (\n <progress\n data-slot=\"progress-bar\"\n className={[\"theo-slide-deck-progress\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-progress\n value={value}\n max={max}\n aria-label=\"Slide progress\"\n />\n );\n};\n"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"path": "components/composites/slide-deck/schema.ts",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"path": "components/composites/slide-deck/thumbnails.tsx",
|
|
83
83
|
"type": "registry:block",
|
|
84
84
|
"target": "components/blocks/slide-deck/thumbnails.tsx",
|
|
85
|
-
"content": "/**\n * `<SlideDeck.Thumbnails>` — sidebar with mini Slide instances.\n *\n * Performance strategy:\n * - Each thumbnail renders the actual `<Slide>` inside a scaled wrapper (~0.18×).\n * - IntersectionObserver lazy-loads thumbnails — off-screen ones show a\n * placeholder skeleton instead of parsing the markdown.\n * - EC-13: when IntersectionObserver is undefined (legacy env / SSR), falls\n * back to eager render. Acceptable for decks ≤ 50 slides.\n *\n * Click handler dispatches JUMP_TO. Auto-scroll keeps current thumbnail visible.\n */\nimport { type FC, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Slide } from \"@/components/ui/slide/index\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface ThumbnailsProps {\n className?: string;\n /** Scale of each thumbnail. Default 0.18. */\n scale?: number;\n}\n\nconst CANVAS_W = 1280;\nconst CANVAS_H = 720;\n\ninterface ThumbnailItemProps {\n markdown: string;\n index: number;\n isCurrent: boolean;\n scale: number;\n eager: boolean;\n onSelect: (index: number) => void;\n registerRef: (index: number, el: HTMLElement | null) => void;\n}\n\nconst ThumbnailItem: FC<ThumbnailItemProps> = ({\n markdown,\n index,\n isCurrent,\n scale,\n eager,\n onSelect,\n registerRef,\n}) => {\n const [revealed, setRevealed] = useState(eager);\n\n const setRef = useCallback(\n (el: HTMLElement | null) => {\n registerRef(index, el);\n if (eager) return;\n if (!el) return;\n if (typeof IntersectionObserver === \"undefined\") {\n // EC-13 fallback: no IO, render eagerly.\n setRevealed(true);\n return;\n }\n const io = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n setRevealed(true);\n io.disconnect();\n break;\n }\n }\n },\n { rootMargin: \"200px\" },\n );\n io.observe(el);\n },\n [index, eager, registerRef],\n );\n\n const w = Math.round(CANVAS_W * scale);\n const h = Math.round(CANVAS_H * scale);\n return (\n <button\n ref={setRef}\n type=\"button\"\n onClick={() => onSelect(index)}\n data-theo-slide-deck-thumbnail\n data-current={isCurrent || undefined}\n aria-label={`Slide ${index + 1}`}\n aria-current={isCurrent ? \"page\" : undefined}\n className=\"theo-slide-deck-thumbnail\"\n style={{\n width: w,\n height: h,\n padding: 0,\n border: isCurrent ? \"2px solid currentColor\" : \"1px solid rgba(127,127,127,0.3)\",\n borderRadius: 6,\n overflow: \"hidden\",\n position: \"relative\",\n cursor: \"pointer\",\n flexShrink: 0,\n background: \"transparent\",\n }}\n >\n <div\n style={{\n width: CANVAS_W,\n height: CANVAS_H,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n pointerEvents: \"none\",\n }}\n >\n {revealed ? (\n <Slide markdown={markdown} aria-label={`Thumbnail ${index + 1}`} />\n ) : (\n <div\n data-theo-slide-deck-thumbnail-placeholder\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"rgba(127,127,127,0.08)\",\n }}\n />\n )}\n </div>\n </button>\n );\n};\n\nexport const Thumbnails: FC<ThumbnailsProps> = ({ className, scale = 0.18 }) => {\n const { state, dispatch, slides } = useDeckContext();\n const refs = useRef<Map<number, HTMLElement>>(new Map());\n\n const registerRef = useCallback((index: number, el: HTMLElement | null) => {\n if (el) {\n refs.current.set(index, el);\n } else {\n refs.current.delete(index);\n }\n }, []);\n\n const onSelect = useCallback(\n (index: number) => {\n dispatch({ type: \"JUMP_TO\", index });\n },\n [dispatch],\n );\n\n // Auto-scroll current into view.\n useEffect(() => {\n const el = refs.current.get(state.currentIndex);\n if (el && \"scrollIntoView\" in el) {\n el.scrollIntoView({ behavior: \"smooth\", block: \"nearest\", inline: \"nearest\" });\n }\n }, [state.currentIndex]);\n\n // EC-13: eager mode when IO is absent.\n const eagerAll = useMemo(() => typeof IntersectionObserver === \"undefined\", []);\n\n return (\n <ul\n className={[\"theo-slide-deck-thumbnails\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-thumbnails\n aria-label=\"Slide thumbnails\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: 8,\n overflowY: \"auto\",\n padding: 8,\n listStyle: \"none\",\n margin: 0,\n }}\n >\n {slides.map((slide, index) => (\n <li key={`${slide.id ?? index}-${index}`}>\n <ThumbnailItem\n markdown={slide.markdown}\n index={index}\n isCurrent={index === state.currentIndex}\n scale={scale}\n eager={eagerAll || index < 3 /* first 3 always eager for snappy first paint */}\n onSelect={onSelect}\n registerRef={registerRef}\n />\n </li>\n ))}\n </ul>\n );\n};\n"
|
|
85
|
+
"content": "\"use client\";\n\n/**\n * `<SlideDeck.Thumbnails>` — sidebar with mini Slide instances.\n *\n * Performance strategy:\n * - Each thumbnail renders the actual `<Slide>` inside a scaled wrapper (~0.18×).\n * - IntersectionObserver lazy-loads thumbnails — off-screen ones show a\n * placeholder skeleton instead of parsing the markdown.\n * - EC-13: when IntersectionObserver is undefined (legacy env / SSR), falls\n * back to eager render. Acceptable for decks ≤ 50 slides.\n *\n * Click handler dispatches JUMP_TO. Auto-scroll keeps current thumbnail visible.\n */\nimport { type FC, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Slide } from \"@/components/ui/slide/index\";\nimport { useDeckContext } from \"@/components/blocks/slide-deck/context\";\n\nexport interface ThumbnailsProps {\n className?: string;\n /** Scale of each thumbnail. Default 0.18. */\n scale?: number;\n}\n\nconst CANVAS_W = 1280;\nconst CANVAS_H = 720;\n\ninterface ThumbnailItemProps {\n markdown: string;\n index: number;\n isCurrent: boolean;\n scale: number;\n eager: boolean;\n onSelect: (index: number) => void;\n registerRef: (index: number, el: HTMLElement | null) => void;\n}\n\nconst ThumbnailItem: FC<ThumbnailItemProps> = ({\n markdown,\n index,\n isCurrent,\n scale,\n eager,\n onSelect,\n registerRef,\n}) => {\n const [revealed, setRevealed] = useState(eager);\n\n const setRef = useCallback(\n (el: HTMLElement | null) => {\n registerRef(index, el);\n if (eager) return;\n if (!el) return;\n if (typeof IntersectionObserver === \"undefined\") {\n // EC-13 fallback: no IO, render eagerly.\n setRevealed(true);\n return;\n }\n const io = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n setRevealed(true);\n io.disconnect();\n break;\n }\n }\n },\n { rootMargin: \"200px\" },\n );\n io.observe(el);\n },\n [index, eager, registerRef],\n );\n\n const w = Math.round(CANVAS_W * scale);\n const h = Math.round(CANVAS_H * scale);\n return (\n <button\n data-slot=\"thumbnail-item\"\n ref={setRef}\n type=\"button\"\n onClick={() => onSelect(index)}\n data-theo-slide-deck-thumbnail\n data-current={isCurrent || undefined}\n aria-label={`Slide ${index + 1}`}\n aria-current={isCurrent ? \"page\" : undefined}\n className=\"theo-slide-deck-thumbnail\"\n style={{\n width: w,\n height: h,\n padding: 0,\n border: isCurrent ? \"2px solid currentColor\" : \"1px solid rgba(127,127,127,0.3)\",\n borderRadius: 6,\n overflow: \"hidden\",\n position: \"relative\",\n cursor: \"pointer\",\n flexShrink: 0,\n background: \"transparent\",\n }}\n >\n <div\n style={{\n width: CANVAS_W,\n height: CANVAS_H,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n pointerEvents: \"none\",\n }}\n >\n {revealed ? (\n <Slide markdown={markdown} aria-label={`Thumbnail ${index + 1}`} />\n ) : (\n <div\n data-theo-slide-deck-thumbnail-placeholder\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"rgba(127,127,127,0.08)\",\n }}\n />\n )}\n </div>\n </button>\n );\n};\n\nexport const Thumbnails: FC<ThumbnailsProps> = ({ className, scale = 0.18 }) => {\n const { state, dispatch, slides } = useDeckContext();\n const refs = useRef<Map<number, HTMLElement>>(new Map());\n\n const registerRef = useCallback((index: number, el: HTMLElement | null) => {\n if (el) {\n refs.current.set(index, el);\n } else {\n refs.current.delete(index);\n }\n }, []);\n\n const onSelect = useCallback(\n (index: number) => {\n dispatch({ type: \"JUMP_TO\", index });\n },\n [dispatch],\n );\n\n // Auto-scroll current into view.\n useEffect(() => {\n const el = refs.current.get(state.currentIndex);\n if (el && \"scrollIntoView\" in el) {\n el.scrollIntoView({ behavior: \"smooth\", block: \"nearest\", inline: \"nearest\" });\n }\n }, [state.currentIndex]);\n\n // EC-13: eager mode when IO is absent.\n const eagerAll = useMemo(() => typeof IntersectionObserver === \"undefined\", []);\n\n return (\n <ul\n data-slot=\"thumbnails\"\n className={[\"theo-slide-deck-thumbnails\", className].filter(Boolean).join(\" \")}\n data-theo-slide-deck-thumbnails\n aria-label=\"Slide thumbnails\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: 8,\n overflowY: \"auto\",\n padding: 8,\n listStyle: \"none\",\n margin: 0,\n }}\n >\n {slides.map((slide, index) => (\n <li key={`${slide.id ?? index}-${index}`}>\n <ThumbnailItem\n markdown={slide.markdown}\n index={index}\n isCurrent={index === state.currentIndex}\n scale={scale}\n eager={eagerAll || index < 3 /* first 3 always eager for snappy first paint */}\n onSelect={onSelect}\n registerRef={registerRef}\n />\n </li>\n ))}\n </ul>\n );\n};\n"
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"path": "components/composites/slide-deck/transitions.css",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"path": "components/composites/slide-deck/index.ts",
|
|
125
125
|
"type": "registry:block",
|
|
126
126
|
"target": "components/blocks/slide-deck/index.ts",
|
|
127
|
-
"content": "/**\n * Public surface of `@
|
|
127
|
+
"content": "/**\n * Public surface of `@theokit/ui/slide-deck`. Subpath-isolated composite engine.\n */\nexport { SlideDeck } from \"@/components/blocks/slide-deck/slide-deck\";\nexport type { SlideDeckProps } from \"@/components/blocks/slide-deck/slide-deck\";\nexport {\n slideDeckInput,\n slideDeckSlide,\n slideDeckTransition,\n type SlideDeckInput,\n type SlideDeckSlide,\n type SlideDeckTransition,\n} from \"@/components/blocks/slide-deck/schema\";\nexport { splitDeck } from \"@/components/blocks/slide-deck/split-deck\";\nexport { extractNotes } from \"@/components/blocks/slide-deck/notes\";\nexport { countFragmentsInMarkdown } from \"@/components/blocks/slide-deck/fragments\";\nexport {\n formatHash,\n readHashIndex,\n readInitialHash,\n useDeckHashRouting,\n} from \"@/components/blocks/slide-deck/use-deck-hash-routing\";\nexport {\n deckReducer,\n useDeckState,\n type DeckAction,\n type DeckState,\n} from \"@/components/blocks/slide-deck/use-deck-state\";\nexport { useDeckKeyboard } from \"@/components/blocks/slide-deck/use-deck-keyboard\";\nexport { useDeckSwipe } from \"@/components/blocks/slide-deck/use-deck-swipe\";\nexport { useFullscreen } from \"@/components/blocks/slide-deck/use-fullscreen\";\nexport {\n injectPrintStyles,\n printDeck,\n removePrintStyles,\n} from \"@/components/blocks/slide-deck/print-styles\";\nexport { Controls } from \"@/components/blocks/slide-deck/controls\";\nexport { ProgressBar } from \"@/components/blocks/slide-deck/progress-bar\";\nexport { SlideNumber } from \"@/components/blocks/slide-deck/slide-number\";\nexport { Thumbnails } from \"@/components/blocks/slide-deck/thumbnails\";\nexport { PresenterView } from \"@/components/blocks/slide-deck/presenter-view\";\nexport {\n DeckContext,\n useDeckContext,\n type DeckContextValue,\n} from \"@/components/blocks/slide-deck/context\";\n"
|
|
128
128
|
}
|
|
129
129
|
]
|
|
130
130
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"path": "components/primitives/slide/plugins/mermaid/index.tsx",
|
|
18
18
|
"type": "registry:ui",
|
|
19
19
|
"target": "components/ui/slide/plugins/mermaid/index.tsx",
|
|
20
|
-
"content": "import type { Element, Root as HastRoot } from \"hast\";\n/**\n * Slide rich-content plugin — Mermaid diagrams.\n *\n * Peer-dep: `mermaid` (optional, ~370 KB raw — lazy + opt-in mandatory).\n * Render is client-only because Mermaid measures DOM nodes during layout;\n * SSR / static render emits a labelled placeholder + source-code fallback.\n *\n * Pipeline contribution (D11 of the rich-content plan):\n * - `hastTransform`: detect `<pre><code class=\"language-mermaid\">…</code></pre>`\n * and replace with `<div class=\"theo-slide-mermaid-host\" data-theo-mermaid-source=\"…\">`.\n * - `components.div`: when div has `data-theo-mermaid-source`, render the\n * `<MermaidDiagram>` React component that lazy-imports `mermaid` and\n * injects the SVG via `dangerouslySetInnerHTML` after `mermaid.render()`.\n *\n * EC-4: complete SVG tag + attribute allow-list (~30 tags) so the rendered SVG\n * survives sanitize. Critical: without these, sanitize strips the SVG entirely.\n *\n * EC-10: SSR placeholder is distinguishable from a render error. Error path\n * surfaces the Mermaid source code in a `<pre>` so consumers can debug + the\n * print path still shows the code (PDF cannot run dynamic Mermaid).\n */\nimport { type FC, useEffect, useState } from \"react\";\nimport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\n\n// EC-4: comprehensive SVG tag list (mermaid 11.x output across diagram types).\nconst SVG_TAG_NAMES = [\n \"div\",\n // Custom element used to bridge into our React renderer (mapped via components).\n \"theo-mermaid\",\n // root + grouping\n \"svg\",\n \"g\",\n \"defs\",\n \"use\",\n \"symbol\",\n \"marker\",\n \"pattern\",\n \"mask\",\n \"clipPath\",\n // shapes\n \"path\",\n \"rect\",\n \"circle\",\n \"ellipse\",\n \"line\",\n \"polyline\",\n \"polygon\",\n // text\n \"text\",\n \"tspan\",\n \"textPath\",\n \"title\",\n \"desc\",\n // gradients + filters (used by some flowchart themes)\n \"linearGradient\",\n \"radialGradient\",\n \"stop\",\n \"filter\",\n \"feGaussianBlur\",\n \"feOffset\",\n \"feColorMatrix\",\n \"feComponentTransfer\",\n \"feComposite\",\n \"feMerge\",\n \"feMergeNode\",\n \"feFlood\",\n // foreign content (HTML labels)\n \"foreignObject\",\n \"span\",\n \"br\",\n];\n\nconst SVG_ATTRIBUTES: Record<string, string[]> = {\n \"*\": [\n \"id\",\n \"className\",\n \"style\",\n \"transform\",\n \"fill\",\n \"stroke\",\n \"strokeWidth\",\n \"strokeDasharray\",\n \"strokeLinecap\",\n \"strokeLinejoin\",\n \"opacity\",\n \"fillOpacity\",\n \"strokeOpacity\",\n \"ariaLabel\",\n \"ariaHidden\",\n \"role\",\n ],\n svg: [\"xmlns\", \"viewBox\", \"width\", \"height\", \"preserveAspectRatio\", \"xmlnsXlink\", \"version\"],\n path: [\"d\", \"markerEnd\", \"markerStart\", \"markerMid\"],\n rect: [\"x\", \"y\", \"width\", \"height\", \"rx\", \"ry\"],\n circle: [\"cx\", \"cy\", \"r\"],\n ellipse: [\"cx\", \"cy\", \"rx\", \"ry\"],\n line: [\"x1\", \"y1\", \"x2\", \"y2\"],\n polyline: [\"points\"],\n polygon: [\"points\"],\n text: [\n \"x\",\n \"y\",\n \"dx\",\n \"dy\",\n \"textAnchor\",\n \"dominantBaseline\",\n \"fontSize\",\n \"fontFamily\",\n \"fontWeight\",\n ],\n tspan: [\"x\", \"y\", \"dx\", \"dy\"],\n textPath: [\"xlinkHref\", \"href\", \"startOffset\"],\n marker: [\"markerUnits\", \"markerWidth\", \"markerHeight\", \"refX\", \"refY\", \"orient\", \"viewBox\"],\n use: [\"xlinkHref\", \"href\", \"x\", \"y\", \"width\", \"height\"],\n linearGradient: [\"x1\", \"y1\", \"x2\", \"y2\", \"gradientUnits\"],\n radialGradient: [\"cx\", \"cy\", \"r\", \"fx\", \"fy\", \"gradientUnits\"],\n stop: [\"offset\", \"stopColor\", \"stopOpacity\"],\n foreignObject: [\"x\", \"y\", \"width\", \"height\"],\n div: [\"data-state\", \"data-theo-mermaid-source\", \"className\"],\n \"theo-mermaid\": [\"source\"],\n};\n\nexport interface MermaidPluginOptions {\n /** Mermaid theme name. Defaults to `\"default\"`. */\n theme?: \"default\" | \"forest\" | \"dark\" | \"neutral\" | \"base\";\n}\n\n/**\n * React component that renders Mermaid lazily on mount.\n *\n * SSR / pre-load: renders the source as `<pre>` + `role=\"img\"` for a11y.\n * Client: lazy-loads `mermaid`, calls `mermaid.render()`, then injects the\n * resulting SVG via `dangerouslySetInnerHTML` so React owns the DOM\n * subtree (avoids reconciliation crashes when state flips back to the\n * placeholder — `ref.innerHTML` would mutate under React's feet).\n *\n * The Mermaid output is a TRUSTED string from the `mermaid` lib itself (not\n * user-controlled markup) — the user-supplied source is the DSL, not raw HTML.\n * Using dangerouslySetInnerHTML here is a controlled escape hatch.\n *\n * Peer-dep guard: if `import(\"mermaid\")` fails, falls back to the same\n * pre-formatted source view + an `aria-label` hint (EC-10).\n */\nexport const MermaidDiagram: FC<{ source: string; theme?: string }> = ({ source, theme }) => {\n const [svg, setSvg] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n // Reset state when source/theme changes so the placeholder shows again\n // while the new diagram renders.\n setSvg(null);\n setError(null);\n (async () => {\n try {\n // biome-ignore lint/suspicious/noExplicitAny: mermaid default export untyped\n const mermaid: any = (await import(\"mermaid\")).default;\n if (cancelled) return;\n mermaid.initialize({ startOnLoad: false, theme: theme ?? \"default\" });\n const id = `theo-mmd-${Math.random().toString(36).slice(2, 10)}`;\n const result = await mermaid.render(id, source);\n if (!cancelled) {\n setSvg(result.svg);\n }\n } catch (e) {\n if (cancelled) return;\n const msg = e instanceof Error ? e.message : String(e);\n // Recognize the various module-resolution failure messages emitted by\n // Node, Vite, Rollup, Webpack, Vitest, and browser dynamic-import.\n const moduleNotFound =\n msg.includes(\"Cannot find module\") ||\n msg.includes(\"Could not resolve\") ||\n msg.includes(\"Failed to fetch\") ||\n msg.includes(\"Failed to resolve module\") ||\n msg.includes(\"ERR_MODULE_NOT_FOUND\");\n if (moduleNotFound) {\n setError(\"Mermaid not installed. Run: pnpm add mermaid\");\n } else {\n setError(`Mermaid render failed: ${msg}`);\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [source, theme]);\n\n if (error) {\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"error\"\n role=\"img\"\n aria-label={error}\n className=\"theo-slide-mermaid-host\"\n >\n <pre style={{ fontSize: \"0.8em\", opacity: 0.7, whiteSpace: \"pre-wrap\" }}>{source}</pre>\n </div>\n );\n }\n if (svg) {\n // SVG is React-owned via dangerouslySetInnerHTML so the next state change\n // (e.g. new `source` prop → back to placeholder) can unmount cleanly.\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"ready\"\n role=\"img\"\n aria-label=\"Mermaid diagram\"\n className=\"theo-slide-mermaid-host\"\n // biome-ignore lint/security/noDangerouslySetInnerHtml: mermaid.render() output is trusted (lib-generated SVG, not user markup)\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n }\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"loading\"\n role=\"img\"\n aria-label=\"Mermaid diagram\"\n className=\"theo-slide-mermaid-host\"\n >\n <pre style={{ fontSize: \"0.7em\", opacity: 0.4, whiteSpace: \"pre-wrap\" }}>{source}</pre>\n </div>\n );\n};\n\nexport function mermaidPlugin(opts: MermaidPluginOptions = {}): SlidePlugin {\n return {\n name: \"mermaid\",\n sanitizeSchemaExtension: {\n tagNames: SVG_TAG_NAMES,\n attributes: SVG_ATTRIBUTES,\n },\n components: {\n // The hastTransform below replaces mermaid <pre> with a `<div>` carrying\n // `data-theo-mermaid-source`. We can't override `div` globally — instead,\n // we use a custom tagName `theo-mermaid` that the React renderer maps.\n \"theo-mermaid\": ((props: { source?: string }) => (\n <MermaidDiagram source={props.source ?? \"\"} theme={opts.theme} />\n )) as FC<unknown>,\n },\n async hastTransform(tree: HastRoot): Promise<HastRoot> {\n // unist-util-visit is a peer-dep of the slide stack — should always be\n // present. Guard anyway per EC-2.\n // biome-ignore lint/suspicious/noExplicitAny: unist-util-visit untyped at runtime\n let visit: any;\n try {\n visit = (await import(\"unist-util-visit\")).visit;\n } catch (e) {\n throw new Error(`[slide/plugins/mermaid] peer-dep 'unist-util-visit' missing: ${e}`);\n }\n visit(\n tree,\n \"element\",\n (\n node: Element,\n _idx: number | undefined,\n parent: { tagName?: string; type?: string; children?: unknown[] } | undefined,\n ) => {\n if (node.tagName !== \"code\") return;\n const className = (node.properties?.className as string[] | undefined) ?? [];\n if (!className.includes(\"language-mermaid\")) return;\n if (!parent || parent.tagName !== \"pre\") return;\n const codeNode = node.children?.[0];\n const source = codeNode && codeNode.type === \"text\" ? codeNode.value : \"\";\n // Replace the entire <pre> with our custom element. hast-util-to-jsx-runtime\n // maps element tagName to a React component if found in `components` map.\n Object.assign(parent, {\n type: \"element\",\n tagName: \"theo-mermaid\",\n // Note: `source` is passed as a non-standard property; the React\n // component picks it up. We avoid `data-*` attributes here so we\n // don't have to whitelist them in sanitize.\n properties: { source },\n children: [],\n });\n },\n );\n return tree;\n },\n };\n}\n"
|
|
20
|
+
"content": "\"use client\";\n\nimport type { Element, Root as HastRoot } from \"hast\";\n/**\n * Slide rich-content plugin — Mermaid diagrams.\n *\n * Peer-dep: `mermaid` (optional, ~370 KB raw — lazy + opt-in mandatory).\n * Render is client-only because Mermaid measures DOM nodes during layout;\n * SSR / static render emits a labelled placeholder + source-code fallback.\n *\n * Pipeline contribution (D11 of the rich-content plan):\n * - `hastTransform`: detect `<pre><code class=\"language-mermaid\">…</code></pre>`\n * and replace with `<div class=\"theo-slide-mermaid-host\" data-theo-mermaid-source=\"…\">`.\n * - `components.div`: when div has `data-theo-mermaid-source`, render the\n * `<MermaidDiagram>` React component that lazy-imports `mermaid` and\n * injects the SVG via `dangerouslySetInnerHTML` after `mermaid.render()`.\n *\n * EC-4: complete SVG tag + attribute allow-list (~30 tags) so the rendered SVG\n * survives sanitize. Critical: without these, sanitize strips the SVG entirely.\n *\n * EC-10: SSR placeholder is distinguishable from a render error. Error path\n * surfaces the Mermaid source code in a `<pre>` so consumers can debug + the\n * print path still shows the code (PDF cannot run dynamic Mermaid).\n */\nimport { type FC, useEffect, useState } from \"react\";\nimport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\n\n// EC-4: comprehensive SVG tag list (mermaid 11.x output across diagram types).\nconst SVG_TAG_NAMES = [\n \"div\",\n // Custom element used to bridge into our React renderer (mapped via components).\n \"theo-mermaid\",\n // root + grouping\n \"svg\",\n \"g\",\n \"defs\",\n \"use\",\n \"symbol\",\n \"marker\",\n \"pattern\",\n \"mask\",\n \"clipPath\",\n // shapes\n \"path\",\n \"rect\",\n \"circle\",\n \"ellipse\",\n \"line\",\n \"polyline\",\n \"polygon\",\n // text\n \"text\",\n \"tspan\",\n \"textPath\",\n \"title\",\n \"desc\",\n // gradients + filters (used by some flowchart themes)\n \"linearGradient\",\n \"radialGradient\",\n \"stop\",\n \"filter\",\n \"feGaussianBlur\",\n \"feOffset\",\n \"feColorMatrix\",\n \"feComponentTransfer\",\n \"feComposite\",\n \"feMerge\",\n \"feMergeNode\",\n \"feFlood\",\n // foreign content (HTML labels)\n \"foreignObject\",\n \"span\",\n \"br\",\n];\n\nconst SVG_ATTRIBUTES: Record<string, string[]> = {\n \"*\": [\n \"id\",\n \"className\",\n \"style\",\n \"transform\",\n \"fill\",\n \"stroke\",\n \"strokeWidth\",\n \"strokeDasharray\",\n \"strokeLinecap\",\n \"strokeLinejoin\",\n \"opacity\",\n \"fillOpacity\",\n \"strokeOpacity\",\n \"ariaLabel\",\n \"ariaHidden\",\n \"role\",\n ],\n svg: [\"xmlns\", \"viewBox\", \"width\", \"height\", \"preserveAspectRatio\", \"xmlnsXlink\", \"version\"],\n path: [\"d\", \"markerEnd\", \"markerStart\", \"markerMid\"],\n rect: [\"x\", \"y\", \"width\", \"height\", \"rx\", \"ry\"],\n circle: [\"cx\", \"cy\", \"r\"],\n ellipse: [\"cx\", \"cy\", \"rx\", \"ry\"],\n line: [\"x1\", \"y1\", \"x2\", \"y2\"],\n polyline: [\"points\"],\n polygon: [\"points\"],\n text: [\n \"x\",\n \"y\",\n \"dx\",\n \"dy\",\n \"textAnchor\",\n \"dominantBaseline\",\n \"fontSize\",\n \"fontFamily\",\n \"fontWeight\",\n ],\n tspan: [\"x\", \"y\", \"dx\", \"dy\"],\n textPath: [\"xlinkHref\", \"href\", \"startOffset\"],\n marker: [\"markerUnits\", \"markerWidth\", \"markerHeight\", \"refX\", \"refY\", \"orient\", \"viewBox\"],\n use: [\"xlinkHref\", \"href\", \"x\", \"y\", \"width\", \"height\"],\n linearGradient: [\"x1\", \"y1\", \"x2\", \"y2\", \"gradientUnits\"],\n radialGradient: [\"cx\", \"cy\", \"r\", \"fx\", \"fy\", \"gradientUnits\"],\n stop: [\"offset\", \"stopColor\", \"stopOpacity\"],\n foreignObject: [\"x\", \"y\", \"width\", \"height\"],\n div: [\"data-state\", \"data-theo-mermaid-source\", \"className\"],\n \"theo-mermaid\": [\"source\"],\n};\n\nexport interface MermaidPluginOptions {\n /** Mermaid theme name. Defaults to `\"default\"`. */\n theme?: \"default\" | \"forest\" | \"dark\" | \"neutral\" | \"base\";\n}\n\n/**\n * React component that renders Mermaid lazily on mount.\n *\n * SSR / pre-load: renders the source as `<pre>` + `role=\"img\"` for a11y.\n * Client: lazy-loads `mermaid`, calls `mermaid.render()`, then injects the\n * resulting SVG via `dangerouslySetInnerHTML` so React owns the DOM\n * subtree (avoids reconciliation crashes when state flips back to the\n * placeholder — `ref.innerHTML` would mutate under React's feet).\n *\n * The Mermaid output is a TRUSTED string from the `mermaid` lib itself (not\n * user-controlled markup) — the user-supplied source is the DSL, not raw HTML.\n * Using dangerouslySetInnerHTML here is a controlled escape hatch.\n *\n * Peer-dep guard: if `import(\"mermaid\")` fails, falls back to the same\n * pre-formatted source view + an `aria-label` hint (EC-10).\n */\nexport const MermaidDiagram: FC<{ source: string; theme?: string }> = ({ source, theme }) => {\n const [svg, setSvg] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n // Reset state when source/theme changes so the placeholder shows again\n // while the new diagram renders.\n setSvg(null);\n setError(null);\n (async () => {\n try {\n // biome-ignore lint/suspicious/noExplicitAny: mermaid default export untyped\n const mermaid: any = (await import(\"mermaid\")).default;\n if (cancelled) return;\n mermaid.initialize({ startOnLoad: false, theme: theme ?? \"default\" });\n const id = `theo-mmd-${Math.random().toString(36).slice(2, 10)}`;\n const result = await mermaid.render(id, source);\n if (!cancelled) {\n setSvg(result.svg);\n }\n } catch (e) {\n if (cancelled) return;\n const msg = e instanceof Error ? e.message : String(e);\n // Recognize the various module-resolution failure messages emitted by\n // Node, Vite, Rollup, Webpack, Vitest, and browser dynamic-import.\n const moduleNotFound =\n msg.includes(\"Cannot find module\") ||\n msg.includes(\"Could not resolve\") ||\n msg.includes(\"Failed to fetch\") ||\n msg.includes(\"Failed to resolve module\") ||\n msg.includes(\"ERR_MODULE_NOT_FOUND\");\n if (moduleNotFound) {\n setError(\"Mermaid not installed. Run: pnpm add mermaid\");\n } else {\n setError(`Mermaid render failed: ${msg}`);\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [source, theme]);\n\n if (error) {\n return (\n <div\n data-slot=\"mermaid-diagram\"\n data-theo-slide-mermaid\n data-state=\"error\"\n role=\"img\"\n aria-label={error}\n className=\"theo-slide-mermaid-host\"\n >\n <pre style={{ fontSize: \"0.8em\", opacity: 0.7, whiteSpace: \"pre-wrap\" }}>{source}</pre>\n </div>\n );\n }\n if (svg) {\n // SVG is React-owned via dangerouslySetInnerHTML so the next state change\n // (e.g. new `source` prop → back to placeholder) can unmount cleanly.\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"ready\"\n role=\"img\"\n aria-label=\"Mermaid diagram\"\n className=\"theo-slide-mermaid-host\"\n // biome-ignore lint/security/noDangerouslySetInnerHtml: mermaid.render() output is trusted (lib-generated SVG, not user markup)\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n }\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"loading\"\n role=\"img\"\n aria-label=\"Mermaid diagram\"\n className=\"theo-slide-mermaid-host\"\n >\n <pre style={{ fontSize: \"0.7em\", opacity: 0.4, whiteSpace: \"pre-wrap\" }}>{source}</pre>\n </div>\n );\n};\n\nexport function mermaidPlugin(opts: MermaidPluginOptions = {}): SlidePlugin {\n return {\n name: \"mermaid\",\n sanitizeSchemaExtension: {\n tagNames: SVG_TAG_NAMES,\n attributes: SVG_ATTRIBUTES,\n },\n components: {\n // The hastTransform below replaces mermaid <pre> with a `<div>` carrying\n // `data-theo-mermaid-source`. We can't override `div` globally — instead,\n // we use a custom tagName `theo-mermaid` that the React renderer maps.\n \"theo-mermaid\": ((props: { source?: string }) => (\n <MermaidDiagram source={props.source ?? \"\"} theme={opts.theme} />\n )) as FC<unknown>,\n },\n async hastTransform(tree: HastRoot): Promise<HastRoot> {\n // unist-util-visit is a peer-dep of the slide stack — should always be\n // present. Guard anyway per EC-2.\n // biome-ignore lint/suspicious/noExplicitAny: unist-util-visit untyped at runtime\n let visit: any;\n try {\n visit = (await import(\"unist-util-visit\")).visit;\n } catch (e) {\n throw new Error(`[slide/plugins/mermaid] peer-dep 'unist-util-visit' missing: ${e}`);\n }\n visit(\n tree,\n \"element\",\n (\n node: Element,\n _idx: number | undefined,\n parent: { tagName?: string; type?: string; children?: unknown[] } | undefined,\n ) => {\n if (node.tagName !== \"code\") return;\n const className = (node.properties?.className as string[] | undefined) ?? [];\n if (!className.includes(\"language-mermaid\")) return;\n if (!parent || parent.tagName !== \"pre\") return;\n const codeNode = node.children?.[0];\n const source = codeNode && codeNode.type === \"text\" ? codeNode.value : \"\";\n // Replace the entire <pre> with our custom element. hast-util-to-jsx-runtime\n // maps element tagName to a React component if found in `components` map.\n Object.assign(parent, {\n type: \"element\",\n tagName: \"theo-mermaid\",\n // Note: `source` is passed as a non-standard property; the React\n // component picks it up. We avoid `data-*` attributes here so we\n // don't have to whitelist them in sanitize.\n properties: { source },\n children: [],\n });\n },\n );\n return tree;\n },\n };\n}\n"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"path": "components/primitives/slide/plugins/shiki/index.ts",
|
|
18
18
|
"type": "registry:ui",
|
|
19
19
|
"target": "components/ui/slide/plugins/shiki/index.ts",
|
|
20
|
-
"content": "/**\n * Slide rich-content plugin — Shiki syntax highlighting.\n *\n * Lazy + opt-in (ADR D9, D13 of the rich-content plan). Peer-dep `shiki` is\n * declared optional in `package.json`; the plugin guards every dynamic import\n * so a missing peer-dep is reported (D16) rather than crashing the slide.\n *\n * Pipeline contribution:\n * - `hastTransform`: walks `<pre><code class=\"language-XXX\">` nodes, replaces\n * them with the pre-rendered `<pre><code>` tree emitted by Shiki.\n * - `sanitizeSchemaExtension`: allows `<span>` with `style` and `class`\n * (Shiki emits inline styles for tokens).\n *\n * Performance:\n * - Highlighter is created lazily on first use and cached.\n * - Grammars listed in `langs` are pre-loaded once; unknown langs pass-through\n * as plain `<pre><code>` (defaultSchema allows that).\n *\n * @example\n * import { shikiPlugin } from \"@
|
|
20
|
+
"content": "/**\n * Slide rich-content plugin — Shiki syntax highlighting.\n *\n * Lazy + opt-in (ADR D9, D13 of the rich-content plan). Peer-dep `shiki` is\n * declared optional in `package.json`; the plugin guards every dynamic import\n * so a missing peer-dep is reported (D16) rather than crashing the slide.\n *\n * Pipeline contribution:\n * - `hastTransform`: walks `<pre><code class=\"language-XXX\">` nodes, replaces\n * them with the pre-rendered `<pre><code>` tree emitted by Shiki.\n * - `sanitizeSchemaExtension`: allows `<span>` with `style` and `class`\n * (Shiki emits inline styles for tokens).\n *\n * Performance:\n * - Highlighter is created lazily on first use and cached.\n * - Grammars listed in `langs` are pre-loaded once; unknown langs pass-through\n * as plain `<pre><code>` (defaultSchema allows that).\n *\n * @example\n * import { shikiPlugin } from \"@theokit/ui/slide/plugins/shiki\";\n * import \"shiki\"; // installed as peer-dep\n * <Slide markdown={md} plugins={[shikiPlugin({ langs: [\"ts\",\"python\"] })]} />\n */\nimport type { Element, Root as HastRoot } from \"hast\";\nimport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\n\nexport interface ShikiPluginOptions {\n /** Dual-theme map — light/dark Shiki theme names. Defaults to `github-light` / `github-dark`. */\n themes?: { light: string; dark: string };\n /** Languages to pre-load. Unknown languages pass-through. Defaults to a tech-stack baseline. */\n langs?: string[];\n}\n\nconst DEFAULT_LANGS = [\n \"ts\",\n \"tsx\",\n \"js\",\n \"jsx\",\n \"python\",\n \"go\",\n \"rust\",\n \"java\",\n \"json\",\n \"yaml\",\n \"bash\",\n \"shell\",\n \"html\",\n \"css\",\n \"sql\",\n \"markdown\",\n];\n\nexport function shikiPlugin(opts: ShikiPluginOptions = {}): SlidePlugin {\n const themes = opts.themes ?? { light: \"github-light\", dark: \"github-dark\" };\n const langs = opts.langs ?? DEFAULT_LANGS;\n // biome-ignore lint/suspicious/noExplicitAny: shiki has no exported type for `Highlighter` at the top level\n let highlighter: any = null;\n let peerDepMissing = false;\n\n // biome-ignore lint/suspicious/noExplicitAny: shiki Highlighter is untyped here\n async function getHighlighter(): Promise<any> {\n if (highlighter) return highlighter;\n if (peerDepMissing) return null;\n try {\n const shiki = await import(\"shiki\");\n highlighter = await shiki.createHighlighter({\n themes: [themes.light, themes.dark],\n langs,\n });\n return highlighter;\n } catch (e) {\n peerDepMissing = true;\n // Surface via D16 path (composePlugins catches → PLUGIN_ERROR).\n throw new Error(\n `[slide/plugins/shiki] peer-dep 'shiki' not installed or failed to load. Run: pnpm add shiki. Original: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n }\n\n return {\n name: \"shiki\",\n sanitizeSchemaExtension: {\n tagNames: [\"span\", \"pre\", \"code\"],\n // Shiki emits inline `style` and `class` on tokens; \"*\" applies to every tag.\n attributes: {\n \"*\": [\"style\", \"className\"],\n pre: [\"style\", \"className\", \"tabIndex\"],\n code: [\"style\", \"className\"],\n span: [\"style\", \"className\"],\n },\n },\n async hastTransform(tree: HastRoot): Promise<HastRoot> {\n const hl = await getHighlighter();\n if (!hl) return tree; // peer-dep missing AND we've already reported it once\n const { visit } = await import(\"unist-util-visit\");\n const { fromHtml } = await import(\"hast-util-from-html\");\n // Walk: find <code class=\"language-XXX\">, replace its parent <pre> subtree\n // with the highlighted output (which is also a <pre><code>...</code></pre>).\n // biome-ignore lint/suspicious/noExplicitAny: hast unist-util-visit untyped here\n visit(tree, \"element\", (node: Element, _idx: number | undefined, parent: any) => {\n if (node.tagName !== \"code\") return;\n const className = (node.properties?.className as string[] | undefined) ?? [];\n const langClass = className.find((c) => c.startsWith(\"language-\"));\n if (!langClass) return;\n const lang = langClass.replace(\"language-\", \"\");\n if (!langs.includes(lang)) return;\n const first = node.children?.[0];\n const codeText = first && first.type === \"text\" ? first.value : \"\";\n let html: string;\n try {\n html = hl.codeToHtml(codeText, { lang, themes });\n } catch {\n // Shiki failed on this snippet (e.g. malformed input). Leave the plain\n // <pre><code> in place — the consumer still gets readable text.\n return;\n }\n const newTree = fromHtml(html, { fragment: true });\n const top = newTree.children[0];\n if (parent?.tagName === \"pre\" && parent.children?.length === 1 && top) {\n Object.assign(parent, top);\n }\n });\n return tree;\n },\n };\n}\n"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
package/registry/r/slide.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "slide",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Slide",
|
|
6
|
-
"description": "View-only primitive that renders Markdown + YAML frontmatter as a themed slide surface (Marp-inspired). Sanitized hast pipeline, two built-in themes, opt-in plugin system. Subpath-isolated bundle in @
|
|
6
|
+
"description": "View-only primitive that renders Markdown + YAML frontmatter as a themed slide surface (Marp-inspired). Sanitized hast pipeline, two built-in themes, opt-in plugin system. Subpath-isolated bundle in @theokit/ui.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"hast",
|
|
9
9
|
"hast-util-from-html",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"path": "components/primitives/slide/slide.tsx",
|
|
28
28
|
"type": "registry:ui",
|
|
29
29
|
"target": "components/ui/slide/slide.tsx",
|
|
30
|
-
"content": "import {\n type FC,\n type ReactElement,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n/**\n * `<Slide>` — view-only primitive that renders markdown + YAML frontmatter\n * into a themed, fixed-aspect surface. Lives in the isolated subpath\n * `@usetheo/ui/slide`.\n *\n * See RFC 0002 (`docs/rfcs/0002-slide.md`) and the plan in\n * `.claude/knowledge-base/plans/slide-view-primitive-plan.md`.\n *\n * SSR note: initial render returns the section wrapper; the parsed React tree\n * fills in client-side via the useEffect → `parseSlide` chain. Consumers\n * wrapping in Suspense / skeleton can mitigate visible jump.\n *\n * Performance tip: pass a memoized `onValidationError` (via `useCallback`)\n * and a memoized `components` map. Inline arrows recreate on each render\n * and cause re-parses.\n */\nimport { type ParsedSlide, parseSlide } from \"@/components/ui/slide/parse\";\nimport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\nimport type { SlideValidationError } from \"@/components/ui/slide/schema\";\nimport type { SlideTheme } from \"@/components/ui/slide/themes/index\";\nimport { useSlideFit } from \"@/components/ui/slide/use-slide-fit\";\n\nexport type { SlideTheme } from \"@/components/ui/slide/themes/index\";\nexport type {\n SlideValidationError,\n SlideValidationErrorCode,\n} from \"@/components/ui/slide/schema\";\nexport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\n\nexport interface SlideAspectRatio {\n width: number;\n height: number;\n}\n\nexport interface SlideProps {\n /**\n * Slide markdown. CommonMark + GFM + optional YAML frontmatter delimited\n * by `---`. Top-level horizontal rules (`---` on their own line outside\n * frontmatter) imply a deck split and trigger `MULTIPLE_SLIDES`; only the\n * first slide is rendered.\n *\n * Note: `<figure>`/`<figcaption>` tags are stripped by the default\n * sanitize schema (D8). Use `<img>` directly for captionless images.\n */\n markdown: string;\n /** Theme name. Defaults to `\"default\"`. */\n theme?: SlideTheme;\n /**\n * Aspect ratio of the logical canvas. Default `\"16:9\"` → 1280×720.\n * Custom `{ width, height }` accepted; zero/negative/NaN fallback to 16:9.\n */\n aspectRatio?: \"16:9\" | \"4:3\" | SlideAspectRatio;\n /** Lower clamp for container-fit scale. Default 0.1. */\n minScale?: number;\n /** Upper clamp for container-fit scale. Default 4. */\n maxScale?: number;\n /** Best-effort callback invoked in useEffect when validation/sanitize emits errors. */\n onValidationError?: (errors: SlideValidationError[]) => void;\n /** Override individual element renderers (passed to hast-util-to-jsx-runtime). */\n // biome-ignore lint/suspicious/noExplicitAny: third-party component override map\n components?: Record<string, FC<any>>;\n /**\n * Rich-content plugins (Tier 2). Each plugin transforms the mdast/hast tree\n * or adds React component overrides. Pass MEMOIZED arrays to avoid re-parses\n * on every render (D15).\n *\n * @example\n * const plugins = useMemo(() => [emojiPlugin(), shikiPlugin()], []);\n * <Slide markdown={md} plugins={plugins} />\n */\n plugins?: SlidePlugin[];\n /** Accessible label for the slide. Defaults to `\"Slide\"`. */\n \"aria-label\"?: string;\n /** Optional class applied to the outer host element (sizing/positioning hook). */\n className?: string;\n}\n\nconst ASPECT_PRESETS: Record<\"16:9\" | \"4:3\", SlideAspectRatio> = {\n \"16:9\": { width: 1280, height: 720 },\n \"4:3\": { width: 960, height: 720 },\n};\n\n/**\n * Resolve aspectRatio prop to concrete dimensions. Invalid custom values\n * (zero, negative, non-finite) silently fall back to 16:9 — surfaced via\n * `INVALID_ASPECT_RATIO` error in `onValidationError`. ADR D14.\n */\nfunction resolveCanvas(ar: SlideProps[\"aspectRatio\"]): {\n width: number;\n height: number;\n invalid?: boolean;\n} {\n if (!ar || ar === \"16:9\") return ASPECT_PRESETS[\"16:9\"];\n if (ar === \"4:3\") return ASPECT_PRESETS[\"4:3\"];\n if (\n ar.width <= 0 ||\n ar.height <= 0 ||\n !Number.isFinite(ar.width) ||\n !Number.isFinite(ar.height)\n ) {\n return { ...ASPECT_PRESETS[\"16:9\"], invalid: true };\n }\n return ar;\n}\n\nexport const Slide: FC<SlideProps> = ({\n markdown,\n theme = \"default\",\n aspectRatio = \"16:9\",\n minScale,\n maxScale,\n onValidationError,\n components,\n plugins,\n className,\n \"aria-label\": ariaLabel = \"Slide\",\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const canvas = useMemo(() => resolveCanvas(aspectRatio), [aspectRatio]);\n const scale = useSlideFit(containerRef, canvas.width, canvas.height, {\n minScale,\n maxScale,\n });\n\n const [parsed, setParsed] = useState<ParsedSlide | null>(null);\n\n // Surface INVALID_ASPECT_RATIO immediately when the prop is invalid.\n // Independent of markdown parsing so consumers see the signal even with\n // an empty markdown payload.\n useEffect(() => {\n if (canvas.invalid && onValidationError) {\n onValidationError([\n {\n code: \"INVALID_ASPECT_RATIO\",\n path: [\"aspectRatio\"],\n message:\n \"aspectRatio width/height must be positive finite numbers; falling back to 16:9.\",\n got: aspectRatio,\n },\n ]);\n }\n }, [canvas.invalid, onValidationError, aspectRatio]);\n\n // EC-7 / version counter — prevents older parses from overwriting newer\n // ones on rapid prop changes.\n const versionRef = useRef(0);\n useEffect(() => {\n const myVersion = ++versionRef.current;\n let cancelled = false;\n parseSlide(markdown, { components, plugins }).then(\n (result) => {\n if (cancelled || myVersion !== versionRef.current) return;\n setParsed(result);\n if (result.errors.length > 0 && onValidationError) {\n onValidationError(result.errors);\n }\n },\n (err: unknown) => {\n // Defensive: parseSlide promises to never throw, but if a runtime\n // failure escapes (e.g. peer-dep missing), surface a synthetic error\n // and keep the slide visible with empty body.\n if (cancelled || myVersion !== versionRef.current) return;\n if (onValidationError) {\n onValidationError([\n {\n code: \"INVALID_FRONTMATTER\",\n path: [],\n message: err instanceof Error ? err.message : \"parseSlide rejected.\",\n },\n ]);\n }\n setParsed({\n frontmatter: {},\n tree: emptyFragment(),\n errors: [],\n truncated: false,\n });\n },\n );\n return () => {\n cancelled = true;\n };\n }, [markdown, components, plugins, onValidationError]);\n\n const treeNode: ReactNode = parsed?.tree ?? null;\n const slideThemeAttr: SlideTheme = theme;\n\n // Tier 1 — frontmatter-driven attributes & overlays.\n const fm = parsed?.frontmatter ?? {};\n const layout = fm.layout;\n // Background precedence: explicit frontmatter > Marpit  (D18 / EC-5).\n const bgUrl = fm.backgroundImage ?? parsed?.extractedBackground?.url;\n const bgModifier = parsed?.extractedBackground?.modifier;\n const bgGradient = fm.backgroundGradient;\n const headerText = fm.header;\n const footerText = fm.footer;\n const paginateValue = fm.paginate;\n const showPaginate = paginateValue === true;\n\n // Background style: gradient > image > inherited.\n const backgroundImageStyle: string | undefined = bgGradient\n ? bgGradient\n : bgUrl\n ? `url(\"${bgUrl}\")`\n : undefined;\n\n return (\n <div\n ref={containerRef}\n className={[\"theo-slide-host\", className].filter(Boolean).join(\" \")}\n data-theo-slide-host\n style={{\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {/*\n Section is taken out of normal flow with position:absolute so its\n 1280×720 layout box doesn't inflate the host. `translate(-50%, -50%)`\n anchors the section's center to the host's center; the scale applies\n relative to that same origin. Mirrors Reveal.js transformSlides()\n (see `.claude/knowledge-base/reference/slide.md` §4.5 / §14.2).\n */}\n <section\n aria-roledescription=\"slide\"\n aria-label={ariaLabel}\n className=\"theo-slide\"\n data-theo-slide-theme={slideThemeAttr}\n data-theo-slide-layout={layout ?? \"default\"}\n data-theo-slide-bg-modifier={bgModifier}\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n width: canvas.width,\n height: canvas.height,\n transform: `translate(-50%, -50%) scale(${scale})`,\n transformOrigin: \"center\",\n padding: \"var(--theo-slide-padding, 64px)\",\n color: \"inherit\",\n // Background fills the canvas. When neither image nor gradient is set,\n // the slide inherits the parent surface (same pattern as Whiteboard).\n background: \"transparent\",\n // Only set backgroundImage + ancillary props when one is provided —\n // otherwise the shorthand `background: transparent` is preserved as-is\n // (important for the inherit-from-parent guarantee).\n ...(backgroundImageStyle\n ? {\n backgroundImage: backgroundImageStyle,\n backgroundSize: bgModifier === \"fit\" ? \"contain\" : \"cover\",\n backgroundPosition: \"center\",\n backgroundRepeat: \"no-repeat\",\n }\n : {}),\n fontFamily:\n \"var(--theo-slide-font-family, system-ui, -apple-system, 'Segoe UI', sans-serif)\",\n fontSize: \"var(--theo-slide-font-base, 28px)\",\n lineHeight: 1.5,\n boxSizing: \"border-box\",\n overflow: \"hidden\",\n }}\n >\n {headerText ? (\n <div className=\"theo-slide-header\" aria-hidden=\"true\">\n {headerText}\n </div>\n ) : null}\n {treeNode}\n {footerText ? (\n <div className=\"theo-slide-footer\" aria-hidden=\"true\">\n {footerText}\n </div>\n ) : null}\n {showPaginate ? (\n <div className=\"theo-slide-paginate\" aria-hidden=\"true\">\n 1\n </div>\n ) : null}\n </section>\n </div>\n );\n};\n\n/** Empty React fragment placeholder used when parseSlide fails fatally. */\nfunction emptyFragment(): ReactElement {\n // Cheap fallback that does not require importing Fragment from react/jsx-runtime.\n // Using a real element keeps the type as ReactElement (not ReactNode).\n return { type: \"span\", props: { children: null }, key: null } as unknown as ReactElement;\n}\n"
|
|
30
|
+
"content": "\"use client\";\n\nimport {\n type FC,\n type ReactElement,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n/**\n * `<Slide>` — view-only primitive that renders markdown + YAML frontmatter\n * into a themed, fixed-aspect surface. Lives in the isolated subpath\n * `@theokit/ui/slide`.\n *\n * See RFC 0002 (`docs/rfcs/0002-slide.md`) and the plan in\n * `.claude/knowledge-base/plans/slide-view-primitive-plan.md`.\n *\n * SSR note: initial render returns the section wrapper; the parsed React tree\n * fills in client-side via the useEffect → `parseSlide` chain. Consumers\n * wrapping in Suspense / skeleton can mitigate visible jump.\n *\n * Performance tip: pass a memoized `onValidationError` (via `useCallback`)\n * and a memoized `components` map. Inline arrows recreate on each render\n * and cause re-parses.\n */\nimport { type ParsedSlide, parseSlide } from \"@/components/ui/slide/parse\";\nimport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\nimport type { SlideValidationError } from \"@/components/ui/slide/schema\";\nimport type { SlideTheme } from \"@/components/ui/slide/themes/index\";\nimport { useSlideFit } from \"@/components/ui/slide/use-slide-fit\";\n\nexport type { SlideTheme } from \"@/components/ui/slide/themes/index\";\nexport type {\n SlideValidationError,\n SlideValidationErrorCode,\n} from \"@/components/ui/slide/schema\";\nexport type { SlidePlugin } from \"@/components/ui/slide/plugin\";\n\nexport interface SlideAspectRatio {\n width: number;\n height: number;\n}\n\nexport interface SlideProps {\n /**\n * Slide markdown. CommonMark + GFM + optional YAML frontmatter delimited\n * by `---`. Top-level horizontal rules (`---` on their own line outside\n * frontmatter) imply a deck split and trigger `MULTIPLE_SLIDES`; only the\n * first slide is rendered.\n *\n * Note: `<figure>`/`<figcaption>` tags are stripped by the default\n * sanitize schema (D8). Use `<img>` directly for captionless images.\n */\n markdown: string;\n /** Theme name. Defaults to `\"default\"`. */\n theme?: SlideTheme;\n /**\n * Aspect ratio of the logical canvas. Default `\"16:9\"` → 1280×720.\n * Custom `{ width, height }` accepted; zero/negative/NaN fallback to 16:9.\n */\n aspectRatio?: \"16:9\" | \"4:3\" | SlideAspectRatio;\n /** Lower clamp for container-fit scale. Default 0.1. */\n minScale?: number;\n /** Upper clamp for container-fit scale. Default 4. */\n maxScale?: number;\n /** Best-effort callback invoked in useEffect when validation/sanitize emits errors. */\n onValidationError?: (errors: SlideValidationError[]) => void;\n /** Override individual element renderers (passed to hast-util-to-jsx-runtime). */\n // biome-ignore lint/suspicious/noExplicitAny: third-party component override map\n components?: Record<string, FC<any>>;\n /**\n * Rich-content plugins (Tier 2). Each plugin transforms the mdast/hast tree\n * or adds React component overrides. Pass MEMOIZED arrays to avoid re-parses\n * on every render (D15).\n *\n * @example\n * const plugins = useMemo(() => [emojiPlugin(), shikiPlugin()], []);\n * <Slide markdown={md} plugins={plugins} />\n */\n plugins?: SlidePlugin[];\n /** Accessible label for the slide. Defaults to `\"Slide\"`. */\n \"aria-label\"?: string;\n /** Optional class applied to the outer host element (sizing/positioning hook). */\n className?: string;\n}\n\nconst ASPECT_PRESETS: Record<\"16:9\" | \"4:3\", SlideAspectRatio> = {\n \"16:9\": { width: 1280, height: 720 },\n \"4:3\": { width: 960, height: 720 },\n};\n\n/**\n * Resolve aspectRatio prop to concrete dimensions. Invalid custom values\n * (zero, negative, non-finite) silently fall back to 16:9 — surfaced via\n * `INVALID_ASPECT_RATIO` error in `onValidationError`. ADR D14.\n */\nfunction resolveCanvas(ar: SlideProps[\"aspectRatio\"]): {\n width: number;\n height: number;\n invalid?: boolean;\n} {\n if (!ar || ar === \"16:9\") return ASPECT_PRESETS[\"16:9\"];\n if (ar === \"4:3\") return ASPECT_PRESETS[\"4:3\"];\n if (\n ar.width <= 0 ||\n ar.height <= 0 ||\n !Number.isFinite(ar.width) ||\n !Number.isFinite(ar.height)\n ) {\n return { ...ASPECT_PRESETS[\"16:9\"], invalid: true };\n }\n return ar;\n}\n\nexport const Slide: FC<SlideProps> = ({\n markdown,\n theme = \"default\",\n aspectRatio = \"16:9\",\n minScale,\n maxScale,\n onValidationError,\n components,\n plugins,\n className,\n \"aria-label\": ariaLabel = \"Slide\",\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const canvas = useMemo(() => resolveCanvas(aspectRatio), [aspectRatio]);\n const scale = useSlideFit(containerRef, canvas.width, canvas.height, {\n minScale,\n maxScale,\n });\n\n const [parsed, setParsed] = useState<ParsedSlide | null>(null);\n\n // Surface INVALID_ASPECT_RATIO immediately when the prop is invalid.\n // Independent of markdown parsing so consumers see the signal even with\n // an empty markdown payload.\n useEffect(() => {\n if (canvas.invalid && onValidationError) {\n onValidationError([\n {\n code: \"INVALID_ASPECT_RATIO\",\n path: [\"aspectRatio\"],\n message:\n \"aspectRatio width/height must be positive finite numbers; falling back to 16:9.\",\n got: aspectRatio,\n },\n ]);\n }\n }, [canvas.invalid, onValidationError, aspectRatio]);\n\n // EC-7 / version counter — prevents older parses from overwriting newer\n // ones on rapid prop changes.\n const versionRef = useRef(0);\n useEffect(() => {\n const myVersion = ++versionRef.current;\n let cancelled = false;\n parseSlide(markdown, { components, plugins }).then(\n (result) => {\n if (cancelled || myVersion !== versionRef.current) return;\n setParsed(result);\n if (result.errors.length > 0 && onValidationError) {\n onValidationError(result.errors);\n }\n },\n (err: unknown) => {\n // Defensive: parseSlide promises to never throw, but if a runtime\n // failure escapes (e.g. peer-dep missing), surface a synthetic error\n // and keep the slide visible with empty body.\n if (cancelled || myVersion !== versionRef.current) return;\n if (onValidationError) {\n onValidationError([\n {\n code: \"INVALID_FRONTMATTER\",\n path: [],\n message: err instanceof Error ? err.message : \"parseSlide rejected.\",\n },\n ]);\n }\n setParsed({\n frontmatter: {},\n tree: emptyFragment(),\n errors: [],\n truncated: false,\n });\n },\n );\n return () => {\n cancelled = true;\n };\n }, [markdown, components, plugins, onValidationError]);\n\n const treeNode: ReactNode = parsed?.tree ?? null;\n const slideThemeAttr: SlideTheme = theme;\n\n // Tier 1 — frontmatter-driven attributes & overlays.\n const fm = parsed?.frontmatter ?? {};\n const layout = fm.layout;\n // Background precedence: explicit frontmatter > Marpit  (D18 / EC-5).\n const bgUrl = fm.backgroundImage ?? parsed?.extractedBackground?.url;\n const bgModifier = parsed?.extractedBackground?.modifier;\n const bgGradient = fm.backgroundGradient;\n const headerText = fm.header;\n const footerText = fm.footer;\n const paginateValue = fm.paginate;\n const showPaginate = paginateValue === true;\n\n // Background style: gradient > image > inherited.\n const backgroundImageStyle: string | undefined = bgGradient\n ? bgGradient\n : bgUrl\n ? `url(\"${bgUrl}\")`\n : undefined;\n\n return (\n <div\n data-slot=\"slide\"\n ref={containerRef}\n className={[\"theo-slide-host\", className].filter(Boolean).join(\" \")}\n data-theo-slide-host\n style={{\n position: \"relative\",\n overflow: \"hidden\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {/*\n Section is taken out of normal flow with position:absolute so its\n 1280×720 layout box doesn't inflate the host. `translate(-50%, -50%)`\n anchors the section's center to the host's center; the scale applies\n relative to that same origin. Mirrors Reveal.js transformSlides()\n (see `.claude/knowledge-base/reference/slide.md` §4.5 / §14.2).\n */}\n <section\n aria-roledescription=\"slide\"\n aria-label={ariaLabel}\n className=\"theo-slide\"\n data-theo-slide-theme={slideThemeAttr}\n data-theo-slide-layout={layout ?? \"default\"}\n data-theo-slide-bg-modifier={bgModifier}\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n width: canvas.width,\n height: canvas.height,\n transform: `translate(-50%, -50%) scale(${scale})`,\n transformOrigin: \"center\",\n padding: \"var(--theo-slide-padding, 64px)\",\n color: \"inherit\",\n // Background fills the canvas. When neither image nor gradient is set,\n // the slide inherits the parent surface (same pattern as Whiteboard).\n background: \"transparent\",\n // Only set backgroundImage + ancillary props when one is provided —\n // otherwise the shorthand `background: transparent` is preserved as-is\n // (important for the inherit-from-parent guarantee).\n ...(backgroundImageStyle\n ? {\n backgroundImage: backgroundImageStyle,\n backgroundSize: bgModifier === \"fit\" ? \"contain\" : \"cover\",\n backgroundPosition: \"center\",\n backgroundRepeat: \"no-repeat\",\n }\n : {}),\n fontFamily:\n \"var(--theo-slide-font-family, system-ui, -apple-system, 'Segoe UI', sans-serif)\",\n fontSize: \"var(--theo-slide-font-base, 28px)\",\n lineHeight: 1.5,\n boxSizing: \"border-box\",\n overflow: \"hidden\",\n }}\n >\n {headerText ? (\n <div className=\"theo-slide-header\" aria-hidden=\"true\">\n {headerText}\n </div>\n ) : null}\n {treeNode}\n {footerText ? (\n <div className=\"theo-slide-footer\" aria-hidden=\"true\">\n {footerText}\n </div>\n ) : null}\n {showPaginate ? (\n <div className=\"theo-slide-paginate\" aria-hidden=\"true\">\n 1\n </div>\n ) : null}\n </section>\n </div>\n );\n};\n\n/** Empty React fragment placeholder used when parseSlide fails fatally. */\nfunction emptyFragment(): ReactElement {\n // Cheap fallback that does not require importing Fragment from react/jsx-runtime.\n // Using a real element keeps the type as ReactElement (not ReactNode).\n return { type: \"span\", props: { children: null }, key: null } as unknown as ReactElement;\n}\n"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"path": "components/primitives/slide/parse.ts",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"path": "components/primitives/slide/json-schema.ts",
|
|
82
82
|
"type": "registry:ui",
|
|
83
83
|
"target": "components/ui/slide/json-schema.ts",
|
|
84
|
-
"content": "/**\n * JSON Schema for the Slide YAML frontmatter — derived from the Zod\n * `slideFrontmatter` schema via Zod 4's native `z.toJSONSchema()`.\n *\n * Purpose: enable LLM-driven generation pipelines (OpenAI structured outputs,\n * Anthropic tool use, function calling, JSON-mode constrained generation) to\n * produce frontmatter that is GUARANTEED to pass `validateSlide` without\n * round-tripping through human-written prompt engineering.\n *\n * Usage (Anthropic tool use):\n *\n * import { slideFrontmatterJsonSchema } from \"@
|
|
84
|
+
"content": "/**\n * JSON Schema for the Slide YAML frontmatter — derived from the Zod\n * `slideFrontmatter` schema via Zod 4's native `z.toJSONSchema()`.\n *\n * Purpose: enable LLM-driven generation pipelines (OpenAI structured outputs,\n * Anthropic tool use, function calling, JSON-mode constrained generation) to\n * produce frontmatter that is GUARANTEED to pass `validateSlide` without\n * round-tripping through human-written prompt engineering.\n *\n * Usage (Anthropic tool use):\n *\n * import { slideFrontmatterJsonSchema } from \"@theokit/ui/slide\";\n *\n * const tool = {\n * name: \"render_slide\",\n * description: \"Render a presentation slide.\",\n * input_schema: {\n * type: \"object\",\n * properties: {\n * frontmatter: slideFrontmatterJsonSchema,\n * body: { type: \"string\", description: \"CommonMark + GFM markdown body\" },\n * },\n * required: [\"body\"],\n * },\n * };\n *\n * The output matches Zod's behaviour exactly — anything that passes the JSON\n * Schema validator passes Zod, and vice-versa.\n *\n * Companion guide: `docs/slide-llm-guide.md` — copy-paste system prompt that\n * documents every Tier 1 + Tier 2 feature with examples.\n */\nimport { z } from \"zod\";\nimport { slideFrontmatter } from \"@/components/ui/slide/schema\";\n\ninterface JsonSchemaProperties {\n [key: string]: Record<string, unknown>;\n}\n\ninterface JsonSchemaWithProperties {\n properties?: JsonSchemaProperties;\n [key: string]: unknown;\n}\n\n/**\n * JSON Schema (Draft 2020-12) describing all accepted frontmatter fields:\n * `theme`, `layout`, `lang`, `color`, `backgroundColor`, `backgroundImage`,\n * `backgroundGradient`, `header`, `footer`, `paginate`.\n *\n * Note: `backgroundImage` uses a Zod `.transform()` (URL sanitization) which\n * cannot be represented in JSON Schema natively. We pass `unrepresentable: \"any\"`\n * to fall back to `{}` for transformed fields, then enrich `backgroundImage`\n * with manual metadata so LLMs still get useful constraints.\n *\n * Generated once at module load. Treat as immutable.\n */\nconst rawSchema = z.toJSONSchema(slideFrontmatter, {\n unrepresentable: \"any\",\n}) as JsonSchemaWithProperties;\n\nif (rawSchema.properties) {\n // Backgrounds carry hard constraints (http(s) only, ≤500_000 chars) that\n // Zod erases through `.transform()`. Inline them here so the LLM tooling\n // sees the same contract `sanitizeBgUrl` enforces at runtime.\n rawSchema.properties.backgroundImage = {\n type: \"string\",\n format: \"uri\",\n maxLength: 500_000,\n pattern: \"^https?://\",\n description:\n \"Slide background image URL. Only http(s) schemes are accepted; data: URLs are rejected at runtime (use a hosted image).\",\n };\n}\n\nexport const slideFrontmatterJsonSchema = rawSchema;\n"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"path": "components/primitives/slide/use-slide-fit.ts",
|
|
@@ -93,13 +93,13 @@
|
|
|
93
93
|
"path": "components/primitives/slide/index.ts",
|
|
94
94
|
"type": "registry:ui",
|
|
95
95
|
"target": "components/ui/slide/index.ts",
|
|
96
|
-
"content": "/**\n * Public surface of `@
|
|
96
|
+
"content": "/**\n * Public surface of `@theokit/ui/slide`. Subpath-isolated engine: this bundle\n * is emitted separately from the barrel (see `tsup.config.ts`) and depends on\n * the markdown stack via optional peer-deps.\n */\nexport { Slide } from \"@/components/ui/slide/slide\";\nexport type {\n SlideAspectRatio,\n SlideProps,\n SlideTheme,\n SlideValidationError,\n SlideValidationErrorCode,\n} from \"@/components/ui/slide/slide\";\nexport {\n parseSlide,\n parseBody,\n mdastToHast,\n sanitizeHast,\n hastToReact,\n type ParseSlideOptions,\n type ParsedSlide,\n} from \"@/components/ui/slide/parse\";\nexport { validateSlide, type ValidationResult } from \"@/components/ui/slide/validate\";\nexport {\n extractFrontmatter,\n MAX_RAW_FRONTMATTER,\n type ExtractFrontmatterResult,\n} from \"@/components/ui/slide/frontmatter\";\nexport {\n slideFrontmatter,\n slideInput,\n slideTheme,\n type SlideFrontmatter,\n type SlideInput,\n} from \"@/components/ui/slide/schema\";\nexport { isSlideTheme, slideThemes } from \"@/components/ui/slide/themes/index\";\nexport { useSlideFit, type UseSlideFitOptions } from \"@/components/ui/slide/use-slide-fit\";\nexport { collectTagCounts, getSlideSanitizeSchema } from \"@/components/ui/slide/sanitize\";\nexport {\n composePlugins,\n type SlidePlugin,\n type SlideSanitizeExtension,\n type MergedSanitizeExtensions,\n type PluginComposer,\n} from \"@/components/ui/slide/plugin\";\nexport { slideFrontmatterJsonSchema } from \"@/components/ui/slide/json-schema\";\n"
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"path": "components/primitives/slide/themes/index.ts",
|
|
100
100
|
"type": "registry:ui",
|
|
101
101
|
"target": "components/ui/slide/themes/index.ts",
|
|
102
|
-
"content": "/**\n * Theme registry for the Slide primitive. Built-in themes that ship with the\n * `@
|
|
102
|
+
"content": "/**\n * Theme registry for the Slide primitive. Built-in themes that ship with the\n * `@theokit/ui/slide` subpath. Custom themes are NOT registered through this\n * module in v0.1 — consumers override CSS variables on `.theo-slide` directly.\n */\n\nexport const slideThemes = [\"default\", \"violet-forge\"] as const;\nexport type SlideTheme = (typeof slideThemes)[number];\n\n/** Returns true if `value` is a recognized built-in theme. */\nexport function isSlideTheme(value: unknown): value is SlideTheme {\n return typeof value === \"string\" && (slideThemes as readonly string[]).includes(value);\n}\n"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
"path": "components/primitives/slide/themes/default.css",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"path": "components/primitives/slide/themes/violet-forge.css",
|
|
112
112
|
"type": "registry:ui",
|
|
113
113
|
"target": "components/ui/slide/themes/violet-forge.css",
|
|
114
|
-
"content": "/*\n * Slide primitive — `violet-forge` theme.\n *\n * Branded variant: same inherit-first foundation as the `default` theme, but\n * with a Violet Forge accent (links, code backgrounds, blockquote bar)\n * blended with `currentColor` via `color-mix()`. The brand hue stays\n * recognizable across light and dark surfaces because the accent is mixed\n * with the inherited foreground.\n */\n\n@import \"./layouts.css\";\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] {\n --theo-slide-canvas-width: 1280px;\n --theo-slide-canvas-height: 720px;\n --theo-slide-padding: 64px;\n --theo-slide-font-base: 28px;\n --theo-slide-font-family: var(\n --vf-font-family-sans,\n system-ui,\n -apple-system,\n \"Segoe UI\",\n sans-serif\n );\n --theo-slide-font-mono: var(--vf-font-family-mono, ui-monospace, \"JetBrains Mono\", monospace);\n\n /* Violet accent — the brand hue. Used for relative mixing so it adapts\n to light/dark surfaces (mixed with `currentColor`). */\n --theo-slide-accent: #
|
|
114
|
+
"content": "/*\n * Slide primitive — `violet-forge` theme.\n *\n * Branded variant: same inherit-first foundation as the `default` theme, but\n * with a Violet Forge accent (links, code backgrounds, blockquote bar)\n * blended with `currentColor` via `color-mix()`. The brand hue stays\n * recognizable across light and dark surfaces because the accent is mixed\n * with the inherited foreground.\n */\n\n@import \"./layouts.css\";\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] {\n --theo-slide-canvas-width: 1280px;\n --theo-slide-canvas-height: 720px;\n --theo-slide-padding: 64px;\n --theo-slide-font-base: 28px;\n --theo-slide-font-family: var(\n --vf-font-family-sans,\n system-ui,\n -apple-system,\n \"Segoe UI\",\n sans-serif\n );\n --theo-slide-font-mono: var(--vf-font-family-mono, ui-monospace, \"JetBrains Mono\", monospace);\n\n /* Violet accent — the brand hue. Used for relative mixing so it adapts\n to light/dark surfaces (mixed with `currentColor`). */\n --theo-slide-accent: #a855f7;\n\n /* Derived tints — relative to inherited `currentColor` so they auto-flip\n when the parent surface flips. */\n --theo-slide-color-muted: color-mix(in srgb, currentColor 60%, transparent);\n --theo-slide-color-tint-strong: color-mix(in srgb, var(--theo-slide-accent) 15%, transparent);\n --theo-slide-color-tint-weak: color-mix(in srgb, var(--theo-slide-accent) 7%, transparent);\n --theo-slide-color-border: color-mix(in srgb, var(--theo-slide-accent) 30%, transparent);\n --theo-slide-color-link: color-mix(in srgb, var(--theo-slide-accent) 70%, currentColor);\n --theo-slide-color-blockquote-bar: color-mix(in srgb, var(--theo-slide-accent) 60%, currentColor);\n\n color: inherit;\n background: transparent;\n font-family: var(--theo-slide-font-family);\n font-size: var(--theo-slide-font-base);\n line-height: 1.5;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h1,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h2,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h3,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h4,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h5,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h6 {\n color: inherit;\n line-height: 1.2;\n margin: 0 0 0.5em 0;\n font-weight: 600;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h1 {\n font-size: 1.8em;\n letter-spacing: -0.02em;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h2 {\n font-size: 1.4em;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h3 {\n font-size: 1.2em;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h4 {\n font-size: 1.05em;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h5 {\n font-size: 1em;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] h6 {\n font-size: 0.9em;\n color: var(--theo-slide-color-muted);\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] p {\n margin: 0 0 0.75em 0;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] ul,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] ol {\n margin: 0 0 0.75em 0;\n padding-left: 1.5em;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] li {\n margin: 0.25em 0;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] blockquote {\n margin: 0 0 0.75em 0;\n padding: 0.25em 0.75em;\n border-left: 4px solid var(--theo-slide-color-blockquote-bar);\n color: var(--theo-slide-color-muted);\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] a {\n color: var(--theo-slide-color-link);\n text-decoration: underline;\n text-underline-offset: 2px;\n font-weight: 600;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] code {\n font-family: var(--theo-slide-font-mono);\n font-size: 0.9em;\n padding: 0.1em 0.4em;\n border-radius: 4px;\n background: var(--theo-slide-color-tint-strong);\n color: inherit;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] pre {\n font-family: var(--theo-slide-font-mono);\n background: var(--theo-slide-color-tint-strong);\n color: inherit;\n padding: 0.75em 1em;\n border-radius: 8px;\n overflow: auto;\n font-size: 0.85em;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] pre code {\n padding: 0;\n background: transparent;\n font-size: inherit;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] hr {\n border: none;\n border-top: 1px solid var(--theo-slide-color-border);\n margin: 1em 0;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] table {\n border-collapse: collapse;\n margin: 0 0 0.75em 0;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] th,\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] td {\n border: 1px solid var(--theo-slide-color-border);\n padding: 0.4em 0.75em;\n text-align: left;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] thead th {\n background: var(--theo-slide-color-tint-weak);\n font-weight: 600;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] tbody tr:nth-child(even) td {\n background: var(--theo-slide-color-tint-weak);\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] img {\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] em {\n font-style: italic;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] strong {\n font-weight: 600;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] del {\n text-decoration: line-through;\n color: var(--theo-slide-color-muted);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] kbd {\n font-family: var(--theo-slide-font-mono);\n font-size: 0.85em;\n padding: 0.1em 0.4em;\n border: 1px solid var(--theo-slide-color-border);\n border-radius: 4px;\n background: var(--theo-slide-color-tint-weak);\n}\n\n/* ─────────────────────────────────────────────────────────────────\n * GFM alerts (rich-content T1.2) — Violet Forge palette.\n * Same structure as `default`; only the accent colors differ\n * so callouts feel native to the design system.\n * ───────────────────────────────────────────────────────────────── */\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside.theo-slide-alert {\n margin: 0 0 0.75em 0;\n padding: 0.75em 1em;\n border-left: 4px solid currentColor;\n border-radius: 0.4em;\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside.theo-slide-alert::before {\n display: block;\n margin-bottom: 0.35em;\n font-weight: 600;\n font-size: 0.9em;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside.theo-slide-alert > :first-child {\n margin-top: 0;\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside.theo-slide-alert > :last-child {\n margin-bottom: 0;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"note\"] {\n border-color: #6366f1;\n background: color-mix(in srgb, #6366f1 12%, transparent);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"note\"]::before {\n content: \"ⓘ Note\";\n color: #6366f1;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"tip\"] {\n border-color: #22d3ee;\n background: color-mix(in srgb, #22d3ee 12%, transparent);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"tip\"]::before {\n content: \"✓ Tip\";\n color: #22d3ee;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"important\"] {\n border-color: #c084fc;\n background: color-mix(in srgb, #c084fc 12%, transparent);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"]\n aside[data-theo-slide-alert-type=\"important\"]::before {\n content: \"★ Important\";\n color: #c084fc;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"warning\"] {\n border-color: #facc15;\n background: color-mix(in srgb, #facc15 12%, transparent);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"]\n aside[data-theo-slide-alert-type=\"warning\"]::before {\n content: \"⚠ Warning\";\n color: #facc15;\n}\n\n.theo-slide[data-theo-slide-theme=\"violet-forge\"] aside[data-theo-slide-alert-type=\"caution\"] {\n border-color: #f87171;\n background: color-mix(in srgb, #f87171 12%, transparent);\n}\n.theo-slide[data-theo-slide-theme=\"violet-forge\"]\n aside[data-theo-slide-alert-type=\"caution\"]::before {\n content: \"✗ Caution\";\n color: #f87171;\n}\n"
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"path": "components/primitives/slide/themes/layouts.css",
|