@marimo-team/islands 0.23.17-dev3 → 0.23.17-dev30
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-f9vLjQiF.js → ConnectedDataExplorerComponent-BEb8RZam.js} +13 -13
- package/dist/{ErrorBoundary-DhLqwSWA.js → ErrorBoundary-8ZHte0h9.js} +3 -3
- package/dist/{ImageComparisonComponent-C3y7L3eJ.js → ImageComparisonComponent-fdY0LUuf.js} +38 -27
- package/dist/{any-language-editor-DifE-fl7.js → any-language-editor-CioNaP-T.js} +5 -5
- package/dist/{button-BSm8IXRU.js → button-8aS1QcHr.js} +4 -2
- package/dist/{capabilities-C6OQ5YBO.js → capabilities-D23lgbH_.js} +1 -1
- package/dist/{chat-ui-DsZJj75A.js → chat-ui-CnjFz0bt.js} +16 -16
- package/dist/{check-DP7U0H0G.js → check-CCvJTB2E.js} +1 -1
- package/dist/{common-D747zlkP.js → common-BxnkCaMd.js} +21 -21
- package/dist/{copy-DNq8q3zw.js → copy-D45ZUY3j.js} +2 -2
- package/dist/{dist-DIlxLFXG.js → dist-DgzzIPS4.js} +2 -2
- package/dist/{error-banner-B_GQfxxD.js → error-banner-DHfPl1w3.js} +2 -2
- package/dist/{esm-DzZVaNH4.js → esm-DJsE6wi-.js} +1 -1
- package/dist/{extends-w2_S44bV.js → extends-xQ_2Cylt.js} +4 -4
- package/dist/{formats-Dwq1THhr.js → formats-D10vOEsT.js} +3 -3
- package/dist/{glide-data-editor-B9K6-MJe.js → glide-data-editor-FiwJJzrV.js} +9 -9
- package/dist/{html-to-image-CZ1kLKkq.js → html-to-image-B8KJvlou.js} +2111 -2105
- package/dist/{input-C5K4VBvB.js → input-DtTqU0xh.js} +6 -6
- package/dist/{label-DjoYwqmj.js → label-5IC840tS.js} +2 -2
- package/dist/{loader-DX-IDS7n.js → loader-DP2JkPth.js} +1 -1
- package/dist/main.js +47 -44
- package/dist/{mermaid-hv6IckVw.js → mermaid-BNhC-PrB.js} +5 -5
- package/dist/{process-output-bVfbcx5_.js → process-output-CjS9wS4l.js} +3 -3
- package/dist/{reveal-component-CcSMtt1K.js → reveal-component-D8ttQmaR.js} +11 -11
- package/dist/{spec-CQKL_D3i.js → spec-C7rMyZ_8.js} +4 -4
- package/dist/{strings-DIy9NAmg.js → strings-CITcoFNY.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-CEdAYjb7.js → swiper-component-dl1H9WIW.js} +2 -2
- package/dist/{toDate-CqhBWjvO.js → toDate-kxcuDOWd.js} +3 -3
- package/dist/{tooltip-f-qR0iUx.js → tooltip-BkSv4x8S.js} +3 -3
- package/dist/{types-DicH9Oi0.js → types-kNUyqkDD.js} +1 -1
- package/dist/{useAsyncData-CYOQb1EW.js → useAsyncData-Uo4VFafD.js} +1 -1
- package/dist/{useDateFormatter-Bt2qr_XS.js → useDateFormatter-ZCfHadYP.js} +2 -2
- package/dist/{useDeepCompareMemoize-CSHQeQEI.js → useDeepCompareMemoize-s3ZvmdK3.js} +1 -1
- package/dist/{useIframeCapabilities-kA09ykNq.js → useIframeCapabilities-pLC-ceDP.js} +1 -1
- package/dist/{useLifecycle-CMJGqBaY.js → useLifecycle-CLPBATCv.js} +3 -3
- package/dist/{useTheme-DYfby65G.js → useTheme-CEMsu1Js.js} +2 -2
- package/dist/{vega-component-C7nO5zcf.js → vega-component-DGkcoXu3.js} +10 -10
- package/dist/{zod-BUcyXSdR.js → zod-DdNi9290.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +5 -0
- package/src/components/databases/icons/huggingface.svg +8 -0
- package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +35 -0
- package/src/components/editor/actions/export-dialog/__tests__/state.test.ts +61 -0
- package/src/components/editor/actions/export-dialog/export-dialog.tsx +27 -23
- package/src/components/editor/actions/export-dialog/format-notice.tsx +28 -0
- package/src/components/editor/actions/export-dialog/state.ts +23 -5
- package/src/components/editor/actions/export-dialog/use-export-dialog.ts +7 -15
- package/src/components/editor/chrome/panels/outline/__tests__/useActiveOutline.test.ts +26 -0
- package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +114 -2
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +14 -0
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +20 -0
- package/src/components/editor/connections/storage/add-storage-form.tsx +10 -0
- package/src/components/editor/connections/storage/as-code.ts +19 -1
- package/src/components/editor/connections/storage/schemas.ts +19 -0
- package/src/components/storage/__tests__/storage-snippets.test.ts +88 -0
- package/src/components/storage/components.tsx +2 -0
- package/src/components/storage/storage-snippets.ts +58 -0
- package/src/core/__tests__/mode.test.ts +85 -0
- package/src/core/codemirror/code-lens/__tests__/extension.test.ts +2 -7
- package/src/core/codemirror/code-lens/extension.ts +0 -4
- package/src/core/codemirror/find-replace/search-highlight.ts +8 -3
- package/src/core/codemirror/theme/__tests__/light.test.ts +59 -123
- package/src/core/codemirror/theme/dark.ts +6 -5
- package/src/core/codemirror/theme/light.ts +6 -4
- package/src/core/config/feature-flag.tsx +0 -2
- package/src/core/dom/outline.ts +25 -2
- package/src/core/hotkeys/hotkeys.ts +2 -0
- package/src/core/mode.ts +11 -9
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -0
- package/src/core/storage/types.ts +1 -0
- package/src/css/app/codemirror.css +11 -7
- package/src/css/globals.css +8 -0
- package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +20 -1
- package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +58 -0
- package/src/stories/theme.stories.tsx +71 -1
|
@@ -281,12 +281,8 @@ const codeLensTheme = EditorView.baseTheme({
|
|
|
281
281
|
/**
|
|
282
282
|
* Inline icons linking datasource/bucket variables and `mo.cache` /
|
|
283
283
|
* `mo.persistent_cache` calls to their panels.
|
|
284
|
-
* Gated behind the `editor_code_lens` feature flag.
|
|
285
284
|
*/
|
|
286
285
|
export function codeLensBundle(cellId: CellId): Extension {
|
|
287
|
-
if (!getFeatureFlag("editor_code_lens")) {
|
|
288
|
-
return [];
|
|
289
|
-
}
|
|
290
286
|
return [
|
|
291
287
|
codeLensField,
|
|
292
288
|
codeLensHoverField,
|
|
@@ -140,11 +140,16 @@ export const searchHighlighter = ViewPlugin.fromClass(
|
|
|
140
140
|
);
|
|
141
141
|
|
|
142
142
|
export const highlightTheme = EditorView.baseTheme({
|
|
143
|
-
"&light .cm-searchMatch": {
|
|
144
|
-
|
|
143
|
+
"&light .cm-searchMatch": {
|
|
144
|
+
backgroundColor: "var(--cm-search-match-background-light)",
|
|
145
|
+
},
|
|
146
|
+
"&dark .cm-searchMatch": {
|
|
147
|
+
backgroundColor: "var(--cm-search-match-background-dark)",
|
|
148
|
+
},
|
|
145
149
|
|
|
146
150
|
"&light .cm-searchMatch-selected": { backgroundColor: "transparent" },
|
|
147
151
|
"&dark .cm-searchMatch.cm-searchMatch-selected": {
|
|
148
|
-
backgroundColor:
|
|
152
|
+
backgroundColor:
|
|
153
|
+
"var(--cm-search-match-selected-background-dark) !important",
|
|
149
154
|
},
|
|
150
155
|
});
|
|
@@ -1,136 +1,72 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import type { Extension } from "@codemirror/state";
|
|
4
|
+
import { EditorView } from "@codemirror/view";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { darkTheme } from "../dark";
|
|
5
7
|
import { lightTheme } from "../light";
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
const getThemeConfig = () => ({
|
|
9
|
-
variant: "light",
|
|
10
|
-
settings: {
|
|
11
|
-
background: "#ffffff",
|
|
12
|
-
foreground: "#000000",
|
|
13
|
-
caret: "#000000",
|
|
14
|
-
selection: "#d7d4f0",
|
|
15
|
-
lineHighlight: "#cceeff44",
|
|
16
|
-
gutterBackground: "var(--color-background)",
|
|
17
|
-
gutterForeground: "var(--gray-10)",
|
|
18
|
-
},
|
|
19
|
-
styles: [
|
|
20
|
-
{ tag: t.comment, color: "#708090" },
|
|
21
|
-
{ tag: t.variableName, color: "#000000" },
|
|
22
|
-
{ tag: [t.string, t.special(t.brace)], color: "#a11" },
|
|
23
|
-
{ tag: t.number, color: "#164" },
|
|
24
|
-
{ tag: t.bool, color: "#219" },
|
|
25
|
-
{ tag: t.null, color: "#219" },
|
|
26
|
-
{ tag: t.keyword, color: "#708", fontWeight: 500 },
|
|
27
|
-
{ tag: t.className, color: "#00f" },
|
|
28
|
-
{ tag: t.definition(t.typeName), color: "#00f" },
|
|
29
|
-
{ tag: t.typeName, color: "#085" },
|
|
30
|
-
{ tag: t.angleBracket, color: "#000000" },
|
|
31
|
-
{ tag: t.tagName, color: "#170" },
|
|
32
|
-
{ tag: t.attributeName, color: "#00c" },
|
|
33
|
-
{ tag: t.operator, color: "#a2f", fontWeight: 500 },
|
|
34
|
-
{ tag: [t.function(t.variableName)], color: "#00c" },
|
|
35
|
-
{ tag: [t.propertyName], color: "#05a" },
|
|
36
|
-
],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("lightTheme", () => {
|
|
40
|
-
it("should export a theme", () => {
|
|
41
|
-
expect(lightTheme).toBeDefined();
|
|
42
|
-
expect(typeof lightTheme).toBe("object");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe("theme configuration", () => {
|
|
46
|
-
const config = getThemeConfig();
|
|
47
|
-
|
|
48
|
-
it("should have correct basic settings", () => {
|
|
49
|
-
expect(config.variant).toBe("light");
|
|
50
|
-
expect(config.settings).toEqual({
|
|
51
|
-
background: "#ffffff",
|
|
52
|
-
foreground: "#000000",
|
|
53
|
-
caret: "#000000",
|
|
54
|
-
selection: "#d7d4f0",
|
|
55
|
-
lineHighlight: "#cceeff44",
|
|
56
|
-
gutterBackground: "var(--color-background)",
|
|
57
|
-
gutterForeground: "var(--gray-10)",
|
|
58
|
-
});
|
|
59
|
-
});
|
|
9
|
+
const mounted: Array<{ host: HTMLDivElement; view: EditorView }> = [];
|
|
60
10
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
11
|
+
function mountTheme(extension: Extension) {
|
|
12
|
+
const host = document.createElement("div");
|
|
13
|
+
document.body.append(host);
|
|
14
|
+
const root = host.attachShadow({ mode: "open" });
|
|
15
|
+
const parent = document.createElement("div");
|
|
16
|
+
root.append(parent);
|
|
17
|
+
const view = new EditorView({ extensions: extension, parent, root });
|
|
18
|
+
mounted.push({ host, view });
|
|
66
19
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
20
|
+
const style = root.querySelector("style");
|
|
21
|
+
if (!style) {
|
|
22
|
+
throw new Error("CodeMirror did not mount its theme styles");
|
|
23
|
+
}
|
|
24
|
+
return { css: style.textContent ?? "", view };
|
|
25
|
+
}
|
|
73
26
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
expect(style?.tag).toContain(t.special(t.brace));
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("should style numbers and boolean values", () => {
|
|
84
|
-
expect(findStyle(t.number)).toEqual({
|
|
85
|
-
tag: t.number,
|
|
86
|
-
color: "#164",
|
|
87
|
-
});
|
|
88
|
-
expect(findStyle(t.bool)).toEqual({
|
|
89
|
-
tag: t.bool,
|
|
90
|
-
color: "#219",
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("should style keywords with emphasis", () => {
|
|
95
|
-
expect(findStyle(t.keyword)).toEqual({
|
|
96
|
-
tag: t.keyword,
|
|
97
|
-
color: "#708",
|
|
98
|
-
fontWeight: 500,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
for (const { host, view } of mounted.splice(0)) {
|
|
29
|
+
view.destroy();
|
|
30
|
+
host.remove();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
101
33
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
34
|
+
const themeCases = [
|
|
35
|
+
{
|
|
36
|
+
name: "light",
|
|
37
|
+
extension: lightTheme,
|
|
38
|
+
dark: false,
|
|
39
|
+
selectionAlias: "var(--cm-selection-background-light)",
|
|
40
|
+
reactiveColor: "var(--cm-reactive-reference-color-light)",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "dark",
|
|
44
|
+
extension: darkTheme,
|
|
45
|
+
dark: true,
|
|
46
|
+
selectionAlias: "var(--cm-selection-background-dark)",
|
|
47
|
+
reactiveColor: "var(--cm-reactive-reference-color-dark)",
|
|
48
|
+
},
|
|
49
|
+
] as const;
|
|
109
50
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
color: "#00f",
|
|
114
|
-
});
|
|
115
|
-
expect(findStyle(t.typeName)).toEqual({
|
|
116
|
-
tag: t.typeName,
|
|
117
|
-
color: "#085",
|
|
118
|
-
});
|
|
119
|
-
});
|
|
51
|
+
describe.each(themeCases)("$name theme", (theme) => {
|
|
52
|
+
it("uses the production theme tokens", () => {
|
|
53
|
+
const { css, view } = mountTheme(theme.extension);
|
|
120
54
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
55
|
+
expect(view.state.facet(EditorView.darkTheme)).toBe(theme.dark);
|
|
56
|
+
expect(css).toContain(
|
|
57
|
+
`--cm-selection-background: ${theme.selectionAlias};`,
|
|
58
|
+
);
|
|
59
|
+
expect(css).toContain("background-color: var(--cm-selection-background);");
|
|
60
|
+
expect(css).toContain(`color: ${theme.reactiveColor};`);
|
|
61
|
+
expect(css).toContain(
|
|
62
|
+
"border-bottom: 2px solid var(--cm-reactive-reference-border-color);",
|
|
63
|
+
);
|
|
127
64
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
65
|
+
const hoverRule = css
|
|
66
|
+
.split("\n")
|
|
67
|
+
.find((rule) => rule.includes(".mo-cm-reactive-reference-hover"));
|
|
68
|
+
expect(hoverRule).toBeDefined();
|
|
69
|
+
expect(hoverRule).toContain("cursor: pointer;");
|
|
70
|
+
expect(hoverRule).not.toContain("border-bottom");
|
|
135
71
|
});
|
|
136
72
|
});
|
|
@@ -11,7 +11,7 @@ export const darkTheme = [
|
|
|
11
11
|
background: "var(--cm-background)",
|
|
12
12
|
foreground: "#abb2bf",
|
|
13
13
|
caret: "#528bff",
|
|
14
|
-
selection: "
|
|
14
|
+
selection: "var(--cm-selection-background)",
|
|
15
15
|
lineHighlight: "#2c313c",
|
|
16
16
|
gutterBackground: "var(--color-background)",
|
|
17
17
|
gutterForeground: "var(--gray-10)",
|
|
@@ -36,15 +36,16 @@ export const darkTheme = [
|
|
|
36
36
|
],
|
|
37
37
|
}),
|
|
38
38
|
EditorView.theme({
|
|
39
|
+
"&": {
|
|
40
|
+
"--cm-selection-background": "var(--cm-selection-background-dark)",
|
|
41
|
+
},
|
|
39
42
|
".mo-cm-reactive-reference": {
|
|
40
43
|
fontWeight: "400",
|
|
41
|
-
color: "
|
|
42
|
-
borderBottom: "2px solid
|
|
44
|
+
color: "var(--cm-reactive-reference-color-dark)",
|
|
45
|
+
borderBottom: "2px solid var(--cm-reactive-reference-border-color)",
|
|
43
46
|
},
|
|
44
47
|
".mo-cm-reactive-reference-hover": {
|
|
45
48
|
cursor: "pointer",
|
|
46
|
-
borderBottomWidth: "3px",
|
|
47
|
-
borderBottomColor: "#4a90a5",
|
|
48
49
|
},
|
|
49
50
|
}),
|
|
50
51
|
];
|
|
@@ -11,7 +11,7 @@ export const lightTheme = [
|
|
|
11
11
|
background: "#ffffff",
|
|
12
12
|
foreground: "#000000",
|
|
13
13
|
caret: "#000000",
|
|
14
|
-
selection: "
|
|
14
|
+
selection: "var(--cm-selection-background)",
|
|
15
15
|
lineHighlight: "#cceeff44",
|
|
16
16
|
gutterBackground: "var(--color-background)",
|
|
17
17
|
gutterForeground: "var(--gray-10)",
|
|
@@ -45,14 +45,16 @@ export const lightTheme = [
|
|
|
45
45
|
],
|
|
46
46
|
}),
|
|
47
47
|
EditorView.theme({
|
|
48
|
+
"&": {
|
|
49
|
+
"--cm-selection-background": "var(--cm-selection-background-light)",
|
|
50
|
+
},
|
|
48
51
|
".mo-cm-reactive-reference": {
|
|
49
52
|
fontWeight: "400",
|
|
50
|
-
color: "
|
|
51
|
-
borderBottom: "2px solid
|
|
53
|
+
color: "var(--cm-reactive-reference-color-light)",
|
|
54
|
+
borderBottom: "2px solid var(--cm-reactive-reference-border-color)",
|
|
52
55
|
},
|
|
53
56
|
".mo-cm-reactive-reference-hover": {
|
|
54
57
|
cursor: "pointer",
|
|
55
|
-
borderBottomWidth: "3px",
|
|
56
58
|
},
|
|
57
59
|
}),
|
|
58
60
|
];
|
|
@@ -13,7 +13,6 @@ export interface ExperimentalFeatures {
|
|
|
13
13
|
external_agents: boolean;
|
|
14
14
|
debugger: boolean; // Live frame-watching debugger (gutter breakpoints + pdb)
|
|
15
15
|
line_timing: boolean; // Green active-line highlight + per-line elapsed timer
|
|
16
|
-
editor_code_lens: boolean; // Inline icons in cell editors linking datasources/buckets/caches to their panels
|
|
17
16
|
// Add new feature flags here
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -25,7 +24,6 @@ const defaultValues: ExperimentalFeatures = {
|
|
|
25
24
|
external_agents: import.meta.env.DEV,
|
|
26
25
|
debugger: false,
|
|
27
26
|
line_timing: false,
|
|
28
|
-
editor_code_lens: import.meta.env.DEV,
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
export function getFeatureFlag<T extends keyof ExperimentalFeatures>(
|
package/src/core/dom/outline.ts
CHANGED
|
@@ -63,13 +63,36 @@ function getOutline(html: string): Outline | null {
|
|
|
63
63
|
return { items };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function toXPathStringLiteral(value: string): string {
|
|
67
|
+
if (!value.includes('"')) {
|
|
68
|
+
return `"${value}"`;
|
|
69
|
+
}
|
|
70
|
+
if (!value.includes("'")) {
|
|
71
|
+
return `'${value}'`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const segments = value.split('"');
|
|
75
|
+
const literals: string[] = [];
|
|
76
|
+
for (const [index, segment] of segments.entries()) {
|
|
77
|
+
if (segment) {
|
|
78
|
+
literals.push(`"${segment}"`);
|
|
79
|
+
}
|
|
80
|
+
if (index < segments.length - 1) {
|
|
81
|
+
literals.push(`'"'`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return `concat(${literals.join(", ")})`;
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
export function headingToIdentifier(heading: Element): OutlineItem["by"] {
|
|
67
88
|
const id = heading.id;
|
|
68
89
|
if (id) {
|
|
69
90
|
return { id };
|
|
70
91
|
}
|
|
71
|
-
const name = heading.textContent;
|
|
72
|
-
return {
|
|
92
|
+
const name = heading.textContent ?? "";
|
|
93
|
+
return {
|
|
94
|
+
path: `//${heading.tagName}[contains(., ${toXPathStringLiteral(name)})]`,
|
|
95
|
+
};
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
export function mergeOutlines(outlines: (Outline | null)[]): Outline {
|
|
@@ -393,11 +393,13 @@ const DEFAULT_HOT_KEY = {
|
|
|
393
393
|
name: "Collapse all sections",
|
|
394
394
|
group: "Editing",
|
|
395
395
|
key: "Mod-Shift-\\",
|
|
396
|
+
additionalKeywords: ["fold", "headers"],
|
|
396
397
|
},
|
|
397
398
|
"global.expandAllSections": {
|
|
398
399
|
name: "Expand all sections",
|
|
399
400
|
group: "Editing",
|
|
400
401
|
key: "Mod-Shift-/",
|
|
402
|
+
additionalKeywords: ["unfold", "headers"],
|
|
401
403
|
},
|
|
402
404
|
"global.toggleMinimap": {
|
|
403
405
|
name: "Toggle Minimap",
|
package/src/core/mode.ts
CHANGED
|
@@ -55,20 +55,22 @@ export async function runDuringPresentMode(
|
|
|
55
55
|
fn: () => void | Promise<void>,
|
|
56
56
|
): Promise<void> {
|
|
57
57
|
const state = store.get(viewStateAtom);
|
|
58
|
-
if (state.mode
|
|
58
|
+
if (state.mode !== "edit") {
|
|
59
59
|
await fn();
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
store.set(viewStateAtom, { ...state, mode: "present" });
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
try {
|
|
65
|
+
// Wait 100ms to allow the page to render
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
67
|
+
// Wait 2 frames
|
|
68
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
69
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
70
|
+
await fn();
|
|
71
|
+
} finally {
|
|
72
|
+
store.set(viewStateAtom, state);
|
|
73
|
+
}
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
export const viewStateAtom = atom<ViewState>({
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
@reference "../globals.css";
|
|
2
2
|
|
|
3
3
|
.light .cm-selectionBackground {
|
|
4
|
-
/*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/* Keep drawn selections visible when focus moves to find/replace or another editor. */
|
|
5
|
+
background-color: var(
|
|
6
|
+
--cm-selection-background,
|
|
7
|
+
var(--cm-selection-background-light)
|
|
8
|
+
) !important;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
.dark .cm-selectionBackground {
|
|
11
|
-
background-color:
|
|
12
|
+
background-color: var(
|
|
13
|
+
--cm-selection-background,
|
|
14
|
+
var(--cm-selection-background-dark)
|
|
15
|
+
) !important;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
.cm-focused.cm-focused {
|
|
@@ -125,7 +129,7 @@
|
|
|
125
129
|
/* -- Codeium -- */
|
|
126
130
|
|
|
127
131
|
.cm-codeium-cycle {
|
|
128
|
-
font-size:
|
|
132
|
+
font-size: 0.5625rem;
|
|
129
133
|
background-color: var(--sky-3);
|
|
130
134
|
padding: 2px;
|
|
131
135
|
border-radius: 2px;
|
|
@@ -133,7 +137,7 @@
|
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
.cm-codeium-cycle-key {
|
|
136
|
-
font-size:
|
|
140
|
+
font-size: 0.5625rem;
|
|
137
141
|
font-family: monospace;
|
|
138
142
|
display: inline-block;
|
|
139
143
|
padding: 2px;
|
package/src/css/globals.css
CHANGED
|
@@ -107,6 +107,14 @@
|
|
|
107
107
|
/* Codemirror editor */
|
|
108
108
|
--cm-background: light-dark(var(--background), #282c34);
|
|
109
109
|
--cm-comment: light-dark(#708090, #6b7280);
|
|
110
|
+
--cm-selection-background-light: #d7d4f0;
|
|
111
|
+
--cm-selection-background-dark: #1177cc80;
|
|
112
|
+
--cm-reactive-reference-color-light: #005f87;
|
|
113
|
+
--cm-reactive-reference-color-dark: #2a7aa5;
|
|
114
|
+
--cm-reactive-reference-border-color: #bad3de;
|
|
115
|
+
--cm-search-match-background-light: #99ff7780;
|
|
116
|
+
--cm-search-match-background-dark: #22bb0070;
|
|
117
|
+
--cm-search-match-selected-background-dark: #6199ff88;
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
.dark,
|
|
@@ -49,6 +49,18 @@ const ImageComparisonComponent: React.FC<ImageComparisonData> = ({
|
|
|
49
49
|
minHeight: "2rem",
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// The slider's width="100%" images resolve against its shadow-DOM wrappers (circular),
|
|
53
|
+
// so size the images explicitly and letterbox with object-fit to keep them aligned.
|
|
54
|
+
// Treat empty strings as "not provided" (normalize_dimension passes them through).
|
|
55
|
+
const imageStyle: React.CSSProperties | undefined =
|
|
56
|
+
width || height
|
|
57
|
+
? {
|
|
58
|
+
width: width || undefined,
|
|
59
|
+
height: height || undefined,
|
|
60
|
+
objectFit: "contain",
|
|
61
|
+
}
|
|
62
|
+
: undefined;
|
|
63
|
+
|
|
52
64
|
// If an image fails to load, the slider collapses to nothing; surface a
|
|
53
65
|
// visible error instead of silently rendering an empty output.
|
|
54
66
|
if (failedSrcs.size > 0) {
|
|
@@ -67,12 +79,18 @@ const ImageComparisonComponent: React.FC<ImageComparisonData> = ({
|
|
|
67
79
|
|
|
68
80
|
return (
|
|
69
81
|
<div style={containerStyle}>
|
|
70
|
-
<ImgComparisonSlider
|
|
82
|
+
<ImgComparisonSlider
|
|
83
|
+
value={value}
|
|
84
|
+
direction={direction}
|
|
85
|
+
// inline-block leaves a baseline gap below the slider; align to top.
|
|
86
|
+
style={{ verticalAlign: "top" }}
|
|
87
|
+
>
|
|
71
88
|
<img
|
|
72
89
|
slot="first"
|
|
73
90
|
src={beforeSrc}
|
|
74
91
|
alt="Before"
|
|
75
92
|
width="100%"
|
|
93
|
+
style={imageStyle}
|
|
76
94
|
onError={() => handleError(beforeSrc)}
|
|
77
95
|
/>
|
|
78
96
|
<img
|
|
@@ -80,6 +98,7 @@ const ImageComparisonComponent: React.FC<ImageComparisonData> = ({
|
|
|
80
98
|
src={afterSrc}
|
|
81
99
|
alt="After"
|
|
82
100
|
width="100%"
|
|
101
|
+
style={imageStyle}
|
|
83
102
|
onError={() => handleError(afterSrc)}
|
|
84
103
|
/>
|
|
85
104
|
</ImgComparisonSlider>
|
|
@@ -68,4 +68,62 @@ describe("ImageComparisonComponent", () => {
|
|
|
68
68
|
expect(error?.textContent).toContain("…");
|
|
69
69
|
expect(error?.textContent).not.toContain("A".repeat(200));
|
|
70
70
|
});
|
|
71
|
+
|
|
72
|
+
it("sizes both images explicitly when dimensions are given", () => {
|
|
73
|
+
const { getAllByAltText } = render(
|
|
74
|
+
<ImageComparisonComponent {...baseProps} width="300px" height="300px" />,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
78
|
+
expect((img as HTMLElement).style.width).toBe("300px");
|
|
79
|
+
expect((img as HTMLElement).style.height).toBe("300px");
|
|
80
|
+
// Letterbox identically so the slider's clip stays aligned.
|
|
81
|
+
expect((img as HTMLElement).style.objectFit).toBe("contain");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("applies a single dimension to both images when only one is given", () => {
|
|
86
|
+
const { getAllByAltText } = render(
|
|
87
|
+
<ImageComparisonComponent {...baseProps} width="300px" />,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
91
|
+
expect((img as HTMLElement).style.width).toBe("300px");
|
|
92
|
+
expect((img as HTMLElement).style.objectFit).toBe("contain");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("applies height-only sizing to both images", () => {
|
|
97
|
+
const { getAllByAltText } = render(
|
|
98
|
+
<ImageComparisonComponent {...baseProps} height="300px" />,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
102
|
+
expect((img as HTMLElement).style.height).toBe("300px");
|
|
103
|
+
expect((img as HTMLElement).style.objectFit).toBe("contain");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("treats empty-string dimensions as not provided", () => {
|
|
108
|
+
const { getAllByAltText } = render(
|
|
109
|
+
<ImageComparisonComponent {...baseProps} width="" height="" />,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
113
|
+
expect((img as HTMLElement).style.width).toBe("");
|
|
114
|
+
expect((img as HTMLElement).style.height).toBe("");
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("leaves images unstyled when no dimensions are given", () => {
|
|
119
|
+
const { getAllByAltText } = render(
|
|
120
|
+
<ImageComparisonComponent {...baseProps} />,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
124
|
+
// No explicit sizing: the slider keeps its intrinsic-size behavior.
|
|
125
|
+
expect((img as HTMLElement).style.width).toBe("");
|
|
126
|
+
expect((img as HTMLElement).style.height).toBe("");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
71
129
|
});
|