@surrealdb/ui 1.2.12 → 1.2.13

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 (93) 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 +21 -19
  6. package/README.md +15 -10
  7. package/dist/ui.css +1 -1
  8. package/dist/ui.d.ts +234 -1
  9. package/dist/ui.js +8217 -7329
  10. package/dist/ui.js.map +1 -1
  11. package/doc/code-style.md +264 -0
  12. package/doc/stacker-plan.md +219 -0
  13. package/package.json +119 -116
  14. package/res/_mixins.scss +15 -11
  15. package/tests/_setup/e2e-helpers.tsx +85 -85
  16. package/tests/_setup/portable-stories.ts +11 -10
  17. package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +140 -139
  18. package/tests/e2e/MarkdownEditor/edits.test.tsx +101 -100
  19. package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +35 -34
  20. package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +178 -177
  21. package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +50 -49
  22. package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +195 -194
  23. package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +47 -46
  24. package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +35 -34
  25. package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +39 -38
  26. package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +28 -27
  27. package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +49 -48
  28. package/tests/e2e/MarkdownEditor/media-align.test.tsx +40 -39
  29. package/tests/e2e/MarkdownEditor/media-edit.test.tsx +49 -48
  30. package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +108 -107
  31. package/tests/e2e/MarkdownEditor/modes.test.tsx +79 -78
  32. package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +29 -28
  33. package/tests/e2e/MarkdownEditor/regressions.test.tsx +194 -193
  34. package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +81 -80
  35. package/tests/e2e/MarkdownEditor/table-click.test.tsx +36 -35
  36. package/tests/e2e/MarkdownEditor/table-controls.test.tsx +44 -43
  37. package/tests/e2e/MarkdownEditor/table-format.test.tsx +28 -27
  38. package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +22 -21
  39. package/tests/e2e/MarkdownViewer/parity.test.tsx +144 -143
  40. package/tests/e2e/MarkdownViewer/render.test.tsx +20 -19
  41. package/tests/e2e/Stacker/stacker.test.tsx +304 -0
  42. package/tests/unit/Editor/helpers.test.ts +26 -26
  43. package/tests/unit/MarkdownConformance/error-isolation.test.tsx +26 -25
  44. package/tests/unit/MarkdownConformance/indentation.test.tsx +20 -19
  45. package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +255 -0
  46. package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +39 -38
  47. package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +122 -122
  48. package/tests/unit/MarkdownConformance/links.test.tsx +78 -54
  49. package/tests/unit/MarkdownConformance/render-helpers.tsx +60 -60
  50. package/tests/unit/MarkdownEditor/code-info.test.ts +58 -58
  51. package/tests/unit/MarkdownEditor/decorations.test.ts +423 -421
  52. package/tests/unit/MarkdownEditor/editor-ready.test.ts +22 -22
  53. package/tests/unit/MarkdownEditor/html-descriptors.test.ts +70 -70
  54. package/tests/unit/MarkdownEditor/indented-fence.test.ts +80 -80
  55. package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +87 -87
  56. package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +51 -51
  57. package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +63 -63
  58. package/tests/unit/MarkdownEditor/list-indent.test.ts +67 -67
  59. package/tests/unit/MarkdownEditor/slash-commands.test.ts +150 -150
  60. package/tests/unit/MarkdownEditor/table-format.test.ts +67 -67
  61. package/tests/unit/MarkdownEditor/table.test.ts +96 -96
  62. package/tests/unit/MarkdownEditor/triggers.test.ts +167 -167
  63. package/tests/unit/MarkdownEditor/widget-store.test.ts +90 -90
  64. package/tests/unit/MarkdownViewer/callout.test.tsx +42 -42
  65. package/tests/unit/MarkdownViewer/code-highlight.test.tsx +41 -41
  66. package/tests/unit/MarkdownViewer/code-title.test.tsx +45 -45
  67. package/tests/unit/MarkdownViewer/features.test.tsx +129 -129
  68. package/tests/unit/MarkdownViewer/headings.test.tsx +28 -28
  69. package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +53 -53
  70. package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +137 -137
  71. package/tests/unit/MarkdownViewer/jsx.test.tsx +188 -188
  72. package/tests/unit/MarkdownViewer/list-bullets.test.tsx +31 -31
  73. package/tests/unit/MarkdownViewer/list-code.test.tsx +45 -45
  74. package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +109 -109
  75. package/tests/unit/MarkdownViewer/read-time.test.ts +13 -13
  76. package/tests/unit/MarkdownViewer/renderers.test.tsx +75 -75
  77. package/tests/unit/MarkdownViewer/runnable.test.tsx +51 -51
  78. package/tests/unit/MarkdownViewer/ssr.test.tsx +56 -55
  79. package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +43 -42
  80. package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +120 -120
  81. package/tests/unit/MarkdownViewer/tabs.test.tsx +83 -82
  82. package/tests/unit/Stacker/drag.test.ts +21 -0
  83. package/tests/unit/Stacker/helpers.test.ts +105 -0
  84. package/tests/unit/Stacker/reducer.test.ts +107 -0
  85. package/tools/duplicate-vector-mappings.json +141 -141
  86. package/tools/glass-export-overrides.json +6 -6
  87. package/tools/icon-parser.ts +41 -40
  88. package/tools/integration-gradient-exports.json +55 -55
  89. package/tools/picto-compare.ts +32 -32
  90. package/tools/picto-merge-urls.ts +19 -19
  91. package/tools/picto-sync.ts +543 -543
  92. package/tools/picto-verify-gradients.ts +50 -50
  93. package/tools/vector-export-nodes.json +975 -975
@@ -1,71 +1,95 @@
1
1
  import { describe, expect, it } from "vitest";
2
+
2
3
  import { renderMarkdown, visibleText } from "./render-helpers";
3
4
 
4
5
  describe("Conformance / autolinks", () => {
5
- it("keeps an absolute URL href unchanged", () => {
6
- const html = renderMarkdown("Go https://example.com now");
7
- expect(html).toContain('href="https://example.com"');
8
- });
6
+ it("keeps an absolute URL href unchanged", () => {
7
+ const html = renderMarkdown("Go https://example.com now");
8
+ expect(html).toContain('href="https://example.com"');
9
+ });
10
+
11
+ it("adds https:// to a www. autolink", () => {
12
+ const html = renderMarkdown("Visit www.example.com today");
13
+ expect(html).toContain('href="https://www.example.com"');
14
+ });
9
15
 
10
- it("adds https:// to a www. autolink", () => {
11
- const html = renderMarkdown("Visit www.example.com today");
12
- expect(html).toContain('href="https://www.example.com"');
13
- });
16
+ it("adds mailto: to a bare email autolink", () => {
17
+ const html = renderMarkdown("Mail me@example.com please");
18
+ expect(html).toContain('href="mailto:me@example.com"');
19
+ });
14
20
 
15
- it("adds mailto: to a bare email autolink", () => {
16
- const html = renderMarkdown("Mail me@example.com please");
17
- expect(html).toContain('href="mailto:me@example.com"');
18
- });
21
+ it("renders an angle-bracket URL autolink as an anchor", () => {
22
+ const html = renderMarkdown("See <https://example.com> here");
23
+ expect(html).toContain('href="https://example.com"');
24
+ expect(visibleText(html)).toContain("https://example.com");
25
+ // The angle brackets are delimiters, not visible content.
26
+ expect(visibleText(html)).not.toContain("<https://example.com>");
27
+ });
19
28
 
20
- it("renders an angle-bracket URL autolink as an anchor", () => {
21
- const html = renderMarkdown("See <https://example.com> here");
22
- expect(html).toContain('href="https://example.com"');
23
- expect(visibleText(html)).toContain("https://example.com");
24
- // The angle brackets are delimiters, not visible content.
25
- expect(visibleText(html)).not.toContain("<https://example.com>");
26
- });
29
+ it("renders an angle-bracket email autolink with mailto:", () => {
30
+ const html = renderMarkdown("Mail <me@example.com> ok");
31
+ expect(html).toContain('href="mailto:me@example.com"');
32
+ });
33
+
34
+ it("does not linkify a URL inside inline code", () => {
35
+ const html = renderMarkdown("Use `https://example.com` verbatim");
36
+ expect(html).not.toContain('href="https://example.com"');
37
+ expect(html).toContain("https://example.com");
38
+ });
39
+ });
27
40
 
28
- it("renders an angle-bracket email autolink with mailto:", () => {
29
- const html = renderMarkdown("Mail <me@example.com> ok");
30
- expect(html).toContain('href="mailto:me@example.com"');
31
- });
41
+ describe("Conformance / angle brackets in inline code", () => {
42
+ // Regression: `<…>` inside inline code was scanned as an HTML/JSX tag,
43
+ // which pushed the whole paragraph down the descriptor path and rendered
44
+ // every backtick literally while dropping the bracketed text.
45
+ it("keeps inline code intact when it contains an angle-bracket placeholder", () => {
46
+ const html = renderMarkdown(
47
+ "`.{..+collect}` returns the set; and `.{..+shortest=<target>}` returns the shortest path.",
48
+ );
49
+ expect(html).toContain("<code");
50
+ expect(html).toContain(".{..+collect}");
51
+ expect(html).toContain(".{..+shortest=&lt;target&gt;}");
52
+ // Backticks must not leak into the visible output.
53
+ expect(visibleText(html)).not.toContain("`");
54
+ expect(visibleText(html)).toContain(".{..+shortest=<target>}");
55
+ });
32
56
 
33
- it("does not linkify a URL inside inline code", () => {
34
- const html = renderMarkdown("Use `https://example.com` verbatim");
35
- expect(html).not.toContain('href="https://example.com"');
36
- expect(html).toContain("https://example.com");
37
- });
57
+ it("still pairs a real inline tag that sits beside inline code", () => {
58
+ const html = renderMarkdown("Press `Enter` then <strong>go</strong> now");
59
+ expect(html).toContain("<code");
60
+ expect(html).toContain("<strong>go</strong>");
61
+ });
38
62
  });
39
63
 
40
64
  describe("Conformance / reference-style links", () => {
41
- it("resolves a full reference link [text][id]", () => {
42
- const html = renderMarkdown("[click here][site] now\n\n[site]: https://example.com");
43
- expect(html).toContain('href="https://example.com"');
44
- expect(visibleText(html)).toContain("click here");
45
- });
65
+ it("resolves a full reference link [text][id]", () => {
66
+ const html = renderMarkdown("[click here][site] now\n\n[site]: https://example.com");
67
+ expect(html).toContain('href="https://example.com"');
68
+ expect(visibleText(html)).toContain("click here");
69
+ });
46
70
 
47
- it("resolves a collapsed reference link [id][]", () => {
48
- const html = renderMarkdown("[site][] now\n\n[site]: https://example.com");
49
- expect(html).toContain('href="https://example.com"');
50
- expect(visibleText(html)).toContain("site");
51
- });
71
+ it("resolves a collapsed reference link [id][]", () => {
72
+ const html = renderMarkdown("[site][] now\n\n[site]: https://example.com");
73
+ expect(html).toContain('href="https://example.com"');
74
+ expect(visibleText(html)).toContain("site");
75
+ });
52
76
 
53
- it("resolves a shortcut reference link [id]", () => {
54
- const html = renderMarkdown("[site] now\n\n[site]: https://example.com");
55
- expect(html).toContain('href="https://example.com"');
56
- });
77
+ it("resolves a shortcut reference link [id]", () => {
78
+ const html = renderMarkdown("[site] now\n\n[site]: https://example.com");
79
+ expect(html).toContain('href="https://example.com"');
80
+ });
57
81
 
58
- it("matches reference labels case-insensitively", () => {
59
- const html = renderMarkdown("[Click][SITE] now\n\n[site]: https://example.com");
60
- expect(html).toContain('href="https://example.com"');
61
- });
82
+ it("matches reference labels case-insensitively", () => {
83
+ const html = renderMarkdown("[Click][SITE] now\n\n[site]: https://example.com");
84
+ expect(html).toContain('href="https://example.com"');
85
+ });
62
86
 
63
- it("emits the visible text (no anchor) when the reference is undefined", () => {
64
- const html = renderMarkdown("see [missing][nope] end");
65
- expect(html).not.toContain("href=");
66
- const text = visibleText(html);
67
- expect(text).toContain("missing");
68
- // Reference brackets/label are not shown as literal text.
69
- expect(text).not.toContain("[missing]");
70
- });
87
+ it("emits the visible text (no anchor) when the reference is undefined", () => {
88
+ const html = renderMarkdown("see [missing][nope] end");
89
+ expect(html).not.toContain("href=");
90
+ const text = visibleText(html);
91
+ expect(text).toContain("missing");
92
+ // Reference brackets/label are not shown as literal text.
93
+ expect(text).not.toContain("[missing]");
94
+ });
71
95
  });
@@ -8,35 +8,35 @@ import { renderToString } from "react-dom/server";
8
8
 
9
9
  // jsdom lacks matchMedia, which Mantine touches during render.
10
10
  if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
11
- Object.defineProperty(window, "matchMedia", {
12
- writable: true,
13
- value: (query: string) => ({
14
- matches: false,
15
- media: query,
16
- onchange: null,
17
- addListener: () => {},
18
- removeListener: () => {},
19
- addEventListener: () => {},
20
- removeEventListener: () => {},
21
- dispatchEvent: () => false,
22
- }),
23
- });
11
+ Object.defineProperty(window, "matchMedia", {
12
+ writable: true,
13
+ value: (query: string) => ({
14
+ matches: false,
15
+ media: query,
16
+ onchange: null,
17
+ addListener: () => {},
18
+ removeListener: () => {},
19
+ addEventListener: () => {},
20
+ removeEventListener: () => {},
21
+ dispatchEvent: () => false,
22
+ }),
23
+ });
24
24
  }
25
25
 
26
26
  /** Render a `MarkdownViewer` to an HTML string inside a Mantine provider. */
27
27
  export function renderMarkdown(content: string, props?: Partial<MarkdownViewerProps>): string {
28
- return renderToString(
29
- <MantineProvider
30
- theme={MANTINE_THEME}
31
- cssVariablesResolver={v8CssVariablesResolver}
32
- forceColorScheme="dark"
33
- >
34
- <MarkdownViewer
35
- content={content}
36
- {...props}
37
- />
38
- </MantineProvider>,
39
- );
28
+ return renderToString(
29
+ <MantineProvider
30
+ theme={MANTINE_THEME}
31
+ cssVariablesResolver={v8CssVariablesResolver}
32
+ forceColorScheme="dark"
33
+ >
34
+ <MarkdownViewer
35
+ content={content}
36
+ {...props}
37
+ />
38
+ </MantineProvider>,
39
+ );
40
40
  }
41
41
 
42
42
  /**
@@ -45,44 +45,44 @@ export function renderMarkdown(content: string, props?: Partial<MarkdownViewerPr
45
45
  * client (the environment where a throwing embedded component would blank the UI).
46
46
  */
47
47
  export function renderMarkdownClient(
48
- content: string,
49
- props?: Partial<MarkdownViewerProps>,
48
+ content: string,
49
+ props?: Partial<MarkdownViewerProps>,
50
50
  ): string {
51
- const container = document.createElement("div");
52
- document.body.appendChild(container);
53
- const root = createRoot(container);
54
- act(() => {
55
- root.render(
56
- <MantineProvider
57
- theme={MANTINE_THEME}
58
- cssVariablesResolver={v8CssVariablesResolver}
59
- forceColorScheme="dark"
60
- >
61
- <MarkdownViewer
62
- content={content}
63
- {...props}
64
- />
65
- </MantineProvider>,
66
- );
67
- });
68
- const html = container.innerHTML;
69
- act(() => {
70
- root.unmount();
71
- });
72
- container.remove();
73
- return html;
51
+ const container = document.createElement("div");
52
+ document.body.appendChild(container);
53
+ const root = createRoot(container);
54
+ act(() => {
55
+ root.render(
56
+ <MantineProvider
57
+ theme={MANTINE_THEME}
58
+ cssVariablesResolver={v8CssVariablesResolver}
59
+ forceColorScheme="dark"
60
+ >
61
+ <MarkdownViewer
62
+ content={content}
63
+ {...props}
64
+ />
65
+ </MantineProvider>,
66
+ );
67
+ });
68
+ const html = container.innerHTML;
69
+ act(() => {
70
+ root.unmount();
71
+ });
72
+ container.remove();
73
+ return html;
74
74
  }
75
75
 
76
76
  /** Strip all HTML tags and decode common entities, returning the visible text. */
77
77
  export function visibleText(html: string): string {
78
- return html
79
- .replace(/<style[^>]*>[\s\S]*?<\/style>/g, "")
80
- .replace(/<[^>]+>/g, "")
81
- .replace(/&quot;/g, '"')
82
- .replace(/&#39;/g, "'")
83
- .replace(/&lt;/g, "<")
84
- .replace(/&gt;/g, ">")
85
- .replace(/&amp;/g, "&")
86
- .replace(/\s+/g, " ")
87
- .trim();
78
+ return html
79
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/g, "")
80
+ .replace(/<[^>]+>/g, "")
81
+ .replace(/&quot;/g, '"')
82
+ .replace(/&#39;/g, "'")
83
+ .replace(/&lt;/g, "<")
84
+ .replace(/&gt;/g, ">")
85
+ .replace(/&amp;/g, "&")
86
+ .replace(/\s+/g, " ")
87
+ .trim();
88
88
  }
@@ -2,62 +2,62 @@ import { codeInfoString, parseCodeInfo } from "@src/lib/markdown/tree/code-info"
2
2
  import { describe, expect, it } from "vitest";
3
3
 
4
4
  describe("parseCodeInfo", () => {
5
- it("returns an empty language and attributes for a blank info string", () => {
6
- expect(parseCodeInfo("")).toEqual({ language: "", attributes: {} });
7
- expect(parseCodeInfo(" ")).toEqual({ language: "", attributes: {} });
8
- });
9
-
10
- it("reads the language as the first token, lower-cased", () => {
11
- expect(parseCodeInfo("TypeScript")).toEqual({
12
- language: "typescript",
13
- attributes: {},
14
- });
15
- });
16
-
17
- it("resolves loose flags to true", () => {
18
- const parsed = parseCodeInfo("surql runnable");
19
- expect(parsed.language).toBe("surql");
20
- expect(parsed.attributes.runnable).toBe(true);
21
- });
22
-
23
- it("parses double- and single-quoted values", () => {
24
- const parsed = parseCodeInfo(`js title="My Example" label='other'`);
25
- expect(parsed.attributes.title).toBe("My Example");
26
- expect(parsed.attributes.label).toBe("other");
27
- });
28
-
29
- it("keeps an empty string value for empty quotes", () => {
30
- const parsed = parseCodeInfo(`js title=""`);
31
- expect(parsed.attributes.title).toBe("");
32
- });
33
-
34
- it("parses bare unquoted values", () => {
35
- const parsed = parseCodeInfo("ts height=200");
36
- expect(parsed.attributes.height).toBe("200");
37
- });
38
-
39
- it("parses braced expression values", () => {
40
- const parsed = parseCodeInfo("surql runnable={SELECT 1}");
41
- expect(parsed.attributes.runnable).toBe("{SELECT 1}");
42
- });
43
-
44
- it("mixes loose flags and key/value pairs", () => {
45
- const parsed = parseCodeInfo(`surql runnable title="Demo"`);
46
- expect(parsed).toEqual({
47
- language: "surql",
48
- attributes: { runnable: true, title: "Demo" },
49
- });
50
- });
51
-
52
- it("preserves attribute value casing", () => {
53
- const parsed = parseCodeInfo(`surql runnable="SELECT 1"`);
54
- expect(parsed.attributes.runnable).toBe("SELECT 1");
55
- });
56
-
57
- it("codeInfoString returns string values and undefined for flags", () => {
58
- const parsed = parseCodeInfo(`surql runnable title="Demo"`);
59
- expect(codeInfoString(parsed, "title")).toBe("Demo");
60
- expect(codeInfoString(parsed, "runnable")).toBeUndefined();
61
- expect(codeInfoString(parsed, "missing")).toBeUndefined();
62
- });
5
+ it("returns an empty language and attributes for a blank info string", () => {
6
+ expect(parseCodeInfo("")).toEqual({ language: "", attributes: {} });
7
+ expect(parseCodeInfo(" ")).toEqual({ language: "", attributes: {} });
8
+ });
9
+
10
+ it("reads the language as the first token, lower-cased", () => {
11
+ expect(parseCodeInfo("TypeScript")).toEqual({
12
+ language: "typescript",
13
+ attributes: {},
14
+ });
15
+ });
16
+
17
+ it("resolves loose flags to true", () => {
18
+ const parsed = parseCodeInfo("surql runnable");
19
+ expect(parsed.language).toBe("surql");
20
+ expect(parsed.attributes.runnable).toBe(true);
21
+ });
22
+
23
+ it("parses double- and single-quoted values", () => {
24
+ const parsed = parseCodeInfo(`js title="My Example" label='other'`);
25
+ expect(parsed.attributes.title).toBe("My Example");
26
+ expect(parsed.attributes.label).toBe("other");
27
+ });
28
+
29
+ it("keeps an empty string value for empty quotes", () => {
30
+ const parsed = parseCodeInfo(`js title=""`);
31
+ expect(parsed.attributes.title).toBe("");
32
+ });
33
+
34
+ it("parses bare unquoted values", () => {
35
+ const parsed = parseCodeInfo("ts height=200");
36
+ expect(parsed.attributes.height).toBe("200");
37
+ });
38
+
39
+ it("parses braced expression values", () => {
40
+ const parsed = parseCodeInfo("surql runnable={SELECT 1}");
41
+ expect(parsed.attributes.runnable).toBe("{SELECT 1}");
42
+ });
43
+
44
+ it("mixes loose flags and key/value pairs", () => {
45
+ const parsed = parseCodeInfo(`surql runnable title="Demo"`);
46
+ expect(parsed).toEqual({
47
+ language: "surql",
48
+ attributes: { runnable: true, title: "Demo" },
49
+ });
50
+ });
51
+
52
+ it("preserves attribute value casing", () => {
53
+ const parsed = parseCodeInfo(`surql runnable="SELECT 1"`);
54
+ expect(parsed.attributes.runnable).toBe("SELECT 1");
55
+ });
56
+
57
+ it("codeInfoString returns string values and undefined for flags", () => {
58
+ const parsed = parseCodeInfo(`surql runnable title="Demo"`);
59
+ expect(codeInfoString(parsed, "title")).toBe("Demo");
60
+ expect(codeInfoString(parsed, "runnable")).toBeUndefined();
61
+ expect(codeInfoString(parsed, "missing")).toBeUndefined();
62
+ });
63
63
  });