@marimo-team/islands 0.19.12-dev1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Combination-Du-o_hC9.js +11897 -0
- package/dist/{ConnectedDataExplorerComponent-CkXO-pKy.js → ConnectedDataExplorerComponent-BMiGWK57.js} +19 -18
- package/dist/{_baseIsEqual-CBSjxu-D.js → _baseIsEqual-DN5YkPnl.js} +1 -1
- package/dist/{_baseProperty-BVGrW_NZ.js → _baseProperty-6juuyX7Z.js} +5 -5
- package/dist/{_baseUniq-4lqa8rDi.js → _baseUniq-BlF21ach.js} +1 -1
- package/dist/{any-language-editor-t_VsTNa-.js → any-language-editor-1OMbohwD.js} +19 -19
- package/dist/architecture-U656AL7Q-Jd2CvPgJ.js +6 -0
- package/dist/{architectureDiagram-VXUJARFQ-DmJQhcJb.js → architectureDiagram-VXUJARFQ-DhN0C3Xf.js} +15 -15
- package/dist/{blockDiagram-VD42YOAC-CRofISJs.js → blockDiagram-VD42YOAC-DrBkIcbV.js} +7 -7
- package/dist/{button-Cy0ElmIm.js → button-KYalaJYu.js} +783 -200
- package/dist/{c4Diagram-YG6GDRKO-Deqoag4I.js → c4Diagram-YG6GDRKO-pWt4zmu0.js} +4 -4
- package/dist/{channel-CMsnebrL.js → channel-C56Jz8EL.js} +1 -1
- package/dist/{check-DkNR52Mm.js → check-C50jsehH.js} +1 -1
- package/dist/{chunk-76Q3JFCE-jPuajZH_.js → chunk-76Q3JFCE-CQ6a2yGJ.js} +1 -1
- package/dist/{chunk-ABZYJK2D-BGWvKte3.js → chunk-ABZYJK2D-BwNsaa1P.js} +1 -1
- package/dist/{chunk-ATLVNIR6-BYZB6C5T.js → chunk-ATLVNIR6-DtFMAawc.js} +1 -1
- package/dist/{chunk-B4BG7PRW-CwYUp6Uj.js → chunk-B4BG7PRW-lfWcLlzS.js} +4 -4
- package/dist/{chunk-DI55MBZ5-Gyro6dvN.js → chunk-DI55MBZ5-RhhAimfG.js} +4 -4
- package/dist/{chunk-EXTU4WIE-BlA7aWEw.js → chunk-EXTU4WIE-Bmo660a9.js} +1 -1
- package/dist/{chunk-FPAJGGOC-CduL34ft.js → chunk-FPAJGGOC-quWdfNUB.js} +7 -7
- package/dist/{chunk-FWNWRKHM-C-2TI4gt.js → chunk-FWNWRKHM-DmrwhAQr.js} +1 -1
- package/dist/{chunk-JA3XYJ7Z-Cm-pccR-.js → chunk-JA3XYJ7Z-n8UTzfok.js} +2 -2
- package/dist/{chunk-JZLCHNYA-CoLqqXMe.js → chunk-JZLCHNYA-ChKqHUdB.js} +4 -4
- package/dist/{chunk-LBM3YZW2-DWgQiioW.js → chunk-LBM3YZW2-BkxsqkNK.js} +1 -1
- package/dist/{chunk-LHMN2FUI-Dj_AHSvI.js → chunk-LHMN2FUI-CgYPnxfN.js} +1 -1
- package/dist/{chunk-N4CR4FBY-ByLbY9L-.js → chunk-N4CR4FBY-BxOHGL3P.js} +5 -5
- package/dist/{chunk-O7ZBX7Z2-CRZ8i1rP.js → chunk-O7ZBX7Z2-CdpLwOP0.js} +1 -1
- package/dist/{chunk-QN33PNHL-eodIiY9F.js → chunk-QN33PNHL-Dda-55xY.js} +1 -1
- package/dist/{chunk-QXUST7PY-CuJlDW6A.js → chunk-QXUST7PY-ClIEpoCT.js} +5 -5
- package/dist/{chunk-S3R3BYOJ-CwDGYMVf.js → chunk-S3R3BYOJ-C_1SJcWo.js} +3 -3
- package/dist/{chunk-S6J4BHB3-DNVMr0_v.js → chunk-S6J4BHB3-zfWjyfUg.js} +1 -1
- package/dist/{chunk-T53DSG4Q-BFWkpOX5.js → chunk-T53DSG4Q-Bewz1tiU.js} +1 -1
- package/dist/{chunk-TZMSLE5B-DFDr5FCr.js → chunk-TZMSLE5B-XW3duOft.js} +1 -1
- package/dist/{classDiagram-2ON5EDUG-BDBX9etk.js → classDiagram-2ON5EDUG-CUJlS_eo.js} +10 -10
- package/dist/{classDiagram-v2-WZHVMYZB-Bzj_L_BF.js → classDiagram-v2-WZHVMYZB-BhSPpbkE.js} +10 -10
- package/dist/{clone-Cc_6PW77.js → clone-V9hndNcj.js} +1 -1
- package/dist/{constants-DrOu5vvd.js → constants-BGRTDzdW.js} +2 -2
- package/dist/{copy-DRaXIb_a.js → copy-oc-FcZzt.js} +2 -2
- package/dist/{dagre-6UL2VRFP-r2rSdJYL.js → dagre-6UL2VRFP-BArPH353.js} +11 -11
- package/dist/{dagre-D2F8UdM6.js → dagre-Dcgyn_Uy.js} +15 -15
- package/dist/{diagram-PSM6KHXK-BpxVUe9U.js → diagram-PSM6KHXK-B1xAkr9y.js} +16 -16
- package/dist/{diagram-QEK2KX5R-q3dHUcp6.js → diagram-QEK2KX5R-CaoqwzPb.js} +14 -14
- package/dist/{diagram-S2PKOQOG-MDBKrxSC.js → diagram-S2PKOQOG-NXCsFLvR.js} +14 -14
- package/dist/dist-B8Y11RWn.js +1381 -0
- package/dist/dist-BA-HK7pI.js +5 -0
- package/dist/dist-BD5GU948.js +5 -0
- package/dist/{dist-BfactX3G.js → dist-BGzkWRSl.js} +4 -4
- package/dist/dist-BIYmAsND.js +5 -0
- package/dist/{dist-CmZYrgd_.js → dist-BUEi7EKT.js} +1 -1
- package/dist/{dist-B94MxrQS.js → dist-B_i29Q6L.js} +2 -2
- package/dist/dist-BcKTJXJi.js +5 -0
- package/dist/dist-BgnrtcWg.js +8 -0
- package/dist/{dist-glA_fIK_.js → dist-BoagoQQw.js} +2 -2
- package/dist/{dist-C2-m5aEk.js → dist-BswsDM4k.js} +2 -2
- package/dist/dist-C1njTlBq.js +5 -0
- package/dist/{dist-B2-r9y-0.js → dist-C5QB1NtD.js} +3 -3
- package/dist/{dist-Crk9ejOy.js → dist-CD7uLx0M.js} +2 -2
- package/dist/{dist-B4tYJP_i.js → dist-CMOy93xY.js} +2 -2
- package/dist/dist-CSKHwJYH.js +5 -0
- package/dist/dist-CSKKyiIq.js +5 -0
- package/dist/{dist-iiugPhCC.js → dist-C_9IMrtt.js} +1 -1
- package/dist/{dist-CE43BRmt.js → dist-Cb3iqED3.js} +1 -1
- package/dist/{dist-Dit9tk8a.js → dist-CoZ8kKKW.js} +1 -1
- package/dist/{dist-B5ATpkxy.js → dist-CrAYcS_4.js} +2 -2
- package/dist/dist-CrQ_pOuK.js +6 -0
- package/dist/dist-Cskx1daf.js +5 -0
- package/dist/dist-D4i0Ef34.js +8 -0
- package/dist/{dist-T4g7Sr6e.js → dist-D8EhXZ4S.js} +3 -3
- package/dist/{dist-CJrHMxlI.js → dist-DOLQQtWK.js} +3 -3
- package/dist/dist-DOcn61TX.js +8 -0
- package/dist/{dist-DqJdzAYM.js → dist-Dmr_nXF6.js} +2 -2
- package/dist/{dist-yVJ4xE5n.js → dist-DpAbrLuF.js} +5 -5
- package/dist/{dist-CcOGT46m.js → dist-DrC0QKFK.js} +1 -1
- package/dist/{dist-BYmtF1W6.js → dist-Dv2Phbh5.js} +2 -2
- package/dist/dist-DwMejAPB.js +6 -0
- package/dist/dist-DzSe1wby.js +8 -0
- package/dist/{dist-BbBnU4tG.js → dist-EZFqUJhh.js} +1 -1
- package/dist/{dist-Cgf353Ki.js → dist-Ey9hP8-j.js} +1 -1
- package/dist/{dist-BLwfpZD-.js → dist-IlWGXVjO.js} +2 -2
- package/dist/{dist-DOil6y-3.js → dist-LNp8svLl.js} +4 -4
- package/dist/{dist-CPTE45iS.js → dist-W6TdeACj.js} +1 -1
- package/dist/{dist-Dc1SFk5I.js → dist-a6Obzr07.js} +2 -2
- package/dist/{dist-Bsv_ARko.js → dist-bz6WguLy.js} +2 -2
- package/dist/{dist-CC9VUnXd.js → dist-iDeoXzdN.js} +1 -1
- package/dist/{dist-BoAHOW2l.js → dist-iyBCcLRa.js} +2 -2
- package/dist/{dist-CkEUrAus.js → dist-xCB683Dh.js} +2 -2
- package/dist/{erDiagram-Q2GNP2WA-CX1XdqVD.js → erDiagram-Q2GNP2WA-DWCa11g5.js} +10 -10
- package/dist/error-banner-vCG-EbUQ.js +619 -0
- package/dist/{esm-BAS2d2Ad.js → esm-DZSk8vt3.js} +27 -27
- package/dist/{flatten-eGRGXrC3.js → flatten-CWZjF1fc.js} +1 -1
- package/dist/{flowDiagram-NV44I4VS-BCj-ONTw.js → flowDiagram-NV44I4VS-BQ5PQs4L.js} +10 -10
- package/dist/{ganttDiagram-JELNMOA3-D1l5ewiQ.js → ganttDiagram-JELNMOA3-NTOuNWeT.js} +3 -3
- package/dist/{gitGraph-F6HP7TQM-CDM3aU-T.js → gitGraph-F6HP7TQM-DfRNsaDw.js} +3 -3
- package/dist/{gitGraphDiagram-NY62KEGX-KdZh0iiW.js → gitGraphDiagram-NY62KEGX-CYke62Ot.js} +13 -13
- package/dist/{glide-data-editor-2RvcPqmc.js → glide-data-editor-DttqGjrT.js} +571 -572
- package/dist/{graphlib-7UgfJadv.js → graphlib-CwMnCnQ9.js} +8 -8
- package/dist/{info-NVLQJR56-CoL1x1Fy.js → info-NVLQJR56-CUaoPtis.js} +3 -3
- package/dist/{infoDiagram-WHAUD3N6-PSH7lQ0D.js → infoDiagram-WHAUD3N6-B42WjAPh.js} +13 -13
- package/dist/{isEmpty-DQXRKNtW.js → isEmpty-6z2uv6gM.js} +2 -2
- package/dist/{isString-Clqvtgmo.js → isString-D6abkXrl.js} +1 -1
- package/dist/{isSymbol-TWXhTa8k.js → isSymbol-hk7foJ70.js} +1 -1
- package/dist/{journeyDiagram-XKPGCS4Q-BrTAxQ1J.js → journeyDiagram-XKPGCS4Q-ahXD97kr.js} +3 -3
- package/dist/{kanban-definition-3W4ZIXB7-BoYCDp_9.js → kanban-definition-3W4ZIXB7-CiTIpnhy.js} +7 -7
- package/dist/{label-CxU5JNBW.js → label-Cc5tEavt.js} +250 -250
- package/dist/{loader-C0-eIoas.js → loader-Cob3XFOw.js} +2 -2
- package/dist/main.js +1791 -1056
- package/dist/{memoize-Bag7B41I.js → memoize-Ckyqzyu_.js} +1 -1
- package/dist/{merge-Dl1bfxsj.js → merge-Db4Uulx4.js} +1 -1
- package/dist/{mermaid-C2cSe5YL.js → mermaid-B5xl_2hx.js} +73 -62
- package/dist/{mermaid-parser.core-D20zFbMa.js → mermaid-parser.core-BXj7Il0J.js} +8 -8
- package/dist/{min-Bg4bqmiD.js → min-ypdVXicC.js} +4 -4
- package/dist/{mindmap-definition-VGOIOE7T-CmRjsKEt.js → mindmap-definition-VGOIOE7T-Mni766A_.js} +9 -9
- package/dist/{now-mivqkCIv.js → now-Dwu5ou19.js} +2 -2
- package/dist/{once-BqS42WgZ.js → once-C9dA9qgQ.js} +1 -1
- package/dist/{packet-BFZMPI3H-C6aZmgV-.js → packet-BFZMPI3H-DHtQCusE.js} +3 -3
- package/dist/{pie-7BOR55EZ-NB6xYwcB.js → pie-7BOR55EZ-2sVLYbpR.js} +3 -3
- package/dist/{pieDiagram-ADFJNKIX-CtxQlnsU.js → pieDiagram-ADFJNKIX-PbXpgT8_.js} +14 -14
- package/dist/{quadrantDiagram-AYHSOK5B-DllnB2Hl.js → quadrantDiagram-AYHSOK5B-BtXGnx8i.js} +2 -2
- package/dist/{radar-NHE76QYJ-RKhErikV.js → radar-NHE76QYJ-Be0pEUux.js} +3 -3
- package/dist/{range-LoQMRQIX.js → range-D9jxVFd_.js} +5 -5
- package/dist/{reduce-B9mZDxPo.js → reduce-C6NEPj6s.js} +4 -4
- package/dist/{requirementDiagram-UZGBJVZJ-D36MI1k0.js → requirementDiagram-UZGBJVZJ-DxzXQRgq.js} +9 -9
- package/dist/{sankeyDiagram-TZEHDZUN-D1mygNPC.js → sankeyDiagram-TZEHDZUN-D-I7dJ0_.js} +2 -2
- package/dist/{sequenceDiagram-WL72ISMW-CWdn91Rf.js → sequenceDiagram-WL72ISMW-VDme2ljw.js} +4 -4
- package/dist/{slides-component-DfwLApNr.js → slides-component-ql7-5GDI.js} +2 -2
- package/dist/{spec-HoYHAQo2.js → spec-GwhMEXwK.js} +8 -9
- package/dist/{stateDiagram-FKZM4ZOC-CPxroWXd.js → stateDiagram-FKZM4ZOC-g3GI1EcK.js} +12 -12
- package/dist/{stateDiagram-v2-4FDKWEC3-BpM9Q54b.js → stateDiagram-v2-4FDKWEC3-7i6jBXe6.js} +10 -10
- package/dist/stex-D2rme5UG.js +4 -0
- package/dist/style.css +1 -1
- package/dist/{timeline-definition-IT6M3QCI-CVnRHx_t.js → timeline-definition-IT6M3QCI-bhvLlX_b.js} +2 -2
- package/dist/{toString-C4TLO6FA.js → toString-BwTJvlyD.js} +2 -2
- package/dist/tooltip-CL8m4f9y.js +404 -0
- package/dist/{treemap-KMMF4GRG-B37ugcLd.js → treemap-KMMF4GRG-Ba9ifjpG.js} +3 -3
- package/dist/{types-Ckva8JJq.js → types-Dsh6yC4B.js} +412 -413
- package/dist/{useAsyncData-dr8GazGv.js → useAsyncData-BPpyKjTJ.js} +2 -2
- package/dist/{useDeepCompareMemoize-ChviuF5n.js → useDeepCompareMemoize-C8Ms87P-.js} +18 -19
- package/dist/{useIframeCapabilities-DurI5SJh.js → useIframeCapabilities-C7z8VrZ1.js} +2 -2
- package/dist/{useTheme-SlKl8MlS.js → useTheme-Cq-gIssy.js} +299 -300
- package/dist/{vega-component-CnG0vAjf.js → vega-component-B5sxdjMq.js} +10 -10
- package/dist/{xychartDiagram-PRI3JC2R-BltwMWKC.js → xychartDiagram-PRI3JC2R-CFxuifYY.js} +5 -5
- package/package.json +1 -1
- package/src/components/editor/Output.tsx +8 -6
- package/src/components/editor/__tests__/Output.test.tsx +59 -0
- package/src/components/editor/chrome/__tests__/state.test.ts +321 -0
- package/src/components/editor/chrome/state.ts +27 -2
- package/src/components/editor/file-tree/upload.tsx +46 -23
- package/src/components/editor/links/cell-link.tsx +3 -2
- package/src/components/editor/output/console/ConsoleOutput.tsx +13 -3
- package/src/components/pages/gallery-page.tsx +1 -1
- package/src/components/pages/home-page.tsx +5 -3
- package/src/components/tracing/tracing.tsx +50 -39
- package/src/core/documentation/DocHoverTarget.tsx +23 -0
- package/src/core/documentation/doc-lookup.ts +50 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +3 -0
- package/src/css/app/Cell.css +5 -0
- package/src/mount.tsx +2 -2
- package/src/plugins/core/RenderHTML.tsx +15 -0
- package/src/plugins/core/__test__/registerReactComponent.test.ts +204 -0
- package/src/plugins/core/registerReactComponent.tsx +33 -0
- package/src/plugins/impl/MatrixPlugin.tsx +275 -0
- package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +415 -0
- package/src/plugins/impl/anywidget/model.ts +1 -2
- package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +70 -0
- package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +152 -0
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +781 -0
- package/src/plugins/impl/matrix.css +45 -0
- package/src/plugins/layout/mermaid/mermaid.tsx +11 -3
- package/src/plugins/plugins.ts +4 -0
- package/src/utils/__tests__/download.test.tsx +47 -0
- package/src/utils/download.ts +13 -1
- package/src/utils/links.ts +1 -1
- package/src/utils/urls.ts +1 -1
- package/dist/Combination-BTMrlhzT.js +0 -2611
- package/dist/architecture-U656AL7Q-COfwZju8.js +0 -6
- package/dist/dist-4YNZxwMI.js +0 -8
- package/dist/dist-7nR3r2kG.js +0 -5
- package/dist/dist-B2gkyT3r.js +0 -5
- package/dist/dist-B8G3I6vJ.js +0 -8
- package/dist/dist-BJ96Ykfp.js +0 -8
- package/dist/dist-BKLIWGw4.js +0 -5
- package/dist/dist-Bf3ou00A.js +0 -6
- package/dist/dist-BvkKXuPm.js +0 -5
- package/dist/dist-C6NJ3n6r.js +0 -5
- package/dist/dist-CecLPYY5.js +0 -5
- package/dist/dist-Ch0SwRzK.js +0 -5
- package/dist/dist-D6eWHiFh.js +0 -6
- package/dist/dist-DCQ710Bv.js +0 -5
- package/dist/dist-P_pkS5f-.js +0 -8
- package/dist/error-banner-D2zjeN_a.js +0 -1015
- package/dist/hotkeys-B5WnGZXF.js +0 -587
- package/dist/stex-ChDHQs3R.js +0 -4
- package/dist/zod-bjADtMKr.js +0 -10663
- /package/dist/{_arrayReduce-DlK7U3Q6.js → _arrayReduce-REKcIEj3.js} +0 -0
- /package/dist/{_baseFor-DSVmVciX.js → _baseFor-B69PDbIz.js} +0 -0
- /package/dist/{_hasUnicode-Bz2x6u6r.js → _hasUnicode-DrSAc5A5.js} +0 -0
- /package/dist/{dist-r8ecBV-v.js → dist-CUOuFgHt.js} +0 -0
- /package/dist/{invariant-D9QLJ4SZ.js → invariant-D-K49MfV.js} +0 -0
- /package/dist/{main-DhFbkwoC.js → main-DmxVpB19.js} +0 -0
- /package/dist/{purify.es-Brw-U87Q.js → purify.es-D4vaFt5N.js} +0 -0
- /package/dist/{stex-DrxP7bb3.js → stex-DIvyJfNO.js} +0 -0
|
@@ -167,6 +167,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
167
167
|
);
|
|
168
168
|
|
|
169
169
|
const originalIdx = consoleOutputs.length - idx - 1;
|
|
170
|
+
const isPassword = output.mimetype === "text/password";
|
|
170
171
|
|
|
171
172
|
if (output.response == null && lastStdInputIdx === idx) {
|
|
172
173
|
return (
|
|
@@ -174,6 +175,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
174
175
|
key={idx}
|
|
175
176
|
output={output.data}
|
|
176
177
|
isPdb={isPdb}
|
|
178
|
+
isPassword={isPassword}
|
|
177
179
|
onSubmit={(text) => onSubmitDebugger(text, originalIdx)}
|
|
178
180
|
onClear={onClear}
|
|
179
181
|
/>
|
|
@@ -185,6 +187,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
185
187
|
key={idx}
|
|
186
188
|
output={output.data}
|
|
187
189
|
response={output.response}
|
|
190
|
+
isPassword={isPassword}
|
|
188
191
|
/>
|
|
189
192
|
);
|
|
190
193
|
}
|
|
@@ -216,6 +219,7 @@ const StdInput = (props: {
|
|
|
216
219
|
output: string;
|
|
217
220
|
response?: string;
|
|
218
221
|
isPdb: boolean;
|
|
222
|
+
isPassword?: boolean;
|
|
219
223
|
}) => {
|
|
220
224
|
const [value, setValue] = React.useState("");
|
|
221
225
|
|
|
@@ -231,7 +235,7 @@ const StdInput = (props: {
|
|
|
231
235
|
data-testid="console-input"
|
|
232
236
|
// This is used in <StdinBlockingAlert> to find the input
|
|
233
237
|
data-stdin-blocking={true}
|
|
234
|
-
type="text"
|
|
238
|
+
type={props.isPassword ? "password" : "text"}
|
|
235
239
|
autoComplete="off"
|
|
236
240
|
autoFocus={true}
|
|
237
241
|
value={value}
|
|
@@ -277,11 +281,17 @@ const StdInput = (props: {
|
|
|
277
281
|
);
|
|
278
282
|
};
|
|
279
283
|
|
|
280
|
-
const StdInputWithResponse = (props: {
|
|
284
|
+
const StdInputWithResponse = (props: {
|
|
285
|
+
output: string;
|
|
286
|
+
response?: string;
|
|
287
|
+
isPassword?: boolean;
|
|
288
|
+
}) => {
|
|
281
289
|
return (
|
|
282
290
|
<div className="flex gap-2 items-center">
|
|
283
291
|
{renderText(props.output)}
|
|
284
|
-
|
|
292
|
+
{!props.isPassword && (
|
|
293
|
+
<span className="text-(--sky-11)">{props.response}</span>
|
|
294
|
+
)}
|
|
285
295
|
</div>
|
|
286
296
|
);
|
|
287
297
|
};
|
|
@@ -72,7 +72,7 @@ const GalleryPage: React.FC = () => {
|
|
|
72
72
|
opengraphImage && isHttpsUrl(opengraphImage)
|
|
73
73
|
? opengraphImage
|
|
74
74
|
: asURL(
|
|
75
|
-
|
|
75
|
+
`og/thumbnail?file=${encodeURIComponent(relativePath)}`,
|
|
76
76
|
).toString();
|
|
77
77
|
return {
|
|
78
78
|
...file,
|
|
@@ -326,7 +326,7 @@ const Node = ({ node, style }: NodeRendererProps<FileInfo>) => {
|
|
|
326
326
|
return (
|
|
327
327
|
<a
|
|
328
328
|
className={itemClassName}
|
|
329
|
-
href={asURL(`?file=${relativePath}`).toString()}
|
|
329
|
+
href={asURL(`?file=${encodeURIComponent(relativePath)}`).toString()}
|
|
330
330
|
target={tabTarget(relativePath)}
|
|
331
331
|
>
|
|
332
332
|
{iconEl}
|
|
@@ -401,8 +401,10 @@ const MarimoFileComponent = ({ file }: { file: MarimoFile }) => {
|
|
|
401
401
|
// We want to keep the sessionId in this case
|
|
402
402
|
const isNewNotebook = isSessionId(file.path);
|
|
403
403
|
const href = isNewNotebook
|
|
404
|
-
? asURL(
|
|
405
|
-
|
|
404
|
+
? asURL(
|
|
405
|
+
`?file=${encodeURIComponent(file.initializationId ?? file.path)}&session_id=${file.path}`,
|
|
406
|
+
)
|
|
407
|
+
: asURL(`?file=${encodeURIComponent(file.path)}`);
|
|
406
408
|
|
|
407
409
|
const isMarkdown = file.path.endsWith(".md");
|
|
408
410
|
|
|
@@ -32,6 +32,7 @@ import { ClearButton } from "../buttons/clear-button";
|
|
|
32
32
|
import type { SignalListener } from "../charts/types";
|
|
33
33
|
import { ElapsedTime, formatElapsedTime } from "../editor/cell/CellStatus";
|
|
34
34
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
35
|
+
import { usePanelSection } from "../editor/chrome/panels/panel-context";
|
|
35
36
|
import { CellLink } from "../editor/links/cell-link";
|
|
36
37
|
import {
|
|
37
38
|
type ChartPosition,
|
|
@@ -50,6 +51,7 @@ export const Tracing: React.FC = () => {
|
|
|
50
51
|
|
|
51
52
|
const { theme } = useTheme();
|
|
52
53
|
const [chartPosition, setChartPosition] = useState<ChartPosition>("above");
|
|
54
|
+
const panelSection = usePanelSection();
|
|
53
55
|
|
|
54
56
|
const toggleChartPosition = () => {
|
|
55
57
|
if (chartPosition === "above") {
|
|
@@ -69,47 +71,56 @@ export const Tracing: React.FC = () => {
|
|
|
69
71
|
);
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
const tracingComponent = (
|
|
75
|
+
<div className="py-1 px-2 overflow-y-scroll h-full">
|
|
76
|
+
<div className="flex flex-row justify-start gap-3">
|
|
77
|
+
<div className="flex flex-row gap-1 items-center">
|
|
78
|
+
<label htmlFor="chartPosition" className="text-xs">
|
|
79
|
+
Inline chart
|
|
80
|
+
</label>
|
|
81
|
+
<input
|
|
82
|
+
type="checkbox"
|
|
83
|
+
name="chartPosition"
|
|
84
|
+
data-testid="chartPosition"
|
|
85
|
+
onClick={toggleChartPosition}
|
|
86
|
+
defaultChecked={chartPosition === "sideBySide"}
|
|
87
|
+
className="h-3 cursor-pointer"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<ClearButton dataTestId="clear-traces-button" onClick={clearRuns} />
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="flex flex-col gap-3">
|
|
95
|
+
{newestToOldestRunIds.map((runId: RunId, index: number) => {
|
|
96
|
+
const run = runMap.get(runId);
|
|
97
|
+
if (run) {
|
|
98
|
+
return (
|
|
99
|
+
<TraceBlock
|
|
100
|
+
key={run.runId}
|
|
101
|
+
run={run}
|
|
102
|
+
isExpanded={expandedRuns.get(run.runId)}
|
|
103
|
+
isMostRecentRun={index === 0}
|
|
104
|
+
chartPosition={chartPosition}
|
|
105
|
+
theme={theme}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
})}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (panelSection === "sidebar") {
|
|
116
|
+
return tracingComponent;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Allow the panel to be resized when in the wider developer panel
|
|
72
120
|
return (
|
|
73
121
|
<PanelGroup direction="horizontal" className="h-full">
|
|
74
122
|
<Panel defaultSize={50} minSize={30} maxSize={80}>
|
|
75
|
-
|
|
76
|
-
<div className="flex flex-row justify-start gap-3">
|
|
77
|
-
<div className="flex flex-row gap-1 items-center">
|
|
78
|
-
<label htmlFor="chartPosition" className="text-xs">
|
|
79
|
-
Inline chart
|
|
80
|
-
</label>
|
|
81
|
-
<input
|
|
82
|
-
type="checkbox"
|
|
83
|
-
name="chartPosition"
|
|
84
|
-
data-testid="chartPosition"
|
|
85
|
-
onClick={toggleChartPosition}
|
|
86
|
-
defaultChecked={chartPosition === "sideBySide"}
|
|
87
|
-
className="h-3 cursor-pointer"
|
|
88
|
-
/>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<ClearButton dataTestId="clear-traces-button" onClick={clearRuns} />
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div className="flex flex-col gap-3">
|
|
95
|
-
{newestToOldestRunIds.map((runId: RunId, index: number) => {
|
|
96
|
-
const run = runMap.get(runId);
|
|
97
|
-
if (run) {
|
|
98
|
-
return (
|
|
99
|
-
<TraceBlock
|
|
100
|
-
key={run.runId}
|
|
101
|
-
run={run}
|
|
102
|
-
isExpanded={expandedRuns.get(run.runId)}
|
|
103
|
-
isMostRecentRun={index === 0}
|
|
104
|
-
chartPosition={chartPosition}
|
|
105
|
-
theme={theme}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
})}
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
123
|
+
{tracingComponent}
|
|
113
124
|
</Panel>
|
|
114
125
|
<PanelResizeHandle className="w-1 bg-border hover:bg-primary/50 transition-colors" />
|
|
115
126
|
<Panel defaultSize={50}>
|
|
@@ -381,7 +392,7 @@ const TraceRow: React.FC<TraceRowProps> = ({
|
|
|
381
392
|
<span className="text-(--gray-10) dark:text-(--gray-11)">
|
|
382
393
|
[{formatLogTimestamp(cellRun.startTime)}]
|
|
383
394
|
</span>
|
|
384
|
-
<span className="text-(--gray-10) w-16">
|
|
395
|
+
<span className="text-(--gray-10) w-16 overflow-hidden">
|
|
385
396
|
(<CellLink cellId={cellRun.cellId} />)
|
|
386
397
|
</span>
|
|
387
398
|
<span className="w-40 truncate -ml-1">{cellRun.code}</span>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { useDebouncedCallback } from "@/hooks/useDebounce";
|
|
5
|
+
import { requestOutputDocumentation } from "./doc-lookup";
|
|
6
|
+
|
|
7
|
+
export const DocHoverTarget: React.FC<{
|
|
8
|
+
qualifiedName: string;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}> = ({ qualifiedName, children }) => {
|
|
11
|
+
const handleMouseEnter = useDebouncedCallback(() => {
|
|
12
|
+
requestOutputDocumentation(qualifiedName);
|
|
13
|
+
}, 100);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
onMouseEnter={handleMouseEnter}
|
|
18
|
+
onMouseLeave={() => handleMouseEnter.cancel()}
|
|
19
|
+
>
|
|
20
|
+
{children}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { getCells } from "@/core/cells/cells";
|
|
4
|
+
import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
|
|
5
|
+
import { store } from "@/core/state/jotai";
|
|
6
|
+
import { Logger } from "@/utils/Logger";
|
|
7
|
+
import { documentationAtom } from "./state";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Request documentation for a qualified Python name (e.g. "torch.nn.Linear")
|
|
11
|
+
* by piggybacking on the existing code-completion system.
|
|
12
|
+
*
|
|
13
|
+
* We send the qualified name as the "document" to the autocomplete endpoint.
|
|
14
|
+
* Jedi resolves it and returns the docstring in the completion info.
|
|
15
|
+
*/
|
|
16
|
+
export async function requestOutputDocumentation(
|
|
17
|
+
qualifiedName: string,
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
// We need any valid cell_id for the completion request.
|
|
20
|
+
const cellId = getCells().inOrderIds.at(0);
|
|
21
|
+
if (!cellId) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const message = await AUTOCOMPLETER.request({
|
|
27
|
+
document: qualifiedName,
|
|
28
|
+
cellId,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!message || message.options.length === 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Find the option matching the last segment of the qualified name
|
|
36
|
+
const shortName = qualifiedName.split(".").pop() ?? qualifiedName;
|
|
37
|
+
|
|
38
|
+
const defaultOption = message.options[0];
|
|
39
|
+
const match =
|
|
40
|
+
message.options.find((o) => o.name === shortName) ?? defaultOption;
|
|
41
|
+
|
|
42
|
+
if (match?.completion_info) {
|
|
43
|
+
store.set(documentationAtom, {
|
|
44
|
+
documentation: match.completion_info,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
Logger.debug(`Doc lookup failed for "${qualifiedName}"`, error);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/core/islands/main.ts
CHANGED
package/src/css/app/Cell.css
CHANGED
package/src/mount.tsx
CHANGED
|
@@ -59,9 +59,9 @@ import { reportVitals } from "./utils/vitals";
|
|
|
59
59
|
let hasMounted = false;
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Main entry point for the
|
|
62
|
+
* Main entry point for the marimo app.
|
|
63
63
|
*
|
|
64
|
-
* Sets up the
|
|
64
|
+
* Sets up the marimo app with a theme provider.
|
|
65
65
|
*/
|
|
66
66
|
export function mount(options: unknown, el: Element): Error | undefined {
|
|
67
67
|
if (hasMounted) {
|
|
@@ -14,6 +14,7 @@ import React, {
|
|
|
14
14
|
} from "react";
|
|
15
15
|
import { CopyClipboardIcon } from "@/components/icons/copy-icon";
|
|
16
16
|
import { QueryParamPreservingLink } from "@/components/ui/query-param-preserving-link";
|
|
17
|
+
import { DocHoverTarget } from "@/core/documentation/DocHoverTarget";
|
|
17
18
|
import { sanitizeHtml, useSanitizeHtml } from "./sanitize";
|
|
18
19
|
|
|
19
20
|
type ReplacementFn = NonNullable<HTMLReactParserOptions["replace"]>;
|
|
@@ -146,6 +147,19 @@ const addCopyButtonToCodehilite: TransformFn = (
|
|
|
146
147
|
}
|
|
147
148
|
};
|
|
148
149
|
|
|
150
|
+
// Wrap elements with data-marimo-doc attribute in a DocHoverTarget
|
|
151
|
+
const wrapDocHoverTargets: TransformFn = (
|
|
152
|
+
reactNode: ReactNode,
|
|
153
|
+
domNode: DOMNode,
|
|
154
|
+
): JSX.Element | undefined => {
|
|
155
|
+
if (domNode instanceof Element && domNode.attribs?.["data-marimo-doc"]) {
|
|
156
|
+
const qualifiedName = domNode.attribs["data-marimo-doc"];
|
|
157
|
+
return (
|
|
158
|
+
<DocHoverTarget qualifiedName={qualifiedName}>{reactNode}</DocHoverTarget>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
149
163
|
const CopyableCode = ({ children }: { children: ReactNode }) => {
|
|
150
164
|
const ref = useRef<HTMLDivElement>(null);
|
|
151
165
|
return (
|
|
@@ -224,6 +238,7 @@ function parseHtml({
|
|
|
224
238
|
const transformFunctions: TransformFn[] = [
|
|
225
239
|
addCopyButtonToCodehilite,
|
|
226
240
|
preserveQueryParamsInAnchorLinks,
|
|
241
|
+
wrapDocHoverTargets,
|
|
227
242
|
removeWrappingBodyTags,
|
|
228
243
|
removeWrappingHtmlTags,
|
|
229
244
|
];
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import ReactDOM from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import {
|
|
7
|
+
isCustomMarimoElement,
|
|
8
|
+
registerReactComponent,
|
|
9
|
+
} from "../registerReactComponent";
|
|
10
|
+
|
|
11
|
+
// Each custom element name can only be registered once per jsdom window,
|
|
12
|
+
// so we use a counter to generate unique tag names across tests.
|
|
13
|
+
let tagCounter = 0;
|
|
14
|
+
function uniqueTag(base: string) {
|
|
15
|
+
return `marimo-test-${base}-${++tagCounter}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makePlugin(tagName: string) {
|
|
19
|
+
return {
|
|
20
|
+
tagName,
|
|
21
|
+
validator: z.any(),
|
|
22
|
+
render: () => null as never,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("isCustomMarimoElement", () => {
|
|
27
|
+
test("returns false for null", () => {
|
|
28
|
+
expect(isCustomMarimoElement(null)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("returns false for a regular HTMLElement", () => {
|
|
32
|
+
const div = document.createElement("div");
|
|
33
|
+
expect(isCustomMarimoElement(div)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("returns false for a non-HTMLElement", () => {
|
|
37
|
+
const svg = document.createElementNS(
|
|
38
|
+
"http://www.w3.org/2000/svg",
|
|
39
|
+
"circle",
|
|
40
|
+
);
|
|
41
|
+
expect(isCustomMarimoElement(svg as Element)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("returns true for a registered marimo custom element", () => {
|
|
45
|
+
const tag = uniqueTag("is-custom");
|
|
46
|
+
registerReactComponent(makePlugin(tag));
|
|
47
|
+
const el = document.createElement(tag);
|
|
48
|
+
expect(isCustomMarimoElement(el)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("returns false for an element with wrong __type__ value", () => {
|
|
52
|
+
const div = document.createElement("div") as unknown as HTMLElement & {
|
|
53
|
+
__type__: string;
|
|
54
|
+
};
|
|
55
|
+
div.__type__ = "something_else";
|
|
56
|
+
expect(isCustomMarimoElement(div)).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("connectedCallback - light DOM nesting detection", () => {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
let createRootSpy: any;
|
|
63
|
+
const mockRoot = {
|
|
64
|
+
render: vi.fn(),
|
|
65
|
+
unmount: vi.fn(),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
createRootSpy = vi
|
|
70
|
+
.spyOn(ReactDOM, "createRoot")
|
|
71
|
+
.mockReturnValue(mockRoot as unknown as ReactDOM.Root);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
createRootSpy.mockRestore();
|
|
76
|
+
mockRoot.render.mockClear();
|
|
77
|
+
mockRoot.unmount.mockClear();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("skips mounting when element is a light DOM child of another marimo element", () => {
|
|
81
|
+
const parentTag = uniqueTag("parent");
|
|
82
|
+
const childTag = uniqueTag("child");
|
|
83
|
+
registerReactComponent(makePlugin(parentTag));
|
|
84
|
+
registerReactComponent(makePlugin(childTag));
|
|
85
|
+
|
|
86
|
+
const parent = document.createElement(parentTag);
|
|
87
|
+
const child = document.createElement(childTag);
|
|
88
|
+
parent.append(child);
|
|
89
|
+
|
|
90
|
+
createRootSpy.mockClear();
|
|
91
|
+
document.body.append(parent);
|
|
92
|
+
|
|
93
|
+
// Only the parent should mount; the child is skipped because it
|
|
94
|
+
// detects a marimo ancestor in the light DOM.
|
|
95
|
+
expect(createRootSpy).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(createRootSpy).toHaveBeenCalledWith(parent.shadowRoot);
|
|
97
|
+
|
|
98
|
+
parent.remove();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("mounts when element is not nested inside a marimo element", () => {
|
|
102
|
+
const tag = uniqueTag("standalone");
|
|
103
|
+
registerReactComponent(makePlugin(tag));
|
|
104
|
+
|
|
105
|
+
const el = document.createElement(tag);
|
|
106
|
+
createRootSpy.mockClear();
|
|
107
|
+
|
|
108
|
+
document.body.append(el);
|
|
109
|
+
|
|
110
|
+
expect(createRootSpy).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(createRootSpy).toHaveBeenCalledWith(el.shadowRoot);
|
|
112
|
+
|
|
113
|
+
el.remove();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("mounts when nested inside a regular (non-marimo) element", () => {
|
|
117
|
+
const tag = uniqueTag("in-div");
|
|
118
|
+
registerReactComponent(makePlugin(tag));
|
|
119
|
+
|
|
120
|
+
const wrapper = document.createElement("div");
|
|
121
|
+
const el = document.createElement(tag);
|
|
122
|
+
wrapper.append(el);
|
|
123
|
+
|
|
124
|
+
createRootSpy.mockClear();
|
|
125
|
+
document.body.append(wrapper);
|
|
126
|
+
|
|
127
|
+
expect(createRootSpy).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(createRootSpy).toHaveBeenCalledWith(el.shadowRoot);
|
|
129
|
+
|
|
130
|
+
wrapper.remove();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("skips mounting for deeply nested marimo element through non-marimo wrappers", () => {
|
|
134
|
+
const outerTag = uniqueTag("outer");
|
|
135
|
+
const innerTag = uniqueTag("inner");
|
|
136
|
+
registerReactComponent(makePlugin(outerTag));
|
|
137
|
+
registerReactComponent(makePlugin(innerTag));
|
|
138
|
+
|
|
139
|
+
const outer = document.createElement(outerTag);
|
|
140
|
+
const div = document.createElement("div");
|
|
141
|
+
const inner = document.createElement(innerTag);
|
|
142
|
+
|
|
143
|
+
// Structure: outer > div > inner
|
|
144
|
+
outer.append(div);
|
|
145
|
+
div.append(inner);
|
|
146
|
+
|
|
147
|
+
createRootSpy.mockClear();
|
|
148
|
+
document.body.append(outer);
|
|
149
|
+
|
|
150
|
+
// Only outer mounts; inner is skipped because a marimo ancestor
|
|
151
|
+
// is found when traversing through the intermediate div.
|
|
152
|
+
expect(createRootSpy).toHaveBeenCalledTimes(1);
|
|
153
|
+
expect(createRootSpy).toHaveBeenCalledWith(outer.shadowRoot);
|
|
154
|
+
|
|
155
|
+
outer.remove();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("mounts both sibling marimo elements (neither is a child of the other)", () => {
|
|
159
|
+
const tagA = uniqueTag("sibling-a");
|
|
160
|
+
const tagB = uniqueTag("sibling-b");
|
|
161
|
+
registerReactComponent(makePlugin(tagA));
|
|
162
|
+
registerReactComponent(makePlugin(tagB));
|
|
163
|
+
|
|
164
|
+
const wrapper = document.createElement("div");
|
|
165
|
+
const a = document.createElement(tagA);
|
|
166
|
+
const b = document.createElement(tagB);
|
|
167
|
+
wrapper.append(a);
|
|
168
|
+
wrapper.append(b);
|
|
169
|
+
|
|
170
|
+
createRootSpy.mockClear();
|
|
171
|
+
document.body.append(wrapper);
|
|
172
|
+
|
|
173
|
+
// Both siblings should mount since neither is a child of the other.
|
|
174
|
+
expect(createRootSpy).toHaveBeenCalledTimes(2);
|
|
175
|
+
|
|
176
|
+
wrapper.remove();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("mounts when element is inside the shadow DOM of another marimo element", () => {
|
|
180
|
+
const outerTag = uniqueTag("shadow-outer");
|
|
181
|
+
const innerTag = uniqueTag("shadow-inner");
|
|
182
|
+
registerReactComponent(makePlugin(outerTag));
|
|
183
|
+
registerReactComponent(makePlugin(innerTag));
|
|
184
|
+
|
|
185
|
+
const outer = document.createElement(outerTag);
|
|
186
|
+
const inner = document.createElement(innerTag);
|
|
187
|
+
|
|
188
|
+
// Place the inner element inside the outer element's shadow root,
|
|
189
|
+
// simulating how getChildren() -> renderHTML() re-creates children
|
|
190
|
+
// in the shadow DOM. parentElement traversal stays within the
|
|
191
|
+
// shadow root boundary, so inner should NOT detect outer as an
|
|
192
|
+
// ancestor and should mount normally.
|
|
193
|
+
outer.shadowRoot?.append(inner);
|
|
194
|
+
|
|
195
|
+
createRootSpy.mockClear();
|
|
196
|
+
document.body.append(outer);
|
|
197
|
+
|
|
198
|
+
// Both elements should mount: outer in the document, inner in the
|
|
199
|
+
// shadow root (parentElement traversal doesn't cross shadow boundary).
|
|
200
|
+
expect(createRootSpy).toHaveBeenCalledTimes(2);
|
|
201
|
+
|
|
202
|
+
outer.remove();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -316,6 +316,15 @@ export function registerReactComponent<T>(plugin: IPlugin<T, unknown>): void {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
connectedCallback() {
|
|
319
|
+
// Skip mounting if this element is in the light DOM of another
|
|
320
|
+
// marimo custom element. The parent element's shadow DOM will
|
|
321
|
+
// re-create this element via getChildren() -> renderHTML(), so
|
|
322
|
+
// this light DOM copy should remain inert to avoid duplicate
|
|
323
|
+
// side-effects (e.g., mo.lazy firing load() twice).
|
|
324
|
+
if (this.isLightDOMChildOfMarimoElement()) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
319
328
|
if (!this.mounted) {
|
|
320
329
|
// Create a React root on the shadow root
|
|
321
330
|
invariant(this.shadowRoot, "Shadow root should exist");
|
|
@@ -348,6 +357,30 @@ export function registerReactComponent<T>(plugin: IPlugin<T, unknown>): void {
|
|
|
348
357
|
}
|
|
349
358
|
}
|
|
350
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Check if this element is in the light DOM of another marimo
|
|
362
|
+
* custom element. When a marimo element (e.g. marimo-tabs) has
|
|
363
|
+
* children that are also marimo elements (e.g. marimo-lazy), the
|
|
364
|
+
* browser upgrades both the light DOM originals AND the shadow DOM
|
|
365
|
+
* copies created by renderHTML(). The light DOM copies should not
|
|
366
|
+
* mount since they are not rendered (no <slot>) and would cause
|
|
367
|
+
* duplicate side-effects.
|
|
368
|
+
*
|
|
369
|
+
* parentElement traversal stays within the same DOM tree boundary
|
|
370
|
+
* (shadow root or document), so shadow DOM copies never find a
|
|
371
|
+
* marimo ancestor and correctly return false.
|
|
372
|
+
*/
|
|
373
|
+
private isLightDOMChildOfMarimoElement(): boolean {
|
|
374
|
+
let parent = this.parentElement;
|
|
375
|
+
while (parent) {
|
|
376
|
+
if (isCustomMarimoElement(parent)) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
parent = parent.parentElement;
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
351
384
|
/**
|
|
352
385
|
* Reset the plugin initial value and data.
|
|
353
386
|
* And then re-render the plugin.
|