@mcp-b/interactive-components 0.2.0

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/code-editor-B94tewLC.js +425 -0
  4. package/dist/code-preview-gKk0nBKP.js +269 -0
  5. package/dist/components/code-editor/code-editor.d.ts +65 -0
  6. package/dist/components/code-editor/code-editor.js +2 -0
  7. package/dist/components/code-preview/code-preview.d.ts +58 -0
  8. package/dist/components/code-preview/code-preview.js +3 -0
  9. package/dist/components/interactive-editor/interactive-editor.d.ts +77 -0
  10. package/dist/components/interactive-editor/interactive-editor.js +3 -0
  11. package/dist/components/r-editor/r-editor.d.ts +68 -0
  12. package/dist/components/r-editor/r-editor.js +2 -0
  13. package/dist/components/sql-editor/sql-editor.d.ts +78 -0
  14. package/dist/components/sql-editor/sql-editor.js +2 -0
  15. package/dist/decorate-DcF3lt7P.js +9 -0
  16. package/dist/docs/custom-elements.json +2807 -0
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.js +8 -0
  19. package/dist/interactive-editor-WfTWxJGF.js +1454 -0
  20. package/dist/lib/runner-channel.d.ts +25 -0
  21. package/dist/lib/runner-channel.js +55 -0
  22. package/dist/lib/runner-url.d.ts +15 -0
  23. package/dist/lib/runner-url.js +19 -0
  24. package/dist/lib/transform.d.ts +2 -0
  25. package/dist/lib/transform.js +210 -0
  26. package/dist/r-editor-DwSyDo2i.js +743 -0
  27. package/dist/runners/ephemeral-indexeddb.js +114 -0
  28. package/dist/runners/pglite-runner.js +175 -0
  29. package/dist/runners/pyscript-runner.html +189 -0
  30. package/dist/runners/webr-runner.html +282 -0
  31. package/dist/sql-editor-CsNrjJ2_.js +968 -0
  32. package/dist/themes/interactive.css +79 -0
  33. package/dist/transform-DWhHlnkw.d.ts +53 -0
  34. package/dist/vite.d.ts +14 -0
  35. package/dist/vite.js +62 -0
  36. package/package.json +96 -0
@@ -0,0 +1,25 @@
1
+ //#region src/lib/runner-channel.d.ts
2
+ declare const RUNNER_CONNECT_TYPE = "sigvelo-runner-connect-v1";
3
+ declare const RUNNER_CHANNEL_PARAM = "sigvelo-channel";
4
+ declare const RUNNER_PARENT_ORIGIN_PARAM = "sigvelo-parent-origin";
5
+ /** Creates a 128-bit runner capability without requiring a secure context. */
6
+ declare function createRunnerChannelId(): string;
7
+ interface RunnerConsoleMessage {
8
+ __sigvelo_console: true;
9
+ level: "log" | "warn" | "error" | "info";
10
+ args: string[];
11
+ ts: number;
12
+ }
13
+ declare function isRecord(value: unknown): value is Record<string, unknown>;
14
+ declare function parseRunnerConsoleMessage(value: unknown): RunnerConsoleMessage | null;
15
+ declare function bindRunnerUrl(url: URL, channelId: string): URL;
16
+ /**
17
+ * Gives one sandboxed runner a private channel. An opaque-origin iframe cannot
18
+ * be named by `targetOrigin`, so the one wildcard send is restricted to its
19
+ * exact WindowProxy and transfers only a fresh port. The runner validates the
20
+ * sender window, sender origin, protocol marker, and transferred port before
21
+ * accepting it; all subsequent traffic stays off the global `message` event.
22
+ */
23
+ declare function connectRunner(iframe: HTMLIFrameElement, channelId: string, onMessage: (data: unknown) => void): MessagePort | null;
24
+ //#endregion
25
+ export { RUNNER_CHANNEL_PARAM, RUNNER_CONNECT_TYPE, RUNNER_PARENT_ORIGIN_PARAM, RunnerConsoleMessage, bindRunnerUrl, connectRunner, createRunnerChannelId, isRecord, parseRunnerConsoleMessage };
@@ -0,0 +1,55 @@
1
+ //#region src/lib/runner-channel.ts
2
+ const RUNNER_CONNECT_TYPE = "sigvelo-runner-connect-v1";
3
+ const RUNNER_CHANNEL_PARAM = "sigvelo-channel";
4
+ const RUNNER_PARENT_ORIGIN_PARAM = "sigvelo-parent-origin";
5
+ /** Creates a 128-bit runner capability without requiring a secure context. */
6
+ function createRunnerChannelId() {
7
+ const values = crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(4));
8
+ return Array.from(values, (value) => value.toString(16).padStart(8, "0")).join("-");
9
+ }
10
+ function isRecord(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ function parseRunnerConsoleMessage(value) {
14
+ if (!isRecord(value) || value.__sigvelo_console !== true) return null;
15
+ if (value.level !== "log" && value.level !== "warn" && value.level !== "error" && value.level !== "info") return null;
16
+ if (!Array.isArray(value.args) || !value.args.every((arg) => typeof arg === "string")) return null;
17
+ if (typeof value.ts !== "number" || !Number.isFinite(value.ts)) return null;
18
+ return {
19
+ __sigvelo_console: true,
20
+ level: value.level,
21
+ args: value.args,
22
+ ts: value.ts
23
+ };
24
+ }
25
+ function bindRunnerUrl(url, channelId) {
26
+ const fragment = new URLSearchParams(url.hash.slice(1));
27
+ fragment.set(RUNNER_CHANNEL_PARAM, channelId);
28
+ fragment.set(RUNNER_PARENT_ORIGIN_PARAM, window.location.origin);
29
+ url.hash = fragment.toString();
30
+ return url;
31
+ }
32
+ /**
33
+ * Gives one sandboxed runner a private channel. An opaque-origin iframe cannot
34
+ * be named by `targetOrigin`, so the one wildcard send is restricted to its
35
+ * exact WindowProxy and transfers only a fresh port. The runner validates the
36
+ * sender window, sender origin, protocol marker, and transferred port before
37
+ * accepting it; all subsequent traffic stays off the global `message` event.
38
+ */
39
+ function connectRunner(iframe, channelId, onMessage) {
40
+ const runnerWindow = iframe.contentWindow;
41
+ if (!runnerWindow) return null;
42
+ const channel = new MessageChannel();
43
+ channel.port1.addEventListener("message", (event) => {
44
+ onMessage(event.data);
45
+ });
46
+ channel.port1.start();
47
+ runnerWindow.postMessage({
48
+ type: RUNNER_CONNECT_TYPE,
49
+ channelId,
50
+ parentOrigin: window.location.origin
51
+ }, "*", [channel.port2]);
52
+ return channel.port1;
53
+ }
54
+ //#endregion
55
+ export { RUNNER_CHANNEL_PARAM, RUNNER_CONNECT_TYPE, RUNNER_PARENT_ORIGIN_PARAM, bindRunnerUrl, connectRunner, createRunnerChannelId, isRecord, parseRunnerConsoleMessage };
@@ -0,0 +1,15 @@
1
+ //#region src/lib/runner-url.d.ts
2
+ type RunnerName = "pglite" | "pyscript" | "webr";
3
+ /**
4
+ * Overrides the same-origin mount where the complete `dist/runners/` tree is served.
5
+ *
6
+ * Hosts must preserve the tree's relative paths and send
7
+ * `Access-Control-Allow-Origin: *`. The Python and R runners intentionally use
8
+ * opaque sandbox origins, so their module and runtime fetches require CORS. The
9
+ * PGlite module worker requires the mount to share the application's origin.
10
+ */
11
+ declare function setRunnerBaseUrl(url: URL): void;
12
+ /** Returns the runner shipped beside this package's compiled modules. */
13
+ declare function getRunnerUrl(name: RunnerName): URL;
14
+ //#endregion
15
+ export { RunnerName, getRunnerUrl, setRunnerBaseUrl };
@@ -0,0 +1,19 @@
1
+ //#region src/lib/runner-url.ts
2
+ let runnerBaseUrl = new URL("../runners/", import.meta.url);
3
+ /**
4
+ * Overrides the same-origin mount where the complete `dist/runners/` tree is served.
5
+ *
6
+ * Hosts must preserve the tree's relative paths and send
7
+ * `Access-Control-Allow-Origin: *`. The Python and R runners intentionally use
8
+ * opaque sandbox origins, so their module and runtime fetches require CORS. The
9
+ * PGlite module worker requires the mount to share the application's origin.
10
+ */
11
+ function setRunnerBaseUrl(url) {
12
+ runnerBaseUrl = new URL(url);
13
+ }
14
+ /** Returns the runner shipped beside this package's compiled modules. */
15
+ function getRunnerUrl(name) {
16
+ return new URL(`${name}-runner.${name === "pglite" ? "js" : "html"}`, runnerBaseUrl);
17
+ }
18
+ //#endregion
19
+ export { getRunnerUrl, setRunnerBaseUrl };
@@ -0,0 +1,2 @@
1
+ import { a as buildModulePreview, c as needsTransform, i as buildHtmlPreview, l as rewriteNpmImports, n as FileLanguage, o as buildPythonPayload, r as PyScriptRunPayload, s as detectLanguage, t as EditorFile, u as transformCode } from "../transform-DWhHlnkw.js";
2
+ export { EditorFile, FileLanguage, PyScriptRunPayload, buildHtmlPreview, buildModulePreview, buildPythonPayload, detectLanguage, needsTransform, rewriteNpmImports, transformCode };
@@ -0,0 +1,210 @@
1
+ //#region src/lib/transform.ts
2
+ function detectLanguage(filename) {
3
+ return {
4
+ ts: "typescript",
5
+ tsx: "tsx",
6
+ jsx: "jsx",
7
+ html: "html",
8
+ htm: "html",
9
+ css: "css",
10
+ js: "javascript",
11
+ mjs: "javascript",
12
+ py: "python",
13
+ r: "r",
14
+ sql: "sql"
15
+ }[filename.split(".").pop()?.toLowerCase() ?? ""] ?? "javascript";
16
+ }
17
+ function needsTransform(lang) {
18
+ return lang === "typescript" || lang === "tsx" || lang === "jsx";
19
+ }
20
+ async function transformCode(code, filename) {
21
+ const res = await fetch("https://esm.sh/transform", {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify({
25
+ code,
26
+ filename,
27
+ target: "es2022",
28
+ importMap: { imports: {} }
29
+ })
30
+ });
31
+ if (!res.ok) {
32
+ const text = await res.text();
33
+ throw new Error(`esm.sh transform failed (${res.status}): ${text}`);
34
+ }
35
+ return (await res.json()).code;
36
+ }
37
+ /** Rewrites bare npm specifiers (`from 'react'`) to esm.sh CDN URLs. */
38
+ function rewriteNpmImports(code) {
39
+ return code.replace(/from\s+['"](?!https?:\/\/|\.\/|\.\.\/|\/)((?:@[^/'"]+\/[^'"]+|[^@.'"][^'"]*))['"];?/g, (_, pkg) => `from 'https://esm.sh/${pkg}';`);
40
+ }
41
+ /**
42
+ * Builds a preview for HTML-entry projects. Inlines referenced CSS and JS
43
+ * files, transforming TS/JSX as needed.
44
+ */
45
+ async function buildHtmlPreview(files) {
46
+ const indexHtml = files.get("index.html");
47
+ if (!indexHtml) return "<html><body><p>No index.html found.</p></body></html>";
48
+ let doc = indexHtml.content;
49
+ doc = doc.replace(/<link\s[^>]*rel=["']stylesheet["'][^>]*href=["']([^"']+)["'][^>]*\/?>/gi, (match, href) => {
50
+ const file = files.get(href);
51
+ return file ? `<style>\n${file.content}\n</style>` : match;
52
+ });
53
+ const moduleFiles = /* @__PURE__ */ new Map();
54
+ for (const [name, file] of files) {
55
+ const lang = file.language ?? detectLanguage(name);
56
+ if (lang === "html" || lang === "css") continue;
57
+ let code = file.content;
58
+ if (needsTransform(lang)) code = await transformCode(code, file.name);
59
+ code = rewriteNpmImports(code);
60
+ moduleFiles.set(name, code);
61
+ }
62
+ const dataUriMap = /* @__PURE__ */ new Map();
63
+ if (moduleFiles.size > 0) {
64
+ for (const [name, code] of moduleFiles) dataUriMap.set(name, toJsDataUri(code));
65
+ for (let pass = 0; pass < moduleFiles.size; pass++) {
66
+ let changed = false;
67
+ for (const [name, code] of moduleFiles) {
68
+ const uri = toJsDataUri(patchRelativeImports(code, name, dataUriMap));
69
+ if (uri !== dataUriMap.get(name)) {
70
+ dataUriMap.set(name, uri);
71
+ changed = true;
72
+ }
73
+ }
74
+ if (!changed) break;
75
+ }
76
+ }
77
+ const scriptMatches = [];
78
+ for (const m of doc.matchAll(/<script\s[^>]*src=["']([^"']+)["'][^>]*><\/script>/gi)) scriptMatches.push({
79
+ match: m[0],
80
+ src: m[1]
81
+ });
82
+ for (const { match, src } of scriptMatches) {
83
+ const dataUri = dataUriMap.get(src);
84
+ if (dataUri) doc = doc.replace(match, `<script type="module" src="${dataUri}"><\/script>`);
85
+ }
86
+ return doc;
87
+ }
88
+ /**
89
+ * Builds a preview for module-entry projects (TS/TSX/JS). Transforms each
90
+ * file, creates Blob URLs for inter-file imports, and wraps in HTML.
91
+ *
92
+ * Returns `{ html, blobUrls }` — caller should revoke blob URLs on cleanup.
93
+ */
94
+ async function buildModulePreview(files, entryName) {
95
+ const blobUrls = [];
96
+ const transformed = /* @__PURE__ */ new Map();
97
+ for (const [name, file] of files) {
98
+ const lang = file.language ?? detectLanguage(name);
99
+ if (lang === "html" || lang === "css") continue;
100
+ let code = file.content;
101
+ if (needsTransform(lang)) code = await transformCode(code, file.name);
102
+ code = rewriteNpmImports(code);
103
+ transformed.set(name, code);
104
+ }
105
+ const cssChunks = [];
106
+ for (const [, file] of files) if ((file.language ?? detectLanguage(file.name)) === "css") cssChunks.push(file.content);
107
+ const fileNames = [...transformed.keys()];
108
+ const blobUrlMap = /* @__PURE__ */ new Map();
109
+ for (const name of fileNames) {
110
+ let code = transformed.get(name);
111
+ code = patchRelativeImports(code, name, blobUrlMap);
112
+ const blob = new Blob([code], { type: "text/javascript" });
113
+ const url = URL.createObjectURL(blob);
114
+ blobUrlMap.set(name, url);
115
+ blobUrls.push(url);
116
+ }
117
+ if (fileNames.length > 1) {
118
+ const entryUrl = blobUrlMap.get(entryName);
119
+ if (entryUrl) {
120
+ URL.revokeObjectURL(entryUrl);
121
+ blobUrls.splice(blobUrls.indexOf(entryUrl), 1);
122
+ }
123
+ let code = transformed.get(entryName);
124
+ code = patchRelativeImports(code, entryName, blobUrlMap);
125
+ const blob = new Blob([code], { type: "text/javascript" });
126
+ const url = URL.createObjectURL(blob);
127
+ blobUrlMap.set(entryName, url);
128
+ blobUrls.push(url);
129
+ }
130
+ const entryUrl = blobUrlMap.get(entryName) ?? "";
131
+ const entryLang = detectLanguage(entryName);
132
+ const isReact = entryLang === "tsx" || entryLang === "jsx";
133
+ return {
134
+ html: `<!DOCTYPE html>
135
+ <html>
136
+ <head>
137
+ <meta charset="utf-8">
138
+ <meta name="viewport" content="width=device-width, initial-scale=1">
139
+ ${cssChunks.length ? `<style>${cssChunks.join("\n")}</style>` : ""}
140
+ <style>body { margin: 0; padding: 0; font-family: system-ui, sans-serif; } * { box-sizing: border-box; }</style>
141
+ </head>
142
+ <body>
143
+ ${isReact ? "<div id=\"root\"></div>" : ""}
144
+ <script type="module" src="${entryUrl}"><\/script>
145
+ </body>
146
+ </html>`,
147
+ blobUrls
148
+ };
149
+ }
150
+ /**
151
+ * Builds a postMessage payload for the PyScript runner page.
152
+ *
153
+ * PyScript cannot run inside srcdoc or blob-URL iframes because it uses
154
+ * `new URL(m, location.href)` internally, and those iframe types have
155
+ * invalid base URLs. Instead, we load a static `pyscript-runner.html`
156
+ * page (served at a real HTTP URL) and send code to it via postMessage.
157
+ */
158
+ function buildPythonPayload(files, entryName) {
159
+ const entry = files.get(entryName);
160
+ const cssChunks = [];
161
+ for (const [, file] of files) if ((file.language ?? detectLanguage(file.name)) === "css") cssChunks.push(file.content);
162
+ let config = null;
163
+ const jsonFile = files.get("pyscript.json");
164
+ if (jsonFile) try {
165
+ config = JSON.parse(jsonFile.content);
166
+ } catch {}
167
+ const htmlFile = files.get("index.html");
168
+ const bodyHtml = htmlFile ? htmlFile.content.replace(/<!DOCTYPE[^>]*>/i, "").replace(/<\/?html[^>]*>/gi, "").replace(/<head[\s\S]*?<\/head>/i, "").replace(/<\/?body[^>]*>/gi, "") : "";
169
+ return {
170
+ type: "pyscript-run",
171
+ code: entry?.content ?? "",
172
+ bodyHtml,
173
+ css: cssChunks.join("\n"),
174
+ config
175
+ };
176
+ }
177
+ function toJsDataUri(code) {
178
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(code).replace(/'/g, "%27")}`;
179
+ }
180
+ function normalizePath(p) {
181
+ const parts = p.split("/");
182
+ const out = [];
183
+ for (const seg of parts) {
184
+ if (seg === "." || seg === "") continue;
185
+ if (seg === ".." && out.length > 0) out.pop();
186
+ else out.push(seg);
187
+ }
188
+ return out.join("/");
189
+ }
190
+ function patchRelativeImports(code, currentFile, blobUrlMap) {
191
+ return code.replace(/from\s+['"](\.[^'"]+)['"]/g, (match, relPath) => {
192
+ const resolved = normalizePath((currentFile.includes("/") ? currentFile.slice(0, currentFile.lastIndexOf("/") + 1) : "") + relPath);
193
+ const candidates = [
194
+ resolved,
195
+ resolved + ".js",
196
+ resolved + ".ts",
197
+ resolved + ".tsx",
198
+ resolved + ".jsx",
199
+ resolved.replace(/\.\w+$/, ".js"),
200
+ resolved.replace(/\.\w+$/, "")
201
+ ];
202
+ for (const c of candidates) {
203
+ const url = blobUrlMap.get(c);
204
+ if (url) return `from '${url}'`;
205
+ }
206
+ return match;
207
+ });
208
+ }
209
+ //#endregion
210
+ export { buildHtmlPreview, buildModulePreview, buildPythonPayload, detectLanguage, needsTransform, rewriteNpmImports, transformCode };