@surrealdb/ui 1.2.12 → 1.2.14

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.
Files changed (99) hide show
  1. package/.claude/launch.json +17 -0
  2. package/.oxfmtrc.json +27 -0
  3. package/.oxlintrc.json +23 -0
  4. package/.zed/settings.json +34 -34
  5. package/AGENTS.md +25 -19
  6. package/CLAUDE.md +111 -0
  7. package/README.md +15 -10
  8. package/dist/icons.d.ts +4 -0
  9. package/dist/icons.js +91 -89
  10. package/dist/icons.js.map +1 -1
  11. package/dist/ui.css +1 -1
  12. package/dist/ui.d.ts +259 -1
  13. package/dist/ui.js +9807 -12265
  14. package/dist/ui.js.map +1 -1
  15. package/doc/code-style.md +264 -0
  16. package/doc/release.md +102 -0
  17. package/doc/stacker-plan.md +219 -0
  18. package/package.json +120 -116
  19. package/release-notes.md +0 -0
  20. package/res/_mixins.scss +15 -11
  21. package/tests/_setup/e2e-helpers.tsx +85 -85
  22. package/tests/_setup/portable-stories.ts +11 -10
  23. package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +140 -139
  24. package/tests/e2e/MarkdownEditor/edits.test.tsx +101 -100
  25. package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +35 -34
  26. package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +178 -177
  27. package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +50 -49
  28. package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +195 -194
  29. package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +47 -46
  30. package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +35 -34
  31. package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +39 -38
  32. package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +28 -27
  33. package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +49 -48
  34. package/tests/e2e/MarkdownEditor/media-align.test.tsx +40 -39
  35. package/tests/e2e/MarkdownEditor/media-edit.test.tsx +49 -48
  36. package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +108 -107
  37. package/tests/e2e/MarkdownEditor/modes.test.tsx +79 -78
  38. package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +29 -28
  39. package/tests/e2e/MarkdownEditor/regressions.test.tsx +194 -193
  40. package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +81 -80
  41. package/tests/e2e/MarkdownEditor/table-click.test.tsx +36 -35
  42. package/tests/e2e/MarkdownEditor/table-controls.test.tsx +44 -43
  43. package/tests/e2e/MarkdownEditor/table-format.test.tsx +28 -27
  44. package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +22 -21
  45. package/tests/e2e/MarkdownViewer/parity.test.tsx +144 -143
  46. package/tests/e2e/MarkdownViewer/render.test.tsx +20 -19
  47. package/tests/e2e/Stacker/stacker.test.tsx +304 -0
  48. package/tests/unit/Editor/helpers.test.ts +26 -26
  49. package/tests/unit/MarkdownConformance/error-isolation.test.tsx +26 -25
  50. package/tests/unit/MarkdownConformance/indentation.test.tsx +20 -19
  51. package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +255 -0
  52. package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +39 -38
  53. package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +122 -122
  54. package/tests/unit/MarkdownConformance/links.test.tsx +78 -54
  55. package/tests/unit/MarkdownConformance/render-helpers.tsx +60 -60
  56. package/tests/unit/MarkdownEditor/code-info.test.ts +58 -58
  57. package/tests/unit/MarkdownEditor/decorations.test.ts +423 -421
  58. package/tests/unit/MarkdownEditor/editor-ready.test.ts +22 -22
  59. package/tests/unit/MarkdownEditor/html-descriptors.test.ts +70 -70
  60. package/tests/unit/MarkdownEditor/indented-fence.test.ts +80 -80
  61. package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +87 -87
  62. package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +51 -51
  63. package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +63 -63
  64. package/tests/unit/MarkdownEditor/list-indent.test.ts +67 -67
  65. package/tests/unit/MarkdownEditor/slash-commands.test.ts +150 -150
  66. package/tests/unit/MarkdownEditor/table-format.test.ts +67 -67
  67. package/tests/unit/MarkdownEditor/table.test.ts +96 -96
  68. package/tests/unit/MarkdownEditor/triggers.test.ts +167 -167
  69. package/tests/unit/MarkdownEditor/widget-store.test.ts +90 -90
  70. package/tests/unit/MarkdownViewer/callout.test.tsx +42 -42
  71. package/tests/unit/MarkdownViewer/code-highlight.test.tsx +41 -41
  72. package/tests/unit/MarkdownViewer/code-title.test.tsx +45 -45
  73. package/tests/unit/MarkdownViewer/features.test.tsx +129 -129
  74. package/tests/unit/MarkdownViewer/headings.test.tsx +28 -28
  75. package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +53 -53
  76. package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +137 -137
  77. package/tests/unit/MarkdownViewer/jsx.test.tsx +188 -188
  78. package/tests/unit/MarkdownViewer/list-bullets.test.tsx +31 -31
  79. package/tests/unit/MarkdownViewer/list-code.test.tsx +45 -45
  80. package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +109 -109
  81. package/tests/unit/MarkdownViewer/read-time.test.ts +13 -13
  82. package/tests/unit/MarkdownViewer/renderers.test.tsx +75 -75
  83. package/tests/unit/MarkdownViewer/runnable.test.tsx +51 -51
  84. package/tests/unit/MarkdownViewer/ssr.test.tsx +56 -55
  85. package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +43 -42
  86. package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +120 -120
  87. package/tests/unit/MarkdownViewer/tabs.test.tsx +83 -82
  88. package/tests/unit/Stacker/drag.test.ts +21 -0
  89. package/tests/unit/Stacker/helpers.test.ts +105 -0
  90. package/tests/unit/Stacker/reducer.test.ts +107 -0
  91. package/tools/duplicate-vector-mappings.json +141 -141
  92. package/tools/glass-export-overrides.json +6 -6
  93. package/tools/icon-parser.ts +41 -40
  94. package/tools/integration-gradient-exports.json +55 -55
  95. package/tools/picto-compare.ts +32 -32
  96. package/tools/picto-merge-urls.ts +19 -19
  97. package/tools/picto-sync.ts +543 -543
  98. package/tools/picto-verify-gradients.ts +50 -50
  99. package/tools/vector-export-nodes.json +975 -975
@@ -3,30 +3,31 @@ import type { MarkdownComponents } from "@src/lib/markdown";
3
3
  import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
4
4
  import { MANTINE_THEME } from "@src/theme/mantine";
5
5
  import { describe, expect, it } from "vitest";
6
+
6
7
  import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
7
8
 
8
9
  function Card({ label }: { label: string }) {
9
- return (
10
- <Paper
11
- withBorder
12
- p="xs"
13
- radius="sm"
14
- mb="xs"
15
- >
16
- <Group gap="xs">
17
- <Text size="sm">value =</Text>
18
- <Text size="sm">{label}</Text>
19
- </Group>
20
- </Paper>
21
- );
10
+ return (
11
+ <Paper
12
+ withBorder
13
+ p="xs"
14
+ radius="sm"
15
+ mb="xs"
16
+ >
17
+ <Group gap="xs">
18
+ <Text size="sm">value =</Text>
19
+ <Text size="sm">{label}</Text>
20
+ </Group>
21
+ </Paper>
22
+ );
22
23
  }
23
24
 
24
25
  const inlineComponents: MarkdownComponents = {
25
- Card,
26
+ Card,
26
27
  };
27
28
 
28
29
  const blockComponents: MarkdownComponents = {
29
- Card: { component: Card, block: true },
30
+ Card: { component: Card, block: true },
30
31
  };
31
32
 
32
33
  const ONE_PER_LINE_DOC = `# Heading
@@ -44,199 +45,199 @@ const MANY_PER_LINE_DOC = `# Heading
44
45
  `;
45
46
 
46
47
  function renderEditor(doc: string, components: MarkdownComponents) {
47
- return (
48
- <MantineProvider
49
- theme={MANTINE_THEME}
50
- cssVariablesResolver={v8CssVariablesResolver}
51
- forceColorScheme="dark"
52
- >
53
- <Paper
54
- p="md"
55
- withBorder
56
- >
57
- <MarkdownEditor
58
- mode="preview"
59
- document={doc}
60
- autoFocus={false}
61
- jsxMode="render"
62
- components={components}
63
- />
64
- </Paper>
65
- </MantineProvider>
66
- );
48
+ return (
49
+ <MantineProvider
50
+ theme={MANTINE_THEME}
51
+ cssVariablesResolver={v8CssVariablesResolver}
52
+ forceColorScheme="dark"
53
+ >
54
+ <Paper
55
+ p="md"
56
+ withBorder
57
+ >
58
+ <MarkdownEditor
59
+ mode="preview"
60
+ document={doc}
61
+ autoFocus={false}
62
+ jsxMode="render"
63
+ components={components}
64
+ />
65
+ </Paper>
66
+ </MantineProvider>
67
+ );
67
68
  }
68
69
 
69
70
  function lineHeightsForInlineWidgets(): number[] {
70
- const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
71
- return lines
72
- .filter((line) => line.querySelector('[data-md-widget="JsxInlineWidget"]'))
73
- .map((line) => line.getBoundingClientRect().height);
71
+ const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
72
+ return lines
73
+ .filter((line) => line.querySelector('[data-md-widget="JsxInlineWidget"]'))
74
+ .map((line) => line.getBoundingClientRect().height);
74
75
  }
75
76
 
76
77
  /** Block replace widgets render between `.cm-line` rows, not inside them. */
77
78
  function blockWidgetHostLine(widget: HTMLElement): HTMLElement | null {
78
- let el: HTMLElement | null = widget;
79
- while (el) {
80
- if (el.classList.contains("cm-line")) return el;
81
- el = el.parentElement;
82
- }
83
- return null;
79
+ let el: HTMLElement | null = widget;
80
+ while (el) {
81
+ if (el.classList.contains("cm-line")) return el;
82
+ el = el.parentElement;
83
+ }
84
+ return null;
84
85
  }
85
86
 
86
87
  async function mountPreviewEditor(doc: string, components: MarkdownComponents) {
87
- mountStory(renderEditor(doc, components));
88
- const view = await getEditorView();
89
- view.contentDOM.blur();
90
- await nextFrame();
91
- await nextFrame();
92
- return view;
88
+ mountStory(renderEditor(doc, components));
89
+ const view = await getEditorView();
90
+ view.contentDOM.blur();
91
+ await nextFrame();
92
+ await nextFrame();
93
+ return view;
93
94
  }
94
95
 
95
96
  describe("MarkdownEditor / inline JSX rendering block content", () => {
96
- it("does not stack cm-widgetBuffer images above and below the widget", async () => {
97
- mountStory(renderEditor(ONE_PER_LINE_DOC, inlineComponents));
98
-
99
- const view = await getEditorView();
100
- view.contentDOM.blur();
101
- await nextFrame();
102
- await nextFrame();
103
-
104
- const cardWidgets = document.querySelectorAll<HTMLElement>(
105
- '[data-md-widget="JsxInlineWidget"]',
106
- );
107
- expect(cardWidgets.length).toBe(3);
108
-
109
- const widgetHeights = Array.from(cardWidgets).map((w) => w.getBoundingClientRect().height);
110
- const lineHeights = lineHeightsForInlineWidgets();
111
- expect(lineHeights.length).toBe(3);
112
-
113
- for (let i = 0; i < lineHeights.length; i++) {
114
- const lineHeight = lineHeights[i] ?? 0;
115
- const widgetHeight = widgetHeights[i] ?? 0;
116
- expect(widgetHeight).toBeGreaterThan(0);
117
- expect(lineHeight).toBeGreaterThan(0);
118
- expect(lineHeight).toBeLessThan(widgetHeight + 20);
119
- }
120
- });
121
-
122
- it("keeps multiple inline JSX widgets on the same line side-by-side", async () => {
123
- mountStory(renderEditor(MANY_PER_LINE_DOC, inlineComponents));
124
-
125
- const view = await getEditorView();
126
- view.contentDOM.blur();
127
- await nextFrame();
128
- await nextFrame();
129
-
130
- const cardWidgets = document.querySelectorAll<HTMLElement>(
131
- '[data-md-widget="JsxInlineWidget"]',
132
- );
133
- expect(cardWidgets.length).toBe(3);
134
-
135
- const rects = Array.from(cardWidgets).map((w) => w.getBoundingClientRect());
136
- const firstTop = rects[0]?.top ?? 0;
137
- const tolerance = 4;
138
- for (const r of rects) {
139
- expect(Math.abs(r.top - firstTop)).toBeLessThan(tolerance);
140
- }
141
- });
97
+ it("does not stack cm-widgetBuffer images above and below the widget", async () => {
98
+ mountStory(renderEditor(ONE_PER_LINE_DOC, inlineComponents));
99
+
100
+ const view = await getEditorView();
101
+ view.contentDOM.blur();
102
+ await nextFrame();
103
+ await nextFrame();
104
+
105
+ const cardWidgets = document.querySelectorAll<HTMLElement>(
106
+ '[data-md-widget="JsxInlineWidget"]',
107
+ );
108
+ expect(cardWidgets.length).toBe(3);
109
+
110
+ const widgetHeights = Array.from(cardWidgets).map((w) => w.getBoundingClientRect().height);
111
+ const lineHeights = lineHeightsForInlineWidgets();
112
+ expect(lineHeights.length).toBe(3);
113
+
114
+ for (let i = 0; i < lineHeights.length; i++) {
115
+ const lineHeight = lineHeights[i] ?? 0;
116
+ const widgetHeight = widgetHeights[i] ?? 0;
117
+ expect(widgetHeight).toBeGreaterThan(0);
118
+ expect(lineHeight).toBeGreaterThan(0);
119
+ expect(lineHeight).toBeLessThan(widgetHeight + 20);
120
+ }
121
+ });
122
+
123
+ it("keeps multiple inline JSX widgets on the same line side-by-side", async () => {
124
+ mountStory(renderEditor(MANY_PER_LINE_DOC, inlineComponents));
125
+
126
+ const view = await getEditorView();
127
+ view.contentDOM.blur();
128
+ await nextFrame();
129
+ await nextFrame();
130
+
131
+ const cardWidgets = document.querySelectorAll<HTMLElement>(
132
+ '[data-md-widget="JsxInlineWidget"]',
133
+ );
134
+ expect(cardWidgets.length).toBe(3);
135
+
136
+ const rects = Array.from(cardWidgets).map((w) => w.getBoundingClientRect());
137
+ const firstTop = rects[0]?.top ?? 0;
138
+ const tolerance = 4;
139
+ for (const r of rects) {
140
+ expect(Math.abs(r.top - firstTop)).toBeLessThan(tolerance);
141
+ }
142
+ });
142
143
  });
143
144
 
144
145
  describe("MarkdownEditor / block JSX components", () => {
145
- it("renders block components via JsxBlockWidget with an edit-source action", async () => {
146
- await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
147
-
148
- const blockWidgets = document.querySelectorAll<HTMLElement>(
149
- '[data-md-widget="JsxBlockWidget"]',
150
- );
151
- expect(blockWidgets.length).toBe(3);
152
- expect(document.querySelectorAll('[data-md-widget="JsxInlineWidget"]').length).toBe(0);
153
-
154
- const editButtons = document.querySelectorAll<HTMLButtonElement>(
155
- '[aria-label="Edit source"]',
156
- );
157
- expect(editButtons.length).toBeGreaterThanOrEqual(3);
158
- });
159
-
160
- it("renders block widgets between cm-lines, not inside a cm-line", async () => {
161
- await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
162
-
163
- expect(document.querySelectorAll(".jsx-block-line-host").length).toBe(0);
164
-
165
- for (const widget of Array.from(
166
- document.querySelectorAll<HTMLElement>('[data-md-widget="JsxBlockWidget"]'),
167
- )) {
168
- expect(blockWidgetHostLine(widget)).toBeNull();
169
- const lineSibling = widget.closest(".cm-line");
170
- expect(lineSibling).toBeNull();
171
- }
172
- });
173
-
174
- it("block widgets do not get cm-widgetBuffer spacer images", async () => {
175
- await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
176
-
177
- for (const widget of Array.from(
178
- document.querySelectorAll<HTMLElement>('[data-md-widget="JsxBlockWidget"]'),
179
- )) {
180
- const parent = widget.parentElement;
181
- expect(parent?.querySelector("img.cm-widgetBuffer")).toBeNull();
182
- }
183
- });
184
-
185
- it("clicking the edit-source action selects the component source range", async () => {
186
- const view = await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
187
-
188
- const editButton = document.querySelector<HTMLButtonElement>('[aria-label="Edit source"]');
189
- expect(editButton).toBeTruthy();
190
- editButton?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
191
- await nextFrame();
192
- await nextFrame();
193
-
194
- const selection = view.state.selection.main;
195
- const selectedSource = view.state.sliceDoc(selection.from, selection.to);
196
- expect(selectedSource.trim()).toBe('<Card label="Alpha" />');
197
- });
198
-
199
- it("renders block components embedded in a mixed paragraph on a dedicated row", async () => {
200
- const doc = 'Intro text <Card label="Mid" /> trailing text.';
201
- await mountPreviewEditor(doc, blockComponents);
202
-
203
- const blockWidgets = document.querySelectorAll<HTMLElement>(
204
- '[data-md-widget="JsxBlockWidget"]',
205
- );
206
- expect(blockWidgets.length).toBe(1);
207
- const midWidget = blockWidgets[0];
208
- expect(midWidget?.textContent).toContain("Mid");
209
- expect(midWidget && blockWidgetHostLine(midWidget)).toBeNull();
210
-
211
- const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
212
- expect(lines.some((line) => line.classList.contains("jsx-block-line-host"))).toBe(false);
213
-
214
- const introLine = lines.find((line) => line.textContent?.includes("Intro text"));
215
- const trailingLine = lines.find((line) => line.textContent?.includes("trailing text"));
216
- expect(introLine).toBeTruthy();
217
- expect(trailingLine).toBeTruthy();
218
- expect(introLine?.style.display).not.toBe("flex");
219
- expect(trailingLine?.style.display).not.toBe("flex");
220
- });
221
-
222
- it("renders block HTML embedded in a mixed paragraph on a dedicated row", async () => {
223
- const doc = "hello world `snippet` <div>test</div> foo bar";
224
- await mountPreviewEditor(doc, blockComponents);
225
-
226
- const blockWidgets = document.querySelectorAll<HTMLElement>(
227
- '[data-md-widget="JsxBlockWidget"]',
228
- );
229
- expect(blockWidgets.length).toBe(1);
230
- const divWidget = blockWidgets[0];
231
- expect(divWidget?.textContent).toContain("test");
232
- expect(divWidget && blockWidgetHostLine(divWidget)).toBeNull();
233
-
234
- const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
235
- const helloLine = lines.find((line) => line.textContent?.includes("hello world"));
236
- const fooLine = lines.find((line) => line.textContent?.includes("foo bar"));
237
- expect(helloLine).toBeTruthy();
238
- expect(fooLine).toBeTruthy();
239
- expect(helloLine?.classList.contains("jsx-block-line-host")).toBe(false);
240
- expect(fooLine?.classList.contains("jsx-block-line-host")).toBe(false);
241
- });
146
+ it("renders block components via JsxBlockWidget with an edit-source action", async () => {
147
+ await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
148
+
149
+ const blockWidgets = document.querySelectorAll<HTMLElement>(
150
+ '[data-md-widget="JsxBlockWidget"]',
151
+ );
152
+ expect(blockWidgets.length).toBe(3);
153
+ expect(document.querySelectorAll('[data-md-widget="JsxInlineWidget"]').length).toBe(0);
154
+
155
+ const editButtons = document.querySelectorAll<HTMLButtonElement>(
156
+ '[aria-label="Edit source"]',
157
+ );
158
+ expect(editButtons.length).toBeGreaterThanOrEqual(3);
159
+ });
160
+
161
+ it("renders block widgets between cm-lines, not inside a cm-line", async () => {
162
+ await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
163
+
164
+ expect(document.querySelectorAll(".jsx-block-line-host").length).toBe(0);
165
+
166
+ for (const widget of Array.from(
167
+ document.querySelectorAll<HTMLElement>('[data-md-widget="JsxBlockWidget"]'),
168
+ )) {
169
+ expect(blockWidgetHostLine(widget)).toBeNull();
170
+ const lineSibling = widget.closest(".cm-line");
171
+ expect(lineSibling).toBeNull();
172
+ }
173
+ });
174
+
175
+ it("block widgets do not get cm-widgetBuffer spacer images", async () => {
176
+ await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
177
+
178
+ for (const widget of Array.from(
179
+ document.querySelectorAll<HTMLElement>('[data-md-widget="JsxBlockWidget"]'),
180
+ )) {
181
+ const parent = widget.parentElement;
182
+ expect(parent?.querySelector("img.cm-widgetBuffer")).toBeNull();
183
+ }
184
+ });
185
+
186
+ it("clicking the edit-source action selects the component source range", async () => {
187
+ const view = await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
188
+
189
+ const editButton = document.querySelector<HTMLButtonElement>('[aria-label="Edit source"]');
190
+ expect(editButton).toBeTruthy();
191
+ editButton?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
192
+ await nextFrame();
193
+ await nextFrame();
194
+
195
+ const selection = view.state.selection.main;
196
+ const selectedSource = view.state.sliceDoc(selection.from, selection.to);
197
+ expect(selectedSource.trim()).toBe('<Card label="Alpha" />');
198
+ });
199
+
200
+ it("renders block components embedded in a mixed paragraph on a dedicated row", async () => {
201
+ const doc = 'Intro text <Card label="Mid" /> trailing text.';
202
+ await mountPreviewEditor(doc, blockComponents);
203
+
204
+ const blockWidgets = document.querySelectorAll<HTMLElement>(
205
+ '[data-md-widget="JsxBlockWidget"]',
206
+ );
207
+ expect(blockWidgets.length).toBe(1);
208
+ const midWidget = blockWidgets[0];
209
+ expect(midWidget?.textContent).toContain("Mid");
210
+ expect(midWidget && blockWidgetHostLine(midWidget)).toBeNull();
211
+
212
+ const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
213
+ expect(lines.some((line) => line.classList.contains("jsx-block-line-host"))).toBe(false);
214
+
215
+ const introLine = lines.find((line) => line.textContent?.includes("Intro text"));
216
+ const trailingLine = lines.find((line) => line.textContent?.includes("trailing text"));
217
+ expect(introLine).toBeTruthy();
218
+ expect(trailingLine).toBeTruthy();
219
+ expect(introLine?.style.display).not.toBe("flex");
220
+ expect(trailingLine?.style.display).not.toBe("flex");
221
+ });
222
+
223
+ it("renders block HTML embedded in a mixed paragraph on a dedicated row", async () => {
224
+ const doc = "hello world `snippet` <div>test</div> foo bar";
225
+ await mountPreviewEditor(doc, blockComponents);
226
+
227
+ const blockWidgets = document.querySelectorAll<HTMLElement>(
228
+ '[data-md-widget="JsxBlockWidget"]',
229
+ );
230
+ expect(blockWidgets.length).toBe(1);
231
+ const divWidget = blockWidgets[0];
232
+ expect(divWidget?.textContent).toContain("test");
233
+ expect(divWidget && blockWidgetHostLine(divWidget)).toBeNull();
234
+
235
+ const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
236
+ const helloLine = lines.find((line) => line.textContent?.includes("hello world"));
237
+ const fooLine = lines.find((line) => line.textContent?.includes("foo bar"));
238
+ expect(helloLine).toBeTruthy();
239
+ expect(fooLine).toBeTruthy();
240
+ expect(helloLine?.classList.contains("jsx-block-line-host")).toBe(false);
241
+ expect(fooLine?.classList.contains("jsx-block-line-host")).toBe(false);
242
+ });
242
243
  });
@@ -2,6 +2,7 @@ import { MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
2
2
  import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
3
3
  import { MANTINE_THEME } from "@src/theme/mantine";
4
4
  import { describe, expect, it } from "vitest";
5
+
5
6
  import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
6
7
 
7
8
  const TAG = '<Since v={"2.0.0"} t="true" b="false" />';
@@ -12,57 +13,57 @@ const STRING_COLOR = "rgb(0, 255, 110)";
12
13
  const PROPERTY_COLOR = "rgb(224, 108, 117)";
13
14
 
14
15
  describe("MarkdownEditor / JSX attribute highlighting", () => {
15
- it("does not let the nested HTML parser mis-highlight attributes after an expression", async () => {
16
- mountStory(
17
- <MantineProvider
18
- theme={MANTINE_THEME}
19
- cssVariablesResolver={v8CssVariablesResolver}
20
- forceColorScheme="dark"
21
- >
22
- <Paper
23
- p="md"
24
- withBorder
25
- >
26
- <MarkdownEditor
27
- mode="source"
28
- document={TAG}
29
- autoFocus={false}
30
- mih={120}
31
- />
32
- </Paper>
33
- </MantineProvider>,
34
- );
35
- await getEditorView();
36
- await nextFrame();
16
+ it("does not let the nested HTML parser mis-highlight attributes after an expression", async () => {
17
+ mountStory(
18
+ <MantineProvider
19
+ theme={MANTINE_THEME}
20
+ cssVariablesResolver={v8CssVariablesResolver}
21
+ forceColorScheme="dark"
22
+ >
23
+ <Paper
24
+ p="md"
25
+ withBorder
26
+ >
27
+ <MarkdownEditor
28
+ mode="source"
29
+ document={TAG}
30
+ autoFocus={false}
31
+ mih={120}
32
+ />
33
+ </Paper>
34
+ </MantineProvider>,
35
+ );
36
+ await getEditorView();
37
+ await nextFrame();
37
38
 
38
- const colors = readLineTokenColors();
39
- expect(colors.v).toBe(PROPERTY_COLOR);
40
- expect(colors.t).toBe(PROPERTY_COLOR);
41
- expect(colors.b).toBe(PROPERTY_COLOR);
42
- expect(colors.trueQuoted).toBe(STRING_COLOR);
43
- expect(colors.falseQuoted).toBe(STRING_COLOR);
44
- expect(colors.exprLiteral).toBe(STRING_COLOR);
45
- });
39
+ const colors = readLineTokenColors();
40
+ expect(colors.v).toBe(PROPERTY_COLOR);
41
+ expect(colors.t).toBe(PROPERTY_COLOR);
42
+ expect(colors.b).toBe(PROPERTY_COLOR);
43
+ expect(colors.trueQuoted).toBe(STRING_COLOR);
44
+ expect(colors.falseQuoted).toBe(STRING_COLOR);
45
+ expect(colors.exprLiteral).toBe(STRING_COLOR);
46
+ });
46
47
  });
47
48
 
48
49
  function readLineTokenColors(): Record<string, string> {
49
- const line = document.querySelector(".cm-line");
50
- expect(line).not.toBeNull();
51
- if (!line) return {};
50
+ const line = document.querySelector(".cm-line");
51
+ expect(line).not.toBeNull();
52
+ if (!line) return {};
52
53
 
53
- const out: Record<string, string> = {};
54
- for (const span of Array.from(line.querySelectorAll("span"))) {
55
- const text = span.textContent ?? "";
56
- if (!text) continue;
57
- const color = getComputedStyle(span).color;
54
+ const out: Record<string, string> = {};
55
+ for (const span of Array.from(line.querySelectorAll("span"))) {
56
+ const text = span.textContent ?? "";
57
+ if (!text) continue;
58
+ const color = getComputedStyle(span).color;
58
59
 
59
- if (text === "v") out.v = color;
60
- if (text === "t") out.t = color;
61
- if (text === "b") out.b = color;
62
- if (text === '"2.0.0"') out.exprLiteral = color;
63
- if (text === '"true"') out.trueQuoted = color;
64
- if (text === '"false"') out.falseQuoted = color;
65
- }
60
+ if (text === "v") out.v = color;
61
+ if (text === "t") out.t = color;
62
+ if (text === "b") out.b = color;
63
+ if (text === '"2.0.0"') out.exprLiteral = color;
64
+ if (text === '"true"') out.trueQuoted = color;
65
+ if (text === '"false"') out.falseQuoted = color;
66
+ }
66
67
 
67
- return out;
68
+ return out;
68
69
  }
@@ -2,6 +2,7 @@ import { MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
2
2
  import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
3
3
  import { MANTINE_THEME } from "@src/theme/mantine";
4
4
  import { describe, expect, it } from "vitest";
5
+
5
6
  import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
6
7
 
7
8
  const JSX_BADGES_DOC = `### Since
@@ -22,38 +23,38 @@ const JSX_BADGES_DOC = `### Since
22
23
  `;
23
24
 
24
25
  describe("MarkdownEditor / inline JSX badges", () => {
25
- it("renders all Since and Label components inline like the viewer", async () => {
26
- mountStory(
27
- <MantineProvider
28
- theme={MANTINE_THEME}
29
- cssVariablesResolver={v8CssVariablesResolver}
30
- forceColorScheme="dark"
31
- >
32
- <Paper
33
- p="md"
34
- withBorder
35
- >
36
- <MarkdownEditor
37
- mode="preview"
38
- document={JSX_BADGES_DOC}
39
- autoFocus={false}
40
- jsxMode="render"
41
- />
42
- </Paper>
43
- </MantineProvider>,
44
- );
45
-
46
- const view = await getEditorView();
47
- view.contentDOM.blur();
48
- await nextFrame();
49
-
50
- const badges = document.querySelectorAll(".mantine-Badge-label");
51
- expect(badges.length).toBe(7);
52
- expect(document.body.textContent).toContain("1.5.0");
53
- expect(document.body.textContent).toContain("Required");
54
- expect(document.body.textContent).toContain("Optional");
55
- expect(document.body.textContent).toContain("Deprecated");
56
- expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Since");
57
- expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Label");
58
- });
26
+ it("renders all Since and Label components inline like the viewer", async () => {
27
+ mountStory(
28
+ <MantineProvider
29
+ theme={MANTINE_THEME}
30
+ cssVariablesResolver={v8CssVariablesResolver}
31
+ forceColorScheme="dark"
32
+ >
33
+ <Paper
34
+ p="md"
35
+ withBorder
36
+ >
37
+ <MarkdownEditor
38
+ mode="preview"
39
+ document={JSX_BADGES_DOC}
40
+ autoFocus={false}
41
+ jsxMode="render"
42
+ />
43
+ </Paper>
44
+ </MantineProvider>,
45
+ );
46
+
47
+ const view = await getEditorView();
48
+ view.contentDOM.blur();
49
+ await nextFrame();
50
+
51
+ const badges = document.querySelectorAll(".mantine-Badge-label");
52
+ expect(badges.length).toBe(7);
53
+ expect(document.body.textContent).toContain("1.5.0");
54
+ expect(document.body.textContent).toContain("Required");
55
+ expect(document.body.textContent).toContain("Optional");
56
+ expect(document.body.textContent).toContain("Deprecated");
57
+ expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Since");
58
+ expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Label");
59
+ });
59
60
  });