@usetheo/ui 0.1.0-next.0 → 0.1.0-next.1

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 (135) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +116 -9
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/plugin-Atb0VKtr.d.ts +172 -0
  6. package/dist/slide/index.d.ts +212 -0
  7. package/dist/slide/index.js +714 -0
  8. package/dist/slide/index.js.map +1 -0
  9. package/dist/slide/plugins/emoji/index.d.ts +29 -0
  10. package/dist/slide/plugins/emoji/index.js +157 -0
  11. package/dist/slide/plugins/emoji/index.js.map +1 -0
  12. package/dist/slide/plugins/math/index.d.ts +13 -0
  13. package/dist/slide/plugins/math/index.js +145 -0
  14. package/dist/slide/plugins/math/index.js.map +1 -0
  15. package/dist/slide/plugins/mermaid/index.d.ts +55 -0
  16. package/dist/slide/plugins/mermaid/index.js +218 -0
  17. package/dist/slide/plugins/mermaid/index.js.map +1 -0
  18. package/dist/slide/plugins/shiki/index.d.ts +18 -0
  19. package/dist/slide/plugins/shiki/index.js +87 -0
  20. package/dist/slide/plugins/shiki/index.js.map +1 -0
  21. package/dist/slide/themes/default.css +256 -0
  22. package/dist/slide/themes/layouts.css +143 -0
  23. package/dist/slide/themes/violet-forge.css +256 -0
  24. package/dist/slide-deck/index.css +52 -0
  25. package/dist/slide-deck/index.css.map +1 -0
  26. package/dist/slide-deck/index.d.ts +377 -0
  27. package/dist/slide-deck/index.js +1797 -0
  28. package/dist/slide-deck/index.js.map +1 -0
  29. package/dist/whiteboard/index.d.ts +258 -0
  30. package/dist/whiteboard/index.js +738 -0
  31. package/dist/whiteboard/index.js.map +1 -0
  32. package/package.json +141 -9
  33. package/registry/r/agent-composer.json +4 -4
  34. package/registry/r/agent-editor.json +9 -9
  35. package/registry/r/agent-error-card.json +2 -2
  36. package/registry/r/agent-event.json +4 -4
  37. package/registry/r/agent-handoff.json +2 -2
  38. package/registry/r/agent-profile.json +2 -2
  39. package/registry/r/agent-starting-state.json +2 -2
  40. package/registry/r/agent-stream.json +9 -9
  41. package/registry/r/agent-streaming.json +2 -2
  42. package/registry/r/agent-timeline.json +4 -4
  43. package/registry/r/approval-card.json +4 -4
  44. package/registry/r/artifact-preview.json +2 -2
  45. package/registry/r/attachment-chip.json +4 -4
  46. package/registry/r/audit-log-entry.json +3 -3
  47. package/registry/r/auto-compact-notice.json +2 -2
  48. package/registry/r/avatar.json +2 -2
  49. package/registry/r/badge.json +2 -2
  50. package/registry/r/browser-controls.json +2 -2
  51. package/registry/r/build-log-stream.json +2 -2
  52. package/registry/r/button.json +2 -2
  53. package/registry/r/capability-indicator.json +3 -3
  54. package/registry/r/card.json +2 -2
  55. package/registry/r/chat-composer.json +3 -3
  56. package/registry/r/chat-message.json +3 -3
  57. package/registry/r/chat-thread.json +2 -2
  58. package/registry/r/checkbox.json +2 -2
  59. package/registry/r/command-palette.json +4 -4
  60. package/registry/r/context-card.json +3 -3
  61. package/registry/r/context-window-bar.json +2 -2
  62. package/registry/r/cost-meter.json +2 -2
  63. package/registry/r/created-files-card.json +3 -3
  64. package/registry/r/cron-job-card.json +2 -2
  65. package/registry/r/cron-jobs-list.json +3 -3
  66. package/registry/r/deployment-row.json +3 -3
  67. package/registry/r/dialog.json +2 -2
  68. package/registry/r/diff-viewer.json +2 -2
  69. package/registry/r/domain-config.json +6 -6
  70. package/registry/r/empty-state.json +3 -3
  71. package/registry/r/env-var-editor.json +5 -5
  72. package/registry/r/folder-context-card.json +3 -3
  73. package/registry/r/folder-selector.json +2 -2
  74. package/registry/r/form-field.json +2 -2
  75. package/registry/r/hook-config.json +2 -2
  76. package/registry/r/hook-event-log.json +2 -2
  77. package/registry/r/input.json +2 -2
  78. package/registry/r/intent-selector.json +3 -3
  79. package/registry/r/label.json +2 -2
  80. package/registry/r/lane-board.json +2 -2
  81. package/registry/r/login-split.json +2 -2
  82. package/registry/r/mcp-server-card.json +2 -2
  83. package/registry/r/mcp-server-list.json +3 -3
  84. package/registry/r/memory-editor.json +3 -3
  85. package/registry/r/mention-menu.json +3 -3
  86. package/registry/r/metrics-panel.json +2 -2
  87. package/registry/r/model-card.json +3 -3
  88. package/registry/r/model-selector.json +2 -2
  89. package/registry/r/permission-matrix.json +2 -2
  90. package/registry/r/permission-modal.json +4 -4
  91. package/registry/r/preview-env-card.json +5 -5
  92. package/registry/r/preview-panel.json +3 -3
  93. package/registry/r/progress-checklist.json +3 -3
  94. package/registry/r/project-card.json +5 -5
  95. package/registry/r/project-switcher.json +2 -2
  96. package/registry/r/quick-action-chips.json +3 -3
  97. package/registry/r/radio-group.json +2 -2
  98. package/registry/r/recent-folders-list.json +2 -2
  99. package/registry/r/rollback-ui.json +4 -4
  100. package/registry/r/rule-card.json +3 -3
  101. package/registry/r/rule-editor.json +10 -10
  102. package/registry/r/rule-types.json +1 -1
  103. package/registry/r/run-stats.json +2 -2
  104. package/registry/r/running-tasks-panel.json +2 -2
  105. package/registry/r/scroll-area.json +2 -2
  106. package/registry/r/select.json +2 -2
  107. package/registry/r/session-list-item.json +2 -2
  108. package/registry/r/session-timeline.json +2 -2
  109. package/registry/r/sheet.json +2 -2
  110. package/registry/r/sidebar.json +2 -2
  111. package/registry/r/skeleton.json +2 -2
  112. package/registry/r/skill-card.json +4 -4
  113. package/registry/r/skill-editor.json +10 -10
  114. package/registry/r/skills-list.json +3 -3
  115. package/registry/r/social-auth-row.json +3 -3
  116. package/registry/r/steps-rail.json +2 -2
  117. package/registry/r/sub-agent-dispatch.json +2 -2
  118. package/registry/r/switch.json +2 -2
  119. package/registry/r/system-prompt-editor.json +2 -2
  120. package/registry/r/tabs.json +2 -2
  121. package/registry/r/task-header.json +4 -4
  122. package/registry/r/task-plan.json +2 -2
  123. package/registry/r/terminal-panel.json +2 -2
  124. package/registry/r/textarea.json +2 -2
  125. package/registry/r/theme-provider.json +2 -2
  126. package/registry/r/theme-script.json +1 -1
  127. package/registry/r/theo-ui-provider.json +2 -2
  128. package/registry/r/toast.json +2 -2
  129. package/registry/r/token-usage-chart.json +2 -2
  130. package/registry/r/tool-call-card.json +3 -3
  131. package/registry/r/tool-call.json +2 -2
  132. package/registry/r/tool-result.json +2 -2
  133. package/registry/r/tools-list.json +3 -3
  134. package/registry/r/tooltip.json +2 -2
  135. package/registry/r/topnav.json +2 -2
@@ -0,0 +1,55 @@
1
+ import { FC } from 'react';
2
+ import { a as SlidePlugin } from '../../../plugin-Atb0VKtr.js';
3
+ import 'hast';
4
+ import 'mdast';
5
+ import 'zod';
6
+
7
+ /**
8
+ * Slide rich-content plugin — Mermaid diagrams.
9
+ *
10
+ * Peer-dep: `mermaid` (optional, ~370 KB raw — lazy + opt-in mandatory).
11
+ * Render is client-only because Mermaid measures DOM nodes during layout;
12
+ * SSR / static render emits a labelled placeholder + source-code fallback.
13
+ *
14
+ * Pipeline contribution (D11 of the rich-content plan):
15
+ * - `hastTransform`: detect `<pre><code class="language-mermaid">…</code></pre>`
16
+ * and replace with `<div class="theo-slide-mermaid-host" data-theo-mermaid-source="…">`.
17
+ * - `components.div`: when div has `data-theo-mermaid-source`, render the
18
+ * `<MermaidDiagram>` React component that lazy-imports `mermaid` and
19
+ * injects the SVG via `dangerouslySetInnerHTML` after `mermaid.render()`.
20
+ *
21
+ * EC-4: complete SVG tag + attribute allow-list (~30 tags) so the rendered SVG
22
+ * survives sanitize. Critical: without these, sanitize strips the SVG entirely.
23
+ *
24
+ * EC-10: SSR placeholder is distinguishable from a render error. Error path
25
+ * surfaces the Mermaid source code in a `<pre>` so consumers can debug + the
26
+ * print path still shows the code (PDF cannot run dynamic Mermaid).
27
+ */
28
+
29
+ interface MermaidPluginOptions {
30
+ /** Mermaid theme name. Defaults to `"default"`. */
31
+ theme?: "default" | "forest" | "dark" | "neutral" | "base";
32
+ }
33
+ /**
34
+ * React component that renders Mermaid lazily on mount.
35
+ *
36
+ * SSR / pre-load: renders the source as `<pre>` + `role="img"` for a11y.
37
+ * Client: lazy-loads `mermaid`, calls `mermaid.render()`, then injects the
38
+ * resulting SVG via `dangerouslySetInnerHTML` so React owns the DOM
39
+ * subtree (avoids reconciliation crashes when state flips back to the
40
+ * placeholder — `ref.innerHTML` would mutate under React's feet).
41
+ *
42
+ * The Mermaid output is a TRUSTED string from the `mermaid` lib itself (not
43
+ * user-controlled markup) — the user-supplied source is the DSL, not raw HTML.
44
+ * Using dangerouslySetInnerHTML here is a controlled escape hatch.
45
+ *
46
+ * Peer-dep guard: if `import("mermaid")` fails, falls back to the same
47
+ * pre-formatted source view + an `aria-label` hint (EC-10).
48
+ */
49
+ declare const MermaidDiagram: FC<{
50
+ source: string;
51
+ theme?: string;
52
+ }>;
53
+ declare function mermaidPlugin(opts?: MermaidPluginOptions): SlidePlugin;
54
+
55
+ export { MermaidDiagram, type MermaidPluginOptions, mermaidPlugin };
@@ -0,0 +1,218 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/components/primitives/slide/plugins/mermaid/index.tsx
5
+ var SVG_TAG_NAMES = [
6
+ "div",
7
+ // Custom element used to bridge into our React renderer (mapped via components).
8
+ "theo-mermaid",
9
+ // root + grouping
10
+ "svg",
11
+ "g",
12
+ "defs",
13
+ "use",
14
+ "symbol",
15
+ "marker",
16
+ "pattern",
17
+ "mask",
18
+ "clipPath",
19
+ // shapes
20
+ "path",
21
+ "rect",
22
+ "circle",
23
+ "ellipse",
24
+ "line",
25
+ "polyline",
26
+ "polygon",
27
+ // text
28
+ "text",
29
+ "tspan",
30
+ "textPath",
31
+ "title",
32
+ "desc",
33
+ // gradients + filters (used by some flowchart themes)
34
+ "linearGradient",
35
+ "radialGradient",
36
+ "stop",
37
+ "filter",
38
+ "feGaussianBlur",
39
+ "feOffset",
40
+ "feColorMatrix",
41
+ "feComponentTransfer",
42
+ "feComposite",
43
+ "feMerge",
44
+ "feMergeNode",
45
+ "feFlood",
46
+ // foreign content (HTML labels)
47
+ "foreignObject",
48
+ "span",
49
+ "br"
50
+ ];
51
+ var SVG_ATTRIBUTES = {
52
+ "*": [
53
+ "id",
54
+ "className",
55
+ "style",
56
+ "transform",
57
+ "fill",
58
+ "stroke",
59
+ "strokeWidth",
60
+ "strokeDasharray",
61
+ "strokeLinecap",
62
+ "strokeLinejoin",
63
+ "opacity",
64
+ "fillOpacity",
65
+ "strokeOpacity",
66
+ "ariaLabel",
67
+ "ariaHidden",
68
+ "role"
69
+ ],
70
+ svg: ["xmlns", "viewBox", "width", "height", "preserveAspectRatio", "xmlnsXlink", "version"],
71
+ path: ["d", "markerEnd", "markerStart", "markerMid"],
72
+ rect: ["x", "y", "width", "height", "rx", "ry"],
73
+ circle: ["cx", "cy", "r"],
74
+ ellipse: ["cx", "cy", "rx", "ry"],
75
+ line: ["x1", "y1", "x2", "y2"],
76
+ polyline: ["points"],
77
+ polygon: ["points"],
78
+ text: [
79
+ "x",
80
+ "y",
81
+ "dx",
82
+ "dy",
83
+ "textAnchor",
84
+ "dominantBaseline",
85
+ "fontSize",
86
+ "fontFamily",
87
+ "fontWeight"
88
+ ],
89
+ tspan: ["x", "y", "dx", "dy"],
90
+ textPath: ["xlinkHref", "href", "startOffset"],
91
+ marker: ["markerUnits", "markerWidth", "markerHeight", "refX", "refY", "orient", "viewBox"],
92
+ use: ["xlinkHref", "href", "x", "y", "width", "height"],
93
+ linearGradient: ["x1", "y1", "x2", "y2", "gradientUnits"],
94
+ radialGradient: ["cx", "cy", "r", "fx", "fy", "gradientUnits"],
95
+ stop: ["offset", "stopColor", "stopOpacity"],
96
+ foreignObject: ["x", "y", "width", "height"],
97
+ div: ["data-state", "data-theo-mermaid-source", "className"],
98
+ "theo-mermaid": ["source"]
99
+ };
100
+ var MermaidDiagram = ({ source, theme }) => {
101
+ const [svg, setSvg] = useState(null);
102
+ const [error, setError] = useState(null);
103
+ useEffect(() => {
104
+ let cancelled = false;
105
+ setSvg(null);
106
+ setError(null);
107
+ (async () => {
108
+ try {
109
+ const mermaid = (await import('mermaid')).default;
110
+ if (cancelled) return;
111
+ mermaid.initialize({ startOnLoad: false, theme: theme ?? "default" });
112
+ const id = `theo-mmd-${Math.random().toString(36).slice(2, 10)}`;
113
+ const result = await mermaid.render(id, source);
114
+ if (!cancelled) {
115
+ setSvg(result.svg);
116
+ }
117
+ } catch (e) {
118
+ if (cancelled) return;
119
+ const msg = e instanceof Error ? e.message : String(e);
120
+ const moduleNotFound = msg.includes("Cannot find module") || msg.includes("Could not resolve") || msg.includes("Failed to fetch") || msg.includes("Failed to resolve module") || msg.includes("ERR_MODULE_NOT_FOUND");
121
+ if (moduleNotFound) {
122
+ setError("Mermaid not installed. Run: pnpm add mermaid");
123
+ } else {
124
+ setError(`Mermaid render failed: ${msg}`);
125
+ }
126
+ }
127
+ })();
128
+ return () => {
129
+ cancelled = true;
130
+ };
131
+ }, [source, theme]);
132
+ if (error) {
133
+ return /* @__PURE__ */ jsx(
134
+ "div",
135
+ {
136
+ "data-theo-slide-mermaid": true,
137
+ "data-state": "error",
138
+ role: "img",
139
+ "aria-label": error,
140
+ className: "theo-slide-mermaid-host",
141
+ children: /* @__PURE__ */ jsx("pre", { style: { fontSize: "0.8em", opacity: 0.7, whiteSpace: "pre-wrap" }, children: source })
142
+ }
143
+ );
144
+ }
145
+ if (svg) {
146
+ return /* @__PURE__ */ jsx(
147
+ "div",
148
+ {
149
+ "data-theo-slide-mermaid": true,
150
+ "data-state": "ready",
151
+ role: "img",
152
+ "aria-label": "Mermaid diagram",
153
+ className: "theo-slide-mermaid-host",
154
+ dangerouslySetInnerHTML: { __html: svg }
155
+ }
156
+ );
157
+ }
158
+ return /* @__PURE__ */ jsx(
159
+ "div",
160
+ {
161
+ "data-theo-slide-mermaid": true,
162
+ "data-state": "loading",
163
+ role: "img",
164
+ "aria-label": "Mermaid diagram",
165
+ className: "theo-slide-mermaid-host",
166
+ children: /* @__PURE__ */ jsx("pre", { style: { fontSize: "0.7em", opacity: 0.4, whiteSpace: "pre-wrap" }, children: source })
167
+ }
168
+ );
169
+ };
170
+ function mermaidPlugin(opts = {}) {
171
+ return {
172
+ name: "mermaid",
173
+ sanitizeSchemaExtension: {
174
+ tagNames: SVG_TAG_NAMES,
175
+ attributes: SVG_ATTRIBUTES
176
+ },
177
+ components: {
178
+ // The hastTransform below replaces mermaid <pre> with a `<div>` carrying
179
+ // `data-theo-mermaid-source`. We can't override `div` globally — instead,
180
+ // we use a custom tagName `theo-mermaid` that the React renderer maps.
181
+ "theo-mermaid": ((props) => /* @__PURE__ */ jsx(MermaidDiagram, { source: props.source ?? "", theme: opts.theme }))
182
+ },
183
+ async hastTransform(tree) {
184
+ let visit;
185
+ try {
186
+ visit = (await import('unist-util-visit')).visit;
187
+ } catch (e) {
188
+ throw new Error(`[slide/plugins/mermaid] peer-dep 'unist-util-visit' missing: ${e}`);
189
+ }
190
+ visit(
191
+ tree,
192
+ "element",
193
+ (node, _idx, parent) => {
194
+ if (node.tagName !== "code") return;
195
+ const className = node.properties?.className ?? [];
196
+ if (!className.includes("language-mermaid")) return;
197
+ if (!parent || parent.tagName !== "pre") return;
198
+ const codeNode = node.children?.[0];
199
+ const source = codeNode && codeNode.type === "text" ? codeNode.value : "";
200
+ Object.assign(parent, {
201
+ type: "element",
202
+ tagName: "theo-mermaid",
203
+ // Note: `source` is passed as a non-standard property; the React
204
+ // component picks it up. We avoid `data-*` attributes here so we
205
+ // don't have to whitelist them in sanitize.
206
+ properties: { source },
207
+ children: []
208
+ });
209
+ }
210
+ );
211
+ return tree;
212
+ }
213
+ };
214
+ }
215
+
216
+ export { MermaidDiagram, mermaidPlugin };
217
+ //# sourceMappingURL=index.js.map
218
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/primitives/slide/plugins/mermaid/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Element, Root as HastRoot } from \"hast\";\n/**\n * Slide rich-content plugin — Mermaid diagrams.\n *\n * Peer-dep: `mermaid` (optional, ~370 KB raw — lazy + opt-in mandatory).\n * Render is client-only because Mermaid measures DOM nodes during layout;\n * SSR / static render emits a labelled placeholder + source-code fallback.\n *\n * Pipeline contribution (D11 of the rich-content plan):\n * - `hastTransform`: detect `<pre><code class=\"language-mermaid\">…</code></pre>`\n * and replace with `<div class=\"theo-slide-mermaid-host\" data-theo-mermaid-source=\"…\">`.\n * - `components.div`: when div has `data-theo-mermaid-source`, render the\n * `<MermaidDiagram>` React component that lazy-imports `mermaid` and\n * injects the SVG via `dangerouslySetInnerHTML` after `mermaid.render()`.\n *\n * EC-4: complete SVG tag + attribute allow-list (~30 tags) so the rendered SVG\n * survives sanitize. Critical: without these, sanitize strips the SVG entirely.\n *\n * EC-10: SSR placeholder is distinguishable from a render error. Error path\n * surfaces the Mermaid source code in a `<pre>` so consumers can debug + the\n * print path still shows the code (PDF cannot run dynamic Mermaid).\n */\nimport { type FC, useEffect, useState } from \"react\";\nimport type { SlidePlugin } from \"../../plugin.js\";\n\n// EC-4: comprehensive SVG tag list (mermaid 11.x output across diagram types).\nconst SVG_TAG_NAMES = [\n \"div\",\n // Custom element used to bridge into our React renderer (mapped via components).\n \"theo-mermaid\",\n // root + grouping\n \"svg\",\n \"g\",\n \"defs\",\n \"use\",\n \"symbol\",\n \"marker\",\n \"pattern\",\n \"mask\",\n \"clipPath\",\n // shapes\n \"path\",\n \"rect\",\n \"circle\",\n \"ellipse\",\n \"line\",\n \"polyline\",\n \"polygon\",\n // text\n \"text\",\n \"tspan\",\n \"textPath\",\n \"title\",\n \"desc\",\n // gradients + filters (used by some flowchart themes)\n \"linearGradient\",\n \"radialGradient\",\n \"stop\",\n \"filter\",\n \"feGaussianBlur\",\n \"feOffset\",\n \"feColorMatrix\",\n \"feComponentTransfer\",\n \"feComposite\",\n \"feMerge\",\n \"feMergeNode\",\n \"feFlood\",\n // foreign content (HTML labels)\n \"foreignObject\",\n \"span\",\n \"br\",\n];\n\nconst SVG_ATTRIBUTES: Record<string, string[]> = {\n \"*\": [\n \"id\",\n \"className\",\n \"style\",\n \"transform\",\n \"fill\",\n \"stroke\",\n \"strokeWidth\",\n \"strokeDasharray\",\n \"strokeLinecap\",\n \"strokeLinejoin\",\n \"opacity\",\n \"fillOpacity\",\n \"strokeOpacity\",\n \"ariaLabel\",\n \"ariaHidden\",\n \"role\",\n ],\n svg: [\"xmlns\", \"viewBox\", \"width\", \"height\", \"preserveAspectRatio\", \"xmlnsXlink\", \"version\"],\n path: [\"d\", \"markerEnd\", \"markerStart\", \"markerMid\"],\n rect: [\"x\", \"y\", \"width\", \"height\", \"rx\", \"ry\"],\n circle: [\"cx\", \"cy\", \"r\"],\n ellipse: [\"cx\", \"cy\", \"rx\", \"ry\"],\n line: [\"x1\", \"y1\", \"x2\", \"y2\"],\n polyline: [\"points\"],\n polygon: [\"points\"],\n text: [\n \"x\",\n \"y\",\n \"dx\",\n \"dy\",\n \"textAnchor\",\n \"dominantBaseline\",\n \"fontSize\",\n \"fontFamily\",\n \"fontWeight\",\n ],\n tspan: [\"x\", \"y\", \"dx\", \"dy\"],\n textPath: [\"xlinkHref\", \"href\", \"startOffset\"],\n marker: [\"markerUnits\", \"markerWidth\", \"markerHeight\", \"refX\", \"refY\", \"orient\", \"viewBox\"],\n use: [\"xlinkHref\", \"href\", \"x\", \"y\", \"width\", \"height\"],\n linearGradient: [\"x1\", \"y1\", \"x2\", \"y2\", \"gradientUnits\"],\n radialGradient: [\"cx\", \"cy\", \"r\", \"fx\", \"fy\", \"gradientUnits\"],\n stop: [\"offset\", \"stopColor\", \"stopOpacity\"],\n foreignObject: [\"x\", \"y\", \"width\", \"height\"],\n div: [\"data-state\", \"data-theo-mermaid-source\", \"className\"],\n \"theo-mermaid\": [\"source\"],\n};\n\nexport interface MermaidPluginOptions {\n /** Mermaid theme name. Defaults to `\"default\"`. */\n theme?: \"default\" | \"forest\" | \"dark\" | \"neutral\" | \"base\";\n}\n\n/**\n * React component that renders Mermaid lazily on mount.\n *\n * SSR / pre-load: renders the source as `<pre>` + `role=\"img\"` for a11y.\n * Client: lazy-loads `mermaid`, calls `mermaid.render()`, then injects the\n * resulting SVG via `dangerouslySetInnerHTML` so React owns the DOM\n * subtree (avoids reconciliation crashes when state flips back to the\n * placeholder — `ref.innerHTML` would mutate under React's feet).\n *\n * The Mermaid output is a TRUSTED string from the `mermaid` lib itself (not\n * user-controlled markup) — the user-supplied source is the DSL, not raw HTML.\n * Using dangerouslySetInnerHTML here is a controlled escape hatch.\n *\n * Peer-dep guard: if `import(\"mermaid\")` fails, falls back to the same\n * pre-formatted source view + an `aria-label` hint (EC-10).\n */\nexport const MermaidDiagram: FC<{ source: string; theme?: string }> = ({ source, theme }) => {\n const [svg, setSvg] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n // Reset state when source/theme changes so the placeholder shows again\n // while the new diagram renders.\n setSvg(null);\n setError(null);\n (async () => {\n try {\n // biome-ignore lint/suspicious/noExplicitAny: mermaid default export untyped\n const mermaid: any = (await import(\"mermaid\")).default;\n if (cancelled) return;\n mermaid.initialize({ startOnLoad: false, theme: theme ?? \"default\" });\n const id = `theo-mmd-${Math.random().toString(36).slice(2, 10)}`;\n const result = await mermaid.render(id, source);\n if (!cancelled) {\n setSvg(result.svg);\n }\n } catch (e) {\n if (cancelled) return;\n const msg = e instanceof Error ? e.message : String(e);\n // Recognize the various module-resolution failure messages emitted by\n // Node, Vite, Rollup, Webpack, Vitest, and browser dynamic-import.\n const moduleNotFound =\n msg.includes(\"Cannot find module\") ||\n msg.includes(\"Could not resolve\") ||\n msg.includes(\"Failed to fetch\") ||\n msg.includes(\"Failed to resolve module\") ||\n msg.includes(\"ERR_MODULE_NOT_FOUND\");\n if (moduleNotFound) {\n setError(\"Mermaid not installed. Run: pnpm add mermaid\");\n } else {\n setError(`Mermaid render failed: ${msg}`);\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [source, theme]);\n\n if (error) {\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"error\"\n role=\"img\"\n aria-label={error}\n className=\"theo-slide-mermaid-host\"\n >\n <pre style={{ fontSize: \"0.8em\", opacity: 0.7, whiteSpace: \"pre-wrap\" }}>{source}</pre>\n </div>\n );\n }\n if (svg) {\n // SVG is React-owned via dangerouslySetInnerHTML so the next state change\n // (e.g. new `source` prop → back to placeholder) can unmount cleanly.\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"ready\"\n role=\"img\"\n aria-label=\"Mermaid diagram\"\n className=\"theo-slide-mermaid-host\"\n // biome-ignore lint/security/noDangerouslySetInnerHtml: mermaid.render() output is trusted (lib-generated SVG, not user markup)\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n }\n return (\n <div\n data-theo-slide-mermaid\n data-state=\"loading\"\n role=\"img\"\n aria-label=\"Mermaid diagram\"\n className=\"theo-slide-mermaid-host\"\n >\n <pre style={{ fontSize: \"0.7em\", opacity: 0.4, whiteSpace: \"pre-wrap\" }}>{source}</pre>\n </div>\n );\n};\n\nexport function mermaidPlugin(opts: MermaidPluginOptions = {}): SlidePlugin {\n return {\n name: \"mermaid\",\n sanitizeSchemaExtension: {\n tagNames: SVG_TAG_NAMES,\n attributes: SVG_ATTRIBUTES,\n },\n components: {\n // The hastTransform below replaces mermaid <pre> with a `<div>` carrying\n // `data-theo-mermaid-source`. We can't override `div` globally — instead,\n // we use a custom tagName `theo-mermaid` that the React renderer maps.\n \"theo-mermaid\": ((props: { source?: string }) => (\n <MermaidDiagram source={props.source ?? \"\"} theme={opts.theme} />\n )) as FC<unknown>,\n },\n async hastTransform(tree: HastRoot): Promise<HastRoot> {\n // unist-util-visit is a peer-dep of the slide stack — should always be\n // present. Guard anyway per EC-2.\n // biome-ignore lint/suspicious/noExplicitAny: unist-util-visit untyped at runtime\n let visit: any;\n try {\n visit = (await import(\"unist-util-visit\")).visit;\n } catch (e) {\n throw new Error(`[slide/plugins/mermaid] peer-dep 'unist-util-visit' missing: ${e}`);\n }\n visit(\n tree,\n \"element\",\n (\n node: Element,\n _idx: number | undefined,\n parent: { tagName?: string; type?: string; children?: unknown[] } | undefined,\n ) => {\n if (node.tagName !== \"code\") return;\n const className = (node.properties?.className as string[] | undefined) ?? [];\n if (!className.includes(\"language-mermaid\")) return;\n if (!parent || parent.tagName !== \"pre\") return;\n const codeNode = node.children?.[0];\n const source = codeNode && codeNode.type === \"text\" ? codeNode.value : \"\";\n // Replace the entire <pre> with our custom element. hast-util-to-jsx-runtime\n // maps element tagName to a React component if found in `components` map.\n Object.assign(parent, {\n type: \"element\",\n tagName: \"theo-mermaid\",\n // Note: `source` is passed as a non-standard property; the React\n // component picks it up. We avoid `data-*` attributes here so we\n // don't have to whitelist them in sanitize.\n properties: { source },\n children: [],\n });\n },\n );\n return tree;\n },\n };\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { a as SlidePlugin } from '../../../plugin-Atb0VKtr.js';
2
+ import 'hast';
3
+ import 'mdast';
4
+ import 'react';
5
+ import 'zod';
6
+
7
+ interface ShikiPluginOptions {
8
+ /** Dual-theme map — light/dark Shiki theme names. Defaults to `github-light` / `github-dark`. */
9
+ themes?: {
10
+ light: string;
11
+ dark: string;
12
+ };
13
+ /** Languages to pre-load. Unknown languages pass-through. Defaults to a tech-stack baseline. */
14
+ langs?: string[];
15
+ }
16
+ declare function shikiPlugin(opts?: ShikiPluginOptions): SlidePlugin;
17
+
18
+ export { type ShikiPluginOptions, shikiPlugin };
@@ -0,0 +1,87 @@
1
+ // src/components/primitives/slide/plugins/shiki/index.ts
2
+ var DEFAULT_LANGS = [
3
+ "ts",
4
+ "tsx",
5
+ "js",
6
+ "jsx",
7
+ "python",
8
+ "go",
9
+ "rust",
10
+ "java",
11
+ "json",
12
+ "yaml",
13
+ "bash",
14
+ "shell",
15
+ "html",
16
+ "css",
17
+ "sql",
18
+ "markdown"
19
+ ];
20
+ function shikiPlugin(opts = {}) {
21
+ const themes = opts.themes ?? { light: "github-light", dark: "github-dark" };
22
+ const langs = opts.langs ?? DEFAULT_LANGS;
23
+ let highlighter = null;
24
+ let peerDepMissing = false;
25
+ async function getHighlighter() {
26
+ if (highlighter) return highlighter;
27
+ if (peerDepMissing) return null;
28
+ try {
29
+ const shiki = await import('shiki');
30
+ highlighter = await shiki.createHighlighter({
31
+ themes: [themes.light, themes.dark],
32
+ langs
33
+ });
34
+ return highlighter;
35
+ } catch (e) {
36
+ peerDepMissing = true;
37
+ throw new Error(
38
+ `[slide/plugins/shiki] peer-dep 'shiki' not installed or failed to load. Run: pnpm add shiki. Original: ${e instanceof Error ? e.message : String(e)}`
39
+ );
40
+ }
41
+ }
42
+ return {
43
+ name: "shiki",
44
+ sanitizeSchemaExtension: {
45
+ tagNames: ["span", "pre", "code"],
46
+ // Shiki emits inline `style` and `class` on tokens; "*" applies to every tag.
47
+ attributes: {
48
+ "*": ["style", "className"],
49
+ pre: ["style", "className", "tabIndex"],
50
+ code: ["style", "className"],
51
+ span: ["style", "className"]
52
+ }
53
+ },
54
+ async hastTransform(tree) {
55
+ const hl = await getHighlighter();
56
+ if (!hl) return tree;
57
+ const { visit } = await import('unist-util-visit');
58
+ const { fromHtml } = await import('hast-util-from-html');
59
+ visit(tree, "element", (node, _idx, parent) => {
60
+ if (node.tagName !== "code") return;
61
+ const className = node.properties?.className ?? [];
62
+ const langClass = className.find((c) => c.startsWith("language-"));
63
+ if (!langClass) return;
64
+ const lang = langClass.replace("language-", "");
65
+ if (!langs.includes(lang)) return;
66
+ const first = node.children?.[0];
67
+ const codeText = first && first.type === "text" ? first.value : "";
68
+ let html;
69
+ try {
70
+ html = hl.codeToHtml(codeText, { lang, themes });
71
+ } catch {
72
+ return;
73
+ }
74
+ const newTree = fromHtml(html, { fragment: true });
75
+ const top = newTree.children[0];
76
+ if (parent?.tagName === "pre" && parent.children?.length === 1 && top) {
77
+ Object.assign(parent, top);
78
+ }
79
+ });
80
+ return tree;
81
+ }
82
+ };
83
+ }
84
+
85
+ export { shikiPlugin };
86
+ //# sourceMappingURL=index.js.map
87
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/primitives/slide/plugins/shiki/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Slide rich-content plugin — Shiki syntax highlighting.\n *\n * Lazy + opt-in (ADR D9, D13 of the rich-content plan). Peer-dep `shiki` is\n * declared optional in `package.json`; the plugin guards every dynamic import\n * so a missing peer-dep is reported (D16) rather than crashing the slide.\n *\n * Pipeline contribution:\n * - `hastTransform`: walks `<pre><code class=\"language-XXX\">` nodes, replaces\n * them with the pre-rendered `<pre><code>` tree emitted by Shiki.\n * - `sanitizeSchemaExtension`: allows `<span>` with `style` and `class`\n * (Shiki emits inline styles for tokens).\n *\n * Performance:\n * - Highlighter is created lazily on first use and cached.\n * - Grammars listed in `langs` are pre-loaded once; unknown langs pass-through\n * as plain `<pre><code>` (defaultSchema allows that).\n *\n * @example\n * import { shikiPlugin } from \"@usetheo/ui/slide/plugins/shiki\";\n * import \"shiki\"; // installed as peer-dep\n * <Slide markdown={md} plugins={[shikiPlugin({ langs: [\"ts\",\"python\"] })]} />\n */\nimport type { Element, Root as HastRoot } from \"hast\";\nimport type { SlidePlugin } from \"../../plugin.js\";\n\nexport interface ShikiPluginOptions {\n /** Dual-theme map — light/dark Shiki theme names. Defaults to `github-light` / `github-dark`. */\n themes?: { light: string; dark: string };\n /** Languages to pre-load. Unknown languages pass-through. Defaults to a tech-stack baseline. */\n langs?: string[];\n}\n\nconst DEFAULT_LANGS = [\n \"ts\",\n \"tsx\",\n \"js\",\n \"jsx\",\n \"python\",\n \"go\",\n \"rust\",\n \"java\",\n \"json\",\n \"yaml\",\n \"bash\",\n \"shell\",\n \"html\",\n \"css\",\n \"sql\",\n \"markdown\",\n];\n\nexport function shikiPlugin(opts: ShikiPluginOptions = {}): SlidePlugin {\n const themes = opts.themes ?? { light: \"github-light\", dark: \"github-dark\" };\n const langs = opts.langs ?? DEFAULT_LANGS;\n // biome-ignore lint/suspicious/noExplicitAny: shiki has no exported type for `Highlighter` at the top level\n let highlighter: any = null;\n let peerDepMissing = false;\n\n // biome-ignore lint/suspicious/noExplicitAny: shiki Highlighter is untyped here\n async function getHighlighter(): Promise<any> {\n if (highlighter) return highlighter;\n if (peerDepMissing) return null;\n try {\n const shiki = await import(\"shiki\");\n highlighter = await shiki.createHighlighter({\n themes: [themes.light, themes.dark],\n langs,\n });\n return highlighter;\n } catch (e) {\n peerDepMissing = true;\n // Surface via D16 path (composePlugins catches → PLUGIN_ERROR).\n throw new Error(\n `[slide/plugins/shiki] peer-dep 'shiki' not installed or failed to load. Run: pnpm add shiki. Original: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n }\n\n return {\n name: \"shiki\",\n sanitizeSchemaExtension: {\n tagNames: [\"span\", \"pre\", \"code\"],\n // Shiki emits inline `style` and `class` on tokens; \"*\" applies to every tag.\n attributes: {\n \"*\": [\"style\", \"className\"],\n pre: [\"style\", \"className\", \"tabIndex\"],\n code: [\"style\", \"className\"],\n span: [\"style\", \"className\"],\n },\n },\n async hastTransform(tree: HastRoot): Promise<HastRoot> {\n const hl = await getHighlighter();\n if (!hl) return tree; // peer-dep missing AND we've already reported it once\n const { visit } = await import(\"unist-util-visit\");\n const { fromHtml } = await import(\"hast-util-from-html\");\n // Walk: find <code class=\"language-XXX\">, replace its parent <pre> subtree\n // with the highlighted output (which is also a <pre><code>...</code></pre>).\n // biome-ignore lint/suspicious/noExplicitAny: hast unist-util-visit untyped here\n visit(tree, \"element\", (node: Element, _idx: number | undefined, parent: any) => {\n if (node.tagName !== \"code\") return;\n const className = (node.properties?.className as string[] | undefined) ?? [];\n const langClass = className.find((c) => c.startsWith(\"language-\"));\n if (!langClass) return;\n const lang = langClass.replace(\"language-\", \"\");\n if (!langs.includes(lang)) return;\n const first = node.children?.[0];\n const codeText = first && first.type === \"text\" ? first.value : \"\";\n let html: string;\n try {\n html = hl.codeToHtml(codeText, { lang, themes });\n } catch {\n // Shiki failed on this snippet (e.g. malformed input). Leave the plain\n // <pre><code> in place — the consumer still gets readable text.\n return;\n }\n const newTree = fromHtml(html, { fragment: true });\n const top = newTree.children[0];\n if (parent?.tagName === \"pre\" && parent.children?.length === 1 && top) {\n Object.assign(parent, top);\n }\n });\n return tree;\n },\n };\n}\n"]}