@marimo-team/islands 0.25.3-dev273 → 0.25.3-dev279
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/{ConnectedDataExplorerComponent-eLHPh6uu.js → ConnectedDataExplorerComponent-CErm132k.js} +25 -26
- package/dist/{ErrorBoundary-DBh-wUT9.js → ErrorBoundary-DmTMuDPR.js} +43 -44
- package/dist/{abnfDiagram-VCTEODGH-hqd0IBtX.js → abnfDiagram-VCTEODGH-d-Qn2fwb.js} +2 -2
- package/dist/{any-language-editor-DnG26QXh.js → any-language-editor-6k6F12Z3.js} +3 -3
- package/dist/{architectureDiagram-5GKGNRK7-BzLAFcc0.js → architectureDiagram-5GKGNRK7-DxragHYv.js} +1 -1
- package/dist/{assertNever-DIU3JJbt.js → assertNever-B-YzWzbL.js} +14 -14
- package/dist/{button-0-RqY1DW.js → button-DZuNnIZO.js} +220 -223
- package/dist/{capabilities-B6yy_CBN.js → capabilities-D9FskwsK.js} +1 -1
- package/dist/{chat-ui-BWIkve2R.js → chat-ui-v1Fdh0NA.js} +332 -333
- package/dist/{check-gUSZbRsl.js → check-XFzZeH-y.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DOeVvuyE.js → chunk-IMKFNOWR-C0x0feFg.js} +1 -1
- package/dist/{chunk-SHT3W25Y-DZoFI8wR.js → chunk-SHT3W25Y-D0ZdASqB.js} +1 -1
- package/dist/{chunk-SVP7TREG-CWu86PSW.js → chunk-SVP7TREG-BHjL96zx.js} +1 -1
- package/dist/{chunk-TICWLB2K-BrM0GoP3.js → chunk-TICWLB2K-CzcrwGfp.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-DYi4V2bB.js → classDiagram-ZZMXUADV-BTqFAZTP.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-tN7FIk-m.js → classDiagram-v2-VYDZK3BY-EPXb0Elt.js} +2 -2
- package/dist/{code-visibility-M_dXDGp6.js → code-visibility-Dw4-TXN0.js} +20 -20
- package/dist/{common-DV0fvw_I.js → common-BXMFi4lr.js} +5399 -5397
- package/dist/{config-BsPruWMR.js → config-C8MFHQ2E.js} +23 -22
- package/dist/{copy-icon-C2I8sw4h.js → copy-icon-CtllOdmg.js} +3 -3
- package/dist/{createReducer-DdtT_7ji.js → createReducer-DRqwmaTc.js} +2 -2
- package/dist/{cynefinDiagram-5FMLGOSQ-D5nCnDc-.js → cynefinDiagram-5FMLGOSQ-CBSD344q.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BHkd7f_i.js → dagre-GXQ25YYZ-JItjh3Ge.js} +1 -1
- package/dist/{data-grid-overlay-editor-Cd1zGKBt.js → data-grid-overlay-editor-hZzwX7Ia.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-D1ALppT2.js → diagram-S7CK7UJ4-CTD9xTVb.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DLv0b83L.js → diagram-UQ7AKVKN-DTfnFai8.js} +1 -1
- package/dist/{diagram-VX7I27RA-7XJ7J6qg.js → diagram-VX7I27RA-BcSnucV-.js} +1 -1
- package/dist/{diagram-Z3DM3KII-7M7zbHp_.js → diagram-Z3DM3KII-C2ATIE8A.js} +1 -1
- package/dist/{dialog-DzCj1qQv.js → dialog-CaYm5imE.js} +33 -33
- package/dist/{dropdown-menu-CdXE15an.js → dropdown-menu-Bb0bFOIk.js} +149 -149
- package/dist/{ebnfDiagram-PWID7BFC-DHH1qlmV.js → ebnfDiagram-PWID7BFC-B7B9JPxc.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-fI_0dGef.js → erDiagram-RLTQ6QDP-v6hDno1O.js} +1 -1
- package/dist/{error-banner-DPc9eIw5.js → error-banner-CHCdHVFB.js} +44 -45
- package/dist/{error-entries-CxN-Nq3V.js → error-entries-C1S-Rmyt.js} +563 -554
- package/dist/{feedback-button-CE2gPi4-.js → feedback-button-6Ui2TDLR.js} +12 -12
- package/dist/{flowDiagram-HODETNUW-Brg9MA6i.js → flowDiagram-HODETNUW-aIFlAtOy.js} +2 -2
- package/dist/{formats-DbSvAPY-.js → formats-PzBQMUnr.js} +3 -3
- package/dist/{glide-data-editor-BycATbgF.js → glide-data-editor-PZNlrSDI.js} +14 -16
- package/dist/{glide-utils-BSw3SSHF.js → glide-utils-BSQr4d90.js} +51 -59
- package/dist/{globals-CW6OvnI5.js → globals-DX6FPZYq.js} +3 -3
- package/dist/{html-to-image-Bc2AvXHG.js → html-to-image-CqGUd58T.js} +14529 -14531
- package/dist/{infoDiagram-27XIBGKW-E9e403YT.js → infoDiagram-27XIBGKW-D6YHLeCP.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-C-RlYDRo.js → ishikawaDiagram-5VMMS53U-D1ZvprOX.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CLFl_uqK.js → kanban-definition-UXKFOSKX-Bna_3BMr.js} +1 -1
- package/dist/{loader-BVEtQ4F3.js → loader-DZvtzQoD.js} +1 -1
- package/dist/main.js +7357 -7361
- package/dist/{mermaid-rd09e1JL.js → mermaid-A36unKXJ.js} +50 -50
- package/dist/{mindmap-definition-YA3MSWOX-BQ8TWtyB.js → mindmap-definition-YA3MSWOX-CyVCs__G.js} +1 -1
- package/dist/{number-overlay-editor-B_rR-LdM.js → number-overlay-editor-D0CsL3_R.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-CN8UHFPi.js → pegDiagram-XKGWAZYB-9hNBD2wZ.js} +2 -2
- package/dist/{pencil-DcT8AE0h.js → pencil-DYUlSKxZ.js} +1 -1
- package/dist/{pieDiagram-E7YTZNPT-DGuJ0EBd.js → pieDiagram-E7YTZNPT-8tr-NYo7.js} +1 -1
- package/dist/{process-output-qecD7Dk8.js → process-output-DskX9jLa.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-BPDA8wy8.js → railroadDiagram-O6MQD6OU-Dzg1dyyp.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-ChzLGbGW.js → requirementDiagram-BXWQKSXE-B7sNqwGG.js} +1 -1
- package/dist/{reveal-component-CngVwOSy.js → reveal-component-D7xeI1QR.js} +1003 -1003
- package/dist/{select-OxgozOC8.js → select-DsoP1qe8.js} +290 -290
- package/dist/{sequenceDiagram-WJ2MYXX4-DFX-bE6O.js → sequenceDiagram-WJ2MYXX4-oP9shbaG.js} +1 -1
- package/dist/{spec-oJn0RhpQ.js → spec-BZMDxAu7.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-BR7cI3wK.js → stateDiagram-D77RDMKH-CjCv4ogv.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-oGqB5UDI.js → stateDiagram-v2-MP3YSRHH-BW9X162a.js} +2 -2
- package/dist/{static-state-CuV6Pc3a.js → static-state-D-Q-Ajaa.js} +8 -8
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-DwSZl1yC.js → swimlanes-42K2YHIH-Ckfs9Wrn.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-BHlcd-Ri.js → swimlanesDiagram-VR7AAH4N-DpTNG9YP.js} +2 -2
- package/dist/{swiper-component-Dvj-goaD.js → swiper-component-DQbK_ZWm.js} +7 -7
- package/dist/{timeline-definition-24CTP7MA-DMmP-G2O.js → timeline-definition-24CTP7MA-BXaBlV6U.js} +1 -1
- package/dist/{toast-DbBHmxmB.js → toast-BhjIy3Tq.js} +158 -158
- package/dist/{tooltip-BfcDQd2j.js → tooltip-DMMiOn2f.js} +64 -64
- package/dist/use-restore-focus-CosX7-gx.js +7568 -0
- package/dist/{useAsyncData-BYIAoMqn.js → useAsyncData-Ci4AvJff.js} +4 -4
- package/dist/{useDateFormatter-ButWkdcK.js → useDateFormatter-CPlava52.js} +2 -2
- package/dist/{useDeepCompareMemoize-Bz0c5GN6.js → useDeepCompareMemoize-NaaWoFlX.js} +1 -1
- package/dist/useEvent-BRH-aCqi.js +19 -0
- package/dist/{useIframeCapabilities-BIolGsF1.js → useIframeCapabilities-BXhDDoCG.js} +1 -1
- package/dist/{useLifecycle-JjxCC7Vu.js → useLifecycle-SniivYmC.js} +2 -2
- package/dist/{vega-component-rxIxAAh9.js → vega-component-DIxaDQNs.js} +40 -40
- package/dist/{vennDiagram-4TSXK5OY-Db8GtH1G.js → vennDiagram-4TSXK5OY-C-hQ4iNJ.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-DmIiC3FU.js → wardleyDiagram-VM6X3IG4-C2MQ_Umv.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-DS9OPrNq.js → xychartDiagram-S5SC5T6Z-BtTTBn7Q.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/executions.ts +4 -2
- package/src/__mocks__/notebook.ts +0 -2
- package/src/__tests__/CellStatus.test.tsx +6 -6
- package/src/components/editor/__tests__/data-attributes.test.tsx +0 -2
- package/src/components/editor/__tests__/output-persistence.test.tsx +20 -5
- package/src/components/editor/cell/CellStatus.tsx +6 -5
- package/src/components/editor/cell/__tests__/useRunCells.test.tsx +169 -0
- package/src/components/editor/cell/collapse.tsx +3 -4
- package/src/components/editor/cell/useRunCells.ts +32 -3
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +2 -3
- package/src/components/editor/notebook-cell.tsx +23 -37
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +4 -10
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -2
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +8 -5
- package/src/components/slides/minimap.tsx +3 -2
- package/src/components/slides/reveal-component.tsx +3 -2
- package/src/components/slides/slide-cell-view.tsx +8 -8
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -101
- package/src/core/cells/__tests__/actions.test.ts +0 -1
- package/src/core/cells/__tests__/cell.test.ts +81 -17
- package/src/core/cells/__tests__/cells.test.ts +0 -100
- package/src/core/cells/__tests__/document.test.ts +2 -8
- package/src/core/cells/__tests__/utils.test.ts +93 -24
- package/src/core/cells/actions.ts +0 -1
- package/src/core/cells/cell.ts +37 -14
- package/src/core/cells/cells.ts +10 -99
- package/src/core/cells/document.ts +2 -13
- package/src/core/cells/types.ts +0 -23
- package/src/core/cells/utils.ts +44 -17
- package/src/core/codemirror/cells/extensions.ts +0 -14
- package/src/core/errors/__tests__/error-entries.test.ts +40 -1
- package/src/core/errors/error-entries.ts +2 -3
- package/src/core/executions/__tests__/freshness.test.ts +420 -0
- package/src/core/executions/__tests__/hooks.test.tsx +106 -0
- package/src/core/executions/atoms.ts +22 -0
- package/src/core/executions/freshness.ts +164 -0
- package/src/core/executions/hooks.ts +17 -1
- package/src/core/executions/index.ts +3 -1
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/islands/components/__tests__/web-components.test.tsx +63 -0
- package/src/core/islands/components/web-components.tsx +11 -0
- package/src/core/kernel/handlers.ts +0 -4
- package/src/core/session/useNotebookSession.tsx +2 -7
- package/src/css/globals.css +6 -2
- package/src/plugins/impl/DataEditorPlugin.tsx +0 -4
- package/src/plugins/impl/data-editor/__tests__/column-types.test.ts +5 -0
- package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +9 -5
- package/src/plugins/impl/data-editor/column-types.ts +3 -0
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -5
- package/src/plugins/impl/data-editor/glide-utils.ts +10 -32
- package/src/plugins/impl/data-editor/types.ts +0 -2
- package/src/stories/cell-status.stories.tsx +12 -14
- package/src/stories/cell.stories.tsx +44 -10
- package/src/stories/layout/vertical/one-column.stories.tsx +0 -10
- package/src/theme/__tests__/islands-theme.test.tsx +276 -0
- package/src/theme/namespace.tsx +4 -3
- package/src/theme/useTheme.ts +71 -41
- package/dist/schemas-CdscWNy_.js +0 -4747
- package/dist/use-restore-focus-CmflVLWJ.js +0 -1672
- package/dist/useTheme-DPLobSFs.js +0 -339
- package/dist/utils-EBklsC5R.js +0 -813
- package/src/components/editor/cell/__tests__/useRunCells.test.ts +0 -45
|
@@ -27,7 +27,7 @@ export const Idle = Template.bind({});
|
|
|
27
27
|
Idle.args = {
|
|
28
28
|
editing: true,
|
|
29
29
|
status: "idle",
|
|
30
|
-
|
|
30
|
+
freshness: { sourceChanged: false, inputsChanged: false },
|
|
31
31
|
interrupted: false,
|
|
32
32
|
disabled: false,
|
|
33
33
|
elapsedTime: null,
|
|
@@ -37,7 +37,7 @@ export const Edited = Template.bind({});
|
|
|
37
37
|
Edited.args = {
|
|
38
38
|
editing: true,
|
|
39
39
|
status: "idle",
|
|
40
|
-
|
|
40
|
+
freshness: { sourceChanged: true, inputsChanged: false },
|
|
41
41
|
interrupted: false,
|
|
42
42
|
disabled: false,
|
|
43
43
|
elapsedTime: 100 as Milliseconds,
|
|
@@ -47,7 +47,7 @@ export const Interrupted = Template.bind({});
|
|
|
47
47
|
Interrupted.args = {
|
|
48
48
|
editing: true,
|
|
49
49
|
status: "idle",
|
|
50
|
-
|
|
50
|
+
freshness: { sourceChanged: false, inputsChanged: false },
|
|
51
51
|
interrupted: true,
|
|
52
52
|
disabled: false,
|
|
53
53
|
elapsedTime: 50 as Milliseconds,
|
|
@@ -57,7 +57,7 @@ export const Running = Template.bind({});
|
|
|
57
57
|
Running.args = {
|
|
58
58
|
editing: true,
|
|
59
59
|
status: "running",
|
|
60
|
-
|
|
60
|
+
freshness: { sourceChanged: false, inputsChanged: false },
|
|
61
61
|
interrupted: true,
|
|
62
62
|
disabled: false,
|
|
63
63
|
elapsedTime: 50 as Milliseconds,
|
|
@@ -67,7 +67,7 @@ export const Queued = Template.bind({});
|
|
|
67
67
|
Queued.args = {
|
|
68
68
|
editing: true,
|
|
69
69
|
status: "queued",
|
|
70
|
-
|
|
70
|
+
freshness: { sourceChanged: false, inputsChanged: false },
|
|
71
71
|
interrupted: true,
|
|
72
72
|
disabled: false,
|
|
73
73
|
elapsedTime: 50 as Milliseconds,
|
|
@@ -77,7 +77,7 @@ export const Disabled = Template.bind({});
|
|
|
77
77
|
Disabled.args = {
|
|
78
78
|
editing: true,
|
|
79
79
|
status: "idle",
|
|
80
|
-
|
|
80
|
+
freshness: { sourceChanged: false, inputsChanged: false },
|
|
81
81
|
interrupted: false,
|
|
82
82
|
disabled: true,
|
|
83
83
|
elapsedTime: null,
|
|
@@ -88,7 +88,7 @@ DisabledTransitively.args = {
|
|
|
88
88
|
editing: true,
|
|
89
89
|
status: "idle",
|
|
90
90
|
blocked: true,
|
|
91
|
-
|
|
91
|
+
freshness: { sourceChanged: false, inputsChanged: false },
|
|
92
92
|
interrupted: false,
|
|
93
93
|
disabled: false,
|
|
94
94
|
elapsedTime: null,
|
|
@@ -99,7 +99,7 @@ DisabledTransitivelyAndEdited.args = {
|
|
|
99
99
|
editing: true,
|
|
100
100
|
status: "idle",
|
|
101
101
|
blocked: true,
|
|
102
|
-
|
|
102
|
+
freshness: { sourceChanged: true, inputsChanged: false },
|
|
103
103
|
interrupted: false,
|
|
104
104
|
disabled: false,
|
|
105
105
|
elapsedTime: null,
|
|
@@ -110,8 +110,7 @@ Stale.args = {
|
|
|
110
110
|
editing: true,
|
|
111
111
|
status: "idle",
|
|
112
112
|
blocked: true,
|
|
113
|
-
|
|
114
|
-
edited: false,
|
|
113
|
+
freshness: { sourceChanged: false, inputsChanged: true },
|
|
115
114
|
interrupted: false,
|
|
116
115
|
disabled: false,
|
|
117
116
|
elapsedTime: null,
|
|
@@ -122,8 +121,7 @@ StaleAndDisabled.args = {
|
|
|
122
121
|
editing: true,
|
|
123
122
|
status: "idle",
|
|
124
123
|
blocked: true,
|
|
125
|
-
|
|
126
|
-
edited: false,
|
|
124
|
+
freshness: { sourceChanged: false, inputsChanged: true },
|
|
127
125
|
interrupted: false,
|
|
128
126
|
disabled: true,
|
|
129
127
|
elapsedTime: null,
|
|
@@ -134,7 +132,7 @@ EditedStaleAndDisabled.args = {
|
|
|
134
132
|
editing: true,
|
|
135
133
|
status: "idle",
|
|
136
134
|
blocked: true,
|
|
137
|
-
|
|
135
|
+
freshness: { sourceChanged: false, inputsChanged: true },
|
|
138
136
|
interrupted: false,
|
|
139
137
|
disabled: true,
|
|
140
138
|
elapsedTime: null,
|
|
@@ -145,7 +143,7 @@ EditedAndStale.args = {
|
|
|
145
143
|
editing: true,
|
|
146
144
|
status: "idle",
|
|
147
145
|
blocked: true,
|
|
148
|
-
|
|
146
|
+
freshness: { sourceChanged: true, inputsChanged: false },
|
|
149
147
|
interrupted: false,
|
|
150
148
|
disabled: false,
|
|
151
149
|
elapsedTime: null,
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
4
4
|
import { createStore, Provider } from "jotai";
|
|
5
5
|
import { createRef } from "react";
|
|
6
|
+
import { MockExecutions } from "@/__mocks__/executions";
|
|
6
7
|
import { cellId } from "@/__tests__/branded";
|
|
8
|
+
import type { CellId } from "@/core/cells/ids";
|
|
9
|
+
import {
|
|
10
|
+
cell,
|
|
11
|
+
document as documentOf,
|
|
12
|
+
withCells,
|
|
13
|
+
} from "@/core/notebook/document/__tests__/fixtures";
|
|
7
14
|
import { type NotebookState, notebookAtom } from "@/core/cells/cells";
|
|
8
15
|
import {
|
|
9
16
|
type CellRuntimeState,
|
|
@@ -22,13 +29,41 @@ type Story = StoryObj<typeof Cell>;
|
|
|
22
29
|
|
|
23
30
|
interface CellOverrides {
|
|
24
31
|
output?: CellRuntimeState["output"];
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
/** How the cell's result is outdated, as the host would report it. */
|
|
33
|
+
freshness?: "edited" | "replaced" | "both";
|
|
27
34
|
config?: CellConfig;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
const EMPTY_OVERRIDES: Readonly<CellOverrides> = {};
|
|
31
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Give the replica a completed execution whose source was edited since,
|
|
41
|
+
* whose input a newer execution replaced, or both.
|
|
42
|
+
*/
|
|
43
|
+
function seedFreshness(
|
|
44
|
+
store: ReturnType<typeof createStore>,
|
|
45
|
+
cid: CellId,
|
|
46
|
+
freshness: NonNullable<CellOverrides["freshness"]>,
|
|
47
|
+
) {
|
|
48
|
+
const ran = MockExecutions.completed(cid, {
|
|
49
|
+
code_fingerprint: "ran",
|
|
50
|
+
depends_on:
|
|
51
|
+
freshness === "edited" ? {} : { x: { execution: "gone", update: 0 } },
|
|
52
|
+
});
|
|
53
|
+
const document = withCells(
|
|
54
|
+
documentOf(),
|
|
55
|
+
{
|
|
56
|
+
c1: null,
|
|
57
|
+
c2: null,
|
|
58
|
+
[cid]: cell(cid, "import marimo as mo", {
|
|
59
|
+
code_fingerprint: freshness === "replaced" ? "ran" : "edited",
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
[cid],
|
|
63
|
+
);
|
|
64
|
+
MockExecutions.seed(store, [ran], null, document);
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
const Cell: React.FC<{
|
|
33
68
|
overrides?: CellOverrides;
|
|
34
69
|
}> = ({ overrides = EMPTY_OVERRIDES }) => {
|
|
@@ -39,13 +74,11 @@ const Cell: React.FC<{
|
|
|
39
74
|
id: cid,
|
|
40
75
|
name: "cell_1",
|
|
41
76
|
code: "import marimo as mo",
|
|
42
|
-
edited: overrides.edited ?? false,
|
|
43
77
|
config: {
|
|
44
78
|
hide_code: overrides.config?.hide_code ?? false,
|
|
45
79
|
disabled: overrides.config?.disabled ?? false,
|
|
46
80
|
column: null,
|
|
47
81
|
},
|
|
48
|
-
lastCodeRun: null,
|
|
49
82
|
},
|
|
50
83
|
},
|
|
51
84
|
cellIds: [cid],
|
|
@@ -53,7 +86,6 @@ const Cell: React.FC<{
|
|
|
53
86
|
[cid]: createCellRuntimeState({
|
|
54
87
|
output: overrides.output ?? null,
|
|
55
88
|
consoleOutputs: [],
|
|
56
|
-
staleInputs: overrides.staleInputs ?? false,
|
|
57
89
|
debuggerActive: false,
|
|
58
90
|
outline: null,
|
|
59
91
|
}),
|
|
@@ -68,6 +100,9 @@ const Cell: React.FC<{
|
|
|
68
100
|
|
|
69
101
|
const store = createStore();
|
|
70
102
|
store.set(notebookAtom, notebook);
|
|
103
|
+
if (overrides.freshness !== undefined) {
|
|
104
|
+
seedFreshness(store, cid, overrides.freshness);
|
|
105
|
+
}
|
|
71
106
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
72
107
|
store.set(requestClientAtom, resolveRequestClient());
|
|
73
108
|
return (
|
|
@@ -145,7 +180,7 @@ export const UnsavedEditsOutput: Story = {
|
|
|
145
180
|
<TooltipProvider>
|
|
146
181
|
<Cell
|
|
147
182
|
overrides={{
|
|
148
|
-
|
|
183
|
+
freshness: "edited",
|
|
149
184
|
output: {
|
|
150
185
|
channel: "output",
|
|
151
186
|
data: "<span class='markdown'><h1>Layout</h1>\n<p><code>marimo</code> provides functions to help you lay out your output, such as\nin rows and columns, accordions, tabs, and callouts. This tutorial\nshows some examples.</p></span>",
|
|
@@ -239,7 +274,7 @@ export const StaleStatus: Story = {
|
|
|
239
274
|
<div className="p-20 max-w-4xl">
|
|
240
275
|
<Cell
|
|
241
276
|
overrides={{
|
|
242
|
-
|
|
277
|
+
freshness: "replaced",
|
|
243
278
|
output: {
|
|
244
279
|
channel: "output",
|
|
245
280
|
data: "This data is stale because a parent is disabled",
|
|
@@ -257,8 +292,7 @@ export const StaleAndEditedStatus: Story = {
|
|
|
257
292
|
<div className="p-20 max-w-4xl">
|
|
258
293
|
<Cell
|
|
259
294
|
overrides={{
|
|
260
|
-
|
|
261
|
-
edited: true,
|
|
295
|
+
freshness: "both",
|
|
262
296
|
output: {
|
|
263
297
|
channel: "output",
|
|
264
298
|
data: "This data is stale because a parent is disabled, but this cell has been edited since.",
|
|
@@ -281,7 +315,7 @@ export const DisabledAndStaleStatus: Story = {
|
|
|
281
315
|
disabled: true,
|
|
282
316
|
hide_code: false,
|
|
283
317
|
},
|
|
284
|
-
|
|
318
|
+
freshness: "replaced",
|
|
285
319
|
output: {
|
|
286
320
|
channel: "output",
|
|
287
321
|
data: "<span class='markdown'><h1>Layout</h1>\n<p><code>marimo</code> provides functions to help you lay out your output, such as\nin rows and columns, accordions, tabs, and callouts. This tutorial\nshows some examples.</p></span>",
|
|
@@ -35,20 +35,11 @@ const createLongReprNotebook = (
|
|
|
35
35
|
|
|
36
36
|
print('hello')
|
|
37
37
|
LongReprA()`,
|
|
38
|
-
edited: false,
|
|
39
38
|
config: {
|
|
40
39
|
hide_code: hideCode,
|
|
41
40
|
disabled: false,
|
|
42
41
|
column: null,
|
|
43
42
|
},
|
|
44
|
-
lastCodeRun: `class LongReprA:
|
|
45
|
-
def __repr__(self):
|
|
46
|
-
return "<dlt.Relation(dataset='" + ("jaffle_ingest_dataset_" * 80) + \\
|
|
47
|
-
"', destination='<dlt.destinations.duckdb(destination_type=\\'duckdb\\'," + \\
|
|
48
|
-
" credentials=\\'local_jaffle.duckdb\\')>')>"
|
|
49
|
-
|
|
50
|
-
print('hello')
|
|
51
|
-
LongReprA()`,
|
|
52
43
|
},
|
|
53
44
|
},
|
|
54
45
|
cellIds: [cellId],
|
|
@@ -68,7 +59,6 @@ LongReprA()`,
|
|
|
68
59
|
timestamp: 1_686_863_705,
|
|
69
60
|
},
|
|
70
61
|
],
|
|
71
|
-
staleInputs: false,
|
|
72
62
|
debuggerActive: false,
|
|
73
63
|
outline: null,
|
|
74
64
|
}),
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render, waitFor } from "@testing-library/react";
|
|
4
|
+
import { createStore } from "jotai";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { JsonOutput } from "@/components/editor/output/JsonOutput";
|
|
7
|
+
import {
|
|
8
|
+
marimoDarkTheme,
|
|
9
|
+
marimoLightTheme,
|
|
10
|
+
} from "@/components/editor/output/json-output/themes";
|
|
11
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
12
|
+
import { defaultUserConfig } from "@/core/config/config-schema";
|
|
13
|
+
import { StyleNamespace } from "../namespace";
|
|
14
|
+
import { resolvedThemeAtom } from "../useTheme";
|
|
15
|
+
|
|
16
|
+
let store: ReturnType<typeof createStore>;
|
|
17
|
+
let unsubscribe: (() => void) | undefined;
|
|
18
|
+
let media: EventTarget & { matches: boolean };
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.stubEnv("VITE_MARIMO_ISLANDS", "true");
|
|
22
|
+
media = Object.assign(new EventTarget(), { matches: false });
|
|
23
|
+
vi.stubGlobal(
|
|
24
|
+
"matchMedia",
|
|
25
|
+
vi.fn(() => media),
|
|
26
|
+
);
|
|
27
|
+
store = createStore();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
stopObserving();
|
|
32
|
+
for (const element of [document.body, document.documentElement]) {
|
|
33
|
+
for (const attribute of [
|
|
34
|
+
"class",
|
|
35
|
+
"style",
|
|
36
|
+
"data-theme",
|
|
37
|
+
"data-mode",
|
|
38
|
+
"data-vscode-theme-kind",
|
|
39
|
+
]) {
|
|
40
|
+
element.removeAttribute(attribute);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
vi.unstubAllEnvs();
|
|
44
|
+
vi.unstubAllGlobals();
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function subscribe() {
|
|
49
|
+
unsubscribe = store.sub(resolvedThemeAtom, vi.fn());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function stopObserving() {
|
|
53
|
+
unsubscribe?.();
|
|
54
|
+
unsubscribe = undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getTheme() {
|
|
58
|
+
return store.get(resolvedThemeAtom);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setSystemTheme(matches: boolean) {
|
|
62
|
+
media.matches = matches;
|
|
63
|
+
media.dispatchEvent(new Event("change"));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe("islands host theme", () => {
|
|
67
|
+
it("defaults to light on a transparent page even if the system is dark", () => {
|
|
68
|
+
media.matches = true;
|
|
69
|
+
subscribe();
|
|
70
|
+
expect(getTheme()).toBe("light");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it.each(["body", "html"])(
|
|
74
|
+
"follows %s theme attributes in both directions",
|
|
75
|
+
async (tag) => {
|
|
76
|
+
const host = document.querySelector(tag)!;
|
|
77
|
+
subscribe();
|
|
78
|
+
for (const [attribute, dark, light] of [
|
|
79
|
+
["class", "dark", "light"],
|
|
80
|
+
["class", "dark-mode", "light-mode"],
|
|
81
|
+
["class", "dark-theme", "light-theme"],
|
|
82
|
+
["data-theme", "dark", "light"],
|
|
83
|
+
["data-mode", "dark", "light"],
|
|
84
|
+
]) {
|
|
85
|
+
host.setAttribute(attribute, dark);
|
|
86
|
+
await expect.poll(getTheme).toBe("dark");
|
|
87
|
+
host.setAttribute(attribute, light);
|
|
88
|
+
await expect.poll(getTheme).toBe("light");
|
|
89
|
+
host.removeAttribute(attribute);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
it("reads the initial host theme and follows color-scheme changes", async () => {
|
|
95
|
+
document.body.style.colorScheme = "dark";
|
|
96
|
+
subscribe();
|
|
97
|
+
expect(getTheme()).toBe("dark");
|
|
98
|
+
document.body.style.colorScheme = "light";
|
|
99
|
+
await expect.poll(getTheme).toBe("light");
|
|
100
|
+
document.body.style.colorScheme = "dark";
|
|
101
|
+
await expect.poll(getTheme).toBe("dark");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("uses the background when color-scheme is normal", async () => {
|
|
105
|
+
document.body.style.colorScheme = "normal";
|
|
106
|
+
document.body.style.backgroundColor = "rgb(30, 30, 30)";
|
|
107
|
+
subscribe();
|
|
108
|
+
expect(getTheme()).toBe("dark");
|
|
109
|
+
document.body.style.backgroundColor = "white";
|
|
110
|
+
await expect.poll(getTheme).toBe("light");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("resolves a host supporting both color schemes when the system changes", async () => {
|
|
114
|
+
document.body.style.colorScheme = "light dark";
|
|
115
|
+
subscribe();
|
|
116
|
+
expect(getTheme()).toBe("light");
|
|
117
|
+
for (const matches of [true, false]) {
|
|
118
|
+
setSystemTheme(matches);
|
|
119
|
+
await expect.poll(getTheme).toBe(matches ? "dark" : "light");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("shares a subscription, skips unchanged themes, and cleans up on unmount", async () => {
|
|
124
|
+
const disconnect = vi.spyOn(MutationObserver.prototype, "disconnect");
|
|
125
|
+
const removeListener = vi.spyOn(media, "removeEventListener");
|
|
126
|
+
const listener = vi.fn();
|
|
127
|
+
subscribe();
|
|
128
|
+
const unsubscribeSecond = store.sub(resolvedThemeAtom, listener);
|
|
129
|
+
document.body.classList.add("unrelated");
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
131
|
+
expect(listener).not.toHaveBeenCalled();
|
|
132
|
+
unsubscribeSecond();
|
|
133
|
+
expect(disconnect).not.toHaveBeenCalled();
|
|
134
|
+
stopObserving();
|
|
135
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
136
|
+
expect(removeListener).toHaveBeenCalledWith("change", expect.any(Function));
|
|
137
|
+
document.body.classList.add("dark");
|
|
138
|
+
subscribe();
|
|
139
|
+
expect(getTheme()).toBe("dark");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("ignores unrecognized theme attributes and transparent body backgrounds", async () => {
|
|
143
|
+
document.body.dataset.theme = "custom-theme";
|
|
144
|
+
document.body.dataset.mode = "auto";
|
|
145
|
+
document.body.style.backgroundColor = "rgba(0, 0, 0, 0)";
|
|
146
|
+
document.documentElement.style.backgroundColor = "white";
|
|
147
|
+
subscribe();
|
|
148
|
+
expect(getTheme()).toBe("light");
|
|
149
|
+
document.documentElement.style.backgroundColor = "black";
|
|
150
|
+
await expect.poll(getTheme).toBe("dark");
|
|
151
|
+
document.documentElement.style.backgroundColor = "transparent";
|
|
152
|
+
await expect.poll(getTheme).toBe("light");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("falls back after a theme attribute is removed", async () => {
|
|
156
|
+
document.body.dataset.theme = "dark";
|
|
157
|
+
document.body.style.colorScheme = "light";
|
|
158
|
+
subscribe();
|
|
159
|
+
expect(getTheme()).toBe("dark");
|
|
160
|
+
delete document.body.dataset.theme;
|
|
161
|
+
await expect.poll(getTheme).toBe("light");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("continues to observe the host without matchMedia", async () => {
|
|
165
|
+
vi.stubGlobal("matchMedia", undefined);
|
|
166
|
+
document.body.style.colorScheme = "light dark";
|
|
167
|
+
subscribe();
|
|
168
|
+
expect(getTheme()).toBe("light");
|
|
169
|
+
document.body.style.colorScheme = "only dark";
|
|
170
|
+
await expect.poll(getTheme).toBe("dark");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it.each(["light", "dark"] as const)(
|
|
174
|
+
"does not override an explicit %s host when system preferences change",
|
|
175
|
+
(theme) => {
|
|
176
|
+
document.body.style.colorScheme = theme;
|
|
177
|
+
subscribe();
|
|
178
|
+
for (const matches of [true, false]) {
|
|
179
|
+
setSystemTheme(matches);
|
|
180
|
+
expect(getTheme()).toBe(theme);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
it("re-reads host CSS when a media query changes its color-scheme", () => {
|
|
186
|
+
const computedStyle = window.getComputedStyle(document.body);
|
|
187
|
+
const scheme = vi.spyOn(computedStyle, "colorScheme", "get");
|
|
188
|
+
vi.spyOn(window, "getComputedStyle").mockReturnValue(computedStyle);
|
|
189
|
+
scheme.mockReturnValue("light");
|
|
190
|
+
subscribe();
|
|
191
|
+
expect(getTheme()).toBe("light");
|
|
192
|
+
scheme.mockReturnValue("dark");
|
|
193
|
+
media.dispatchEvent(new Event("change"));
|
|
194
|
+
expect(getTheme()).toBe("dark");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("discards queued mutations and media events after the last unsubscribe", async () => {
|
|
198
|
+
const readStyle = vi.spyOn(window, "getComputedStyle");
|
|
199
|
+
subscribe();
|
|
200
|
+
document.body.style.colorScheme = "dark";
|
|
201
|
+
stopObserving();
|
|
202
|
+
readStyle.mockClear();
|
|
203
|
+
media.dispatchEvent(new Event("change"));
|
|
204
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
205
|
+
expect(readStyle).not.toHaveBeenCalled();
|
|
206
|
+
subscribe();
|
|
207
|
+
expect(getTheme()).toBe("dark");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("keeps the VS Code override and returns to the host after it is removed", async () => {
|
|
211
|
+
document.body.classList.add("dark");
|
|
212
|
+
const { container, unmount } = render(
|
|
213
|
+
<StyleNamespace>Widget</StyleNamespace>,
|
|
214
|
+
);
|
|
215
|
+
for (const [kind, theme] of [
|
|
216
|
+
["vscode-light", "light"],
|
|
217
|
+
["vscode-high-contrast", "dark"],
|
|
218
|
+
["vscode-light", "light"],
|
|
219
|
+
]) {
|
|
220
|
+
document.body.dataset.vscodeThemeKind = kind;
|
|
221
|
+
await waitFor(() => expect(container.firstChild).toHaveClass(theme));
|
|
222
|
+
}
|
|
223
|
+
delete document.body.dataset.vscodeThemeKind;
|
|
224
|
+
await waitFor(() => expect(container.firstChild).toHaveClass("dark"));
|
|
225
|
+
unmount();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("does not subscribe to host themes outside Islands", async () => {
|
|
229
|
+
vi.stubEnv("VITE_MARIMO_ISLANDS", "false");
|
|
230
|
+
const config = defaultUserConfig();
|
|
231
|
+
store.set(userConfigAtom, {
|
|
232
|
+
...config,
|
|
233
|
+
display: { ...config.display, theme: "light" },
|
|
234
|
+
});
|
|
235
|
+
const observe = vi.spyOn(MutationObserver.prototype, "observe");
|
|
236
|
+
const addListener = vi.spyOn(media, "addEventListener");
|
|
237
|
+
subscribe();
|
|
238
|
+
document.body.classList.add("dark");
|
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
240
|
+
expect(getTheme()).toBe("light");
|
|
241
|
+
expect(observe).not.toHaveBeenCalled();
|
|
242
|
+
expect(addListener).not.toHaveBeenCalled();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("updates the namespace root used by shadow DOM widgets", async () => {
|
|
246
|
+
const { container } = render(<StyleNamespace>Widget</StyleNamespace>);
|
|
247
|
+
expect(container.firstChild).toHaveClass("marimo", "light");
|
|
248
|
+
document.body.classList.add("dark");
|
|
249
|
+
await waitFor(() =>
|
|
250
|
+
expect(container.firstChild).toHaveClass("marimo", "dark"),
|
|
251
|
+
);
|
|
252
|
+
document.body.classList.remove("dark");
|
|
253
|
+
await waitFor(() =>
|
|
254
|
+
expect(container.firstChild).toHaveClass("marimo", "light"),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("updates the dictionary viewer palette without remounting", async () => {
|
|
259
|
+
const { container } = render(
|
|
260
|
+
<JsonOutput data={{ relu: 0, note: "text" }} />,
|
|
261
|
+
);
|
|
262
|
+
const viewer = container.querySelector<HTMLElement>(
|
|
263
|
+
".w-json-view-container",
|
|
264
|
+
);
|
|
265
|
+
expect(viewer).not.toBeNull();
|
|
266
|
+
for (const theme of ["dark", "light"] as const) {
|
|
267
|
+
document.body.dataset.theme = theme;
|
|
268
|
+
const palette = theme === "dark" ? marimoDarkTheme : marimoLightTheme;
|
|
269
|
+
await waitFor(() =>
|
|
270
|
+
expect(viewer?.style.getPropertyValue("--w-rjv-color")).toBe(
|
|
271
|
+
palette["--w-rjv-color"],
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
});
|
package/src/theme/namespace.tsx
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import React, { type PropsWithChildren } from "react";
|
|
3
|
+
import { useTheme } from "./useTheme";
|
|
3
4
|
|
|
4
5
|
const style = {
|
|
5
6
|
display: "contents",
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* so that the styles are scoped to the DOM subtree.
|
|
10
|
+
* Scopes styles and applies the theme before descendants inherit text colors.
|
|
11
11
|
*/
|
|
12
12
|
export const StyleNamespace = React.forwardRef<
|
|
13
13
|
HTMLDivElement,
|
|
14
14
|
PropsWithChildren<{}>
|
|
15
15
|
>(({ children }, ref) => {
|
|
16
|
+
const { theme } = useTheme();
|
|
16
17
|
return (
|
|
17
|
-
<div ref={ref} className=
|
|
18
|
+
<div ref={ref} className={`marimo ${theme}`} style={style}>
|
|
18
19
|
{children}
|
|
19
20
|
</div>
|
|
20
21
|
);
|