@codespark/react 1.0.0 → 1.0.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.
- package/dist/index.d.ts +12 -0
- package/dist/index.js +69 -13
- package/dist/monaco.js +83 -62
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -451,6 +451,12 @@ interface CodesparkPreviewProps extends ConfigContextValue, Pick<WorkspaceInit,
|
|
|
451
451
|
* @default true
|
|
452
452
|
*/
|
|
453
453
|
tailwindcss?: boolean;
|
|
454
|
+
/**
|
|
455
|
+
* Whether to apply preflight styles (base reset, font smoothing, layout defaults) in the preview iframe
|
|
456
|
+
*
|
|
457
|
+
* @default true
|
|
458
|
+
*/
|
|
459
|
+
preflight?: boolean;
|
|
454
460
|
/**
|
|
455
461
|
* Child elements to inject into the preview iframe, such as Script, Style, Link components
|
|
456
462
|
*
|
|
@@ -694,6 +700,12 @@ interface CodesparkProps extends Pick<ConfigContextValue, 'theme'>, Pick<Codespa
|
|
|
694
700
|
* @default 'vertical'
|
|
695
701
|
*/
|
|
696
702
|
orientation?: 'vertical' | 'horizontal';
|
|
703
|
+
/**
|
|
704
|
+
* Whether to apply preflight styles (base reset, font smoothing, layout defaults) in the preview iframe
|
|
705
|
+
*
|
|
706
|
+
* @default true
|
|
707
|
+
*/
|
|
708
|
+
preflight?: boolean;
|
|
697
709
|
}
|
|
698
710
|
/**
|
|
699
711
|
* Codespark - A browser-based React code playground with live preview.
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,16 @@ function useLatest(value) {
|
|
|
57
57
|
ref.current = value;
|
|
58
58
|
return ref;
|
|
59
59
|
}
|
|
60
|
+
function useUpdateEffect(effect, deps) {
|
|
61
|
+
const mounted = useRef(false);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!mounted.current) {
|
|
64
|
+
mounted.current = true;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
return effect();
|
|
68
|
+
}, deps);
|
|
69
|
+
}
|
|
60
70
|
function getLanguageFromFile(name) {
|
|
61
71
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
62
72
|
return ext ? {
|
|
@@ -476,8 +486,8 @@ function createWorkspace(source, config) {
|
|
|
476
486
|
return new Workspace({
|
|
477
487
|
id,
|
|
478
488
|
framework,
|
|
479
|
-
entry:
|
|
480
|
-
files
|
|
489
|
+
entry: Object.keys(files)[0] || "",
|
|
490
|
+
files
|
|
481
491
|
});
|
|
482
492
|
} else {
|
|
483
493
|
const { code, locals, imports } = entry;
|
|
@@ -1180,7 +1190,7 @@ function useInjections(children) {
|
|
|
1180
1190
|
|
|
1181
1191
|
//#endregion
|
|
1182
1192
|
//#region ./srcdoc.html?raw
|
|
1183
|
-
var srcdoc_default = "<!doctype html>\n<html class=\"DEFAULT_THEME\" style=\"color-scheme: DEFAULT_THEME\">\n <head>\n <style>\n :root {\n font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n font-size: 16px;\n line-height: 1.5;\n font-synthesis: none;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n html,\n body {\n margin: 0;\n overflow: hidden;\n }\n\n html {\n width: 100vw;\n height: 100vh;\n padding: 0;\n }\n\n body {\n background: var(--background);\n color: var(--foreground);\n font-size: 14px;\n height: 100%;\n padding: 12px;\n overflow: auto;\n box-sizing: border-box;\n display: flex;\n }\n\n #root {\n margin: auto;\n }\n </style>\n <script type=\"importmap\">\n <!-- IMPORT_MAP -->\n <\/script>\n <!-- PRESET_TAG -->\n <script>\n (() => {\n // Monitor module loading via PerformanceObserver\n const observer = new PerformanceObserver(list => {\n for (const entry of list.getEntries()) {\n if (entry.initiatorType === 'script' || entry.initiatorType === 'link') {\n const url = entry.name;\n // Extract package name from URL (e.g., esm.sh/react@18.2.0/jsx-runtime -> react@18.2.0/jsx-runtime)\n const match = url.match(/esm\\.sh\\/([^\\s?#]+)/);\n const name = match ? match[1] : url;\n loadingModules.delete(name);\n parent.postMessage(\n {\n action: 'fetch_progress',\n args: { loaded: name, remaining: [...loadingModules] }\n },\n '*'\n );\n }\n }\n });\n observer.observe({ entryTypes: ['resource'] });\n\n const scriptEls = [];\n const loadingModules = new Set();\n\n // Queue for sequential eval execution\n let evalQueue = Promise.resolve();\n\n window.__vite_plugin_react_preamble_installed__ = {};\n window.__modules__ = {};\n window.__export__ = (mod, key, get) => {\n Object.defineProperty(mod, key, {\n enumerable: true,\n configurable: true,\n get\n });\n };\n window.__dynamic_import__ = key => {\n return Promise.resolve(window.__modules__[key]);\n };\n window.__render_complete__ = () => {\n parent.postMessage({ action: 'render_complete' }, '*');\n };\n\n async function processEval(ev) {\n const { cmd_id } = ev.data;\n const send_message = payload => parent.postMessage({ ...payload }, ev.origin);\n const send_reply = payload => send_message({ ...payload, cmd_id });\n const send_ok = () => send_reply({ action: 'cmd_ok' });\n const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });\n\n try {\n if (scriptEls.length) {\n scriptEls.forEach(el => {\n document.head.removeChild(el);\n });\n scriptEls.length = 0;\n }\n\n let { script: scripts } = ev.data.args;\n if (typeof scripts === 'string') scripts = [scripts];\n\n for (const script of scripts) {\n const scriptEl = document.createElement('script');\n scriptEl.setAttribute('type', 'module');\n // send ok in the module script to ensure sequential evaluation\n // of multiple proxy.eval() calls\n const done = new Promise(resolve => {\n window.__next__ = resolve;\n });\n scriptEl.innerHTML = script;\n document.head.appendChild(scriptEl);\n scriptEl.onerror = err => {\n send_error(err.message, err.stack);\n window.__next__?.(); // unblock queue on error\n };\n scriptEls.push(scriptEl);\n await done;\n }\n window.__next__ = undefined;\n send_ok();\n } catch (e) {\n send_error(e.message, e.stack);\n }\n }\n\n function handle_message(ev) {\n const { action, cmd_id } = ev.data;\n const send_message = payload => parent.postMessage({ ...payload }, ev.origin);\n const send_reply = payload => send_message({ ...payload, cmd_id });\n const send_ok = () => send_reply({ action: 'cmd_ok' });\n const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });\n\n if (action === 'eval') {\n // Queue eval requests to ensure sequential execution\n evalQueue = evalQueue.then(() => processEval(ev));\n return;\n }\n\n if (action === 'catch_clicks') {\n try {\n const top_origin = ev.origin;\n document.body.addEventListener('click', event => {\n if (event.which !== 1) return;\n if (event.metaKey || event.ctrlKey || event.shiftKey) return;\n if (event.defaultPrevented) return;\n\n // ensure target is a link\n let el = event.target;\n while (el && el.nodeName !== 'A') el = el.parentNode;\n if (!el || el.nodeName !== 'A') return;\n\n if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;\n\n event.preventDefault();\n\n if (el.href.startsWith(top_origin)) {\n const url = new URL(el.href);\n if (url.hash[0] === '#') {\n window.location.hash = url.hash;\n return;\n }\n }\n\n window.open(el.href, '_blank');\n });\n send_ok();\n } catch (e) {\n send_error(e.message, e.stack);\n }\n }\n }\n\n window.addEventListener('message', handle_message, false);\n\n window.onerror = function (msg, url, lineNo, columnNo, error) {\n try {\n parent.postMessage({ action: 'error', value: error }, '*');\n } catch (e) {\n parent.postMessage({ action: 'error', value: msg }, '*');\n }\n window.__next__?.(); // unblock eval queue on error\n };\n\n window.addEventListener('unhandledrejection', event => {\n if (event.reason?.message.includes('Cross-origin')) {\n event.preventDefault();\n return;\n }\n try {\n parent.postMessage({ action: 'unhandledrejection', value: event.reason }, '*');\n } catch (e) {\n parent.postMessage({ action: 'unhandledrejection', value: event.reason.message }, '*');\n }\n });\n\n let previous = { level: null, args: null };\n\n ['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach(level => {\n const original = console[level];\n console[level] = (...args) => {\n const msg = String(args[0]);\n try {\n const stringifiedArgs = stringify(args);\n if (previous.level === level && previous.args && previous.args === stringifiedArgs) {\n parent.postMessage({ action: 'console', level, args, duplicate: true }, '*');\n } else {\n previous = { level, args: stringifiedArgs };\n parent.postMessage({ action: 'console', level, args }, '*');\n }\n } catch (err) {\n parent.postMessage(\n {\n action: 'console',\n level,\n args: args.map(a => {\n return a instanceof Error ? a.message : String(a);\n })\n },\n '*'\n );\n }\n\n original(...args);\n };\n });\n\n [\n { method: 'group', action: 'console_group' },\n { method: 'groupEnd', action: 'console_group_end' },\n { method: 'groupCollapsed', action: 'console_group_collapsed' }\n ].forEach(group_action => {\n const original = console[group_action.method];\n console[group_action.method] = label => {\n parent.postMessage({ action: group_action.action, label }, '*');\n\n original(label);\n };\n });\n\n const timers = new Map();\n const original_time = console.time;\n const original_timelog = console.timeLog;\n const original_timeend = console.timeEnd;\n\n console.time = (label = 'default') => {\n original_time(label);\n timers.set(label, performance.now());\n };\n console.timeLog = (label = 'default') => {\n original_timelog(label);\n const now = performance.now();\n if (timers.has(label))\n parent.postMessage(\n {\n action: 'console',\n level: 'system-log',\n args: [`${label}: ${now - timers.get(label)}ms`]\n },\n '*'\n );\n else\n parent.postMessage(\n {\n action: 'console',\n level: 'system-warn',\n args: [`Timer '${label}' does not exist`]\n },\n '*'\n );\n };\n console.timeEnd = (label = 'default') => {\n original_timeend(label);\n const now = performance.now();\n if (timers.has(label))\n parent.postMessage(\n {\n action: 'console',\n level: 'system-log',\n args: [`${label}: ${now - timers.get(label)}ms`]\n },\n '*'\n );\n else\n parent.postMessage(\n {\n action: 'console',\n level: 'system-warn',\n args: [`Timer '${label}' does not exist`]\n },\n '*'\n );\n\n timers.delete(label);\n };\n\n const original_assert = console.assert;\n console.assert = (condition, ...args) => {\n if (condition) {\n const stack = new Error().stack;\n parent.postMessage({ action: 'console', level: 'assert', args, stack }, '*');\n }\n original_assert(condition, ...args);\n };\n\n const counter = new Map();\n const original_count = console.count;\n const original_countreset = console.countReset;\n\n console.count = (label = 'default') => {\n counter.set(label, (counter.get(label) || 0) + 1);\n parent.postMessage(\n {\n action: 'console',\n level: 'system-log',\n args: `${label}: ${counter.get(label)}`\n },\n '*'\n );\n original_count(label);\n };\n\n console.countReset = (label = 'default') => {\n if (counter.has(label)) counter.set(label, 0);\n else\n parent.postMessage(\n {\n action: 'console',\n level: 'system-warn',\n args: `Count for '${label}' does not exist`\n },\n '*'\n );\n\n original_countreset(label);\n };\n\n const original_trace = console.trace;\n\n console.trace = (...args) => {\n const stack = new Error().stack;\n parent.postMessage({ action: 'console', level: 'trace', args, stack }, '*');\n original_trace(...args);\n };\n\n function stringify(args) {\n try {\n return JSON.stringify(args);\n } catch (error) {\n return null;\n }\n }\n })();\n <\/script>\n </head>\n <body>\n <div id=\"root\"></div>\n </body>\n</html>\n";
|
|
1193
|
+
var srcdoc_default = "<!doctype html>\n<html class=\"DEFAULT_THEME\" style=\"color-scheme: DEFAULT_THEME\">\n <head>\n <script type=\"importmap\">\n <!-- IMPORT_MAP -->\n <\/script>\n <!-- PRESET_TAG -->\n <script>\n (() => {\n // Monitor module loading via PerformanceObserver\n const observer = new PerformanceObserver(list => {\n for (const entry of list.getEntries()) {\n if (entry.initiatorType === 'script' || entry.initiatorType === 'link') {\n const url = entry.name;\n // Extract package name from URL (e.g., esm.sh/react@18.2.0/jsx-runtime -> react@18.2.0/jsx-runtime)\n const match = url.match(/esm\\.sh\\/([^\\s?#]+)/);\n const name = match ? match[1] : url;\n loadingModules.delete(name);\n parent.postMessage(\n {\n action: 'fetch_progress',\n args: { loaded: name, remaining: [...loadingModules] }\n },\n '*'\n );\n }\n }\n });\n observer.observe({ entryTypes: ['resource'] });\n\n const scriptEls = [];\n const loadingModules = new Set();\n\n // Queue for sequential eval execution\n let evalQueue = Promise.resolve();\n\n window.__vite_plugin_react_preamble_installed__ = {};\n window.__modules__ = {};\n window.__export__ = (mod, key, get) => {\n Object.defineProperty(mod, key, {\n enumerable: true,\n configurable: true,\n get\n });\n };\n window.__dynamic_import__ = key => {\n return Promise.resolve(window.__modules__[key]);\n };\n window.__render_complete__ = () => {\n parent.postMessage({ action: 'render_complete' }, '*');\n };\n\n async function processEval(ev) {\n const { cmd_id } = ev.data;\n const send_message = payload => parent.postMessage({ ...payload }, ev.origin);\n const send_reply = payload => send_message({ ...payload, cmd_id });\n const send_ok = () => send_reply({ action: 'cmd_ok' });\n const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });\n\n try {\n if (scriptEls.length) {\n scriptEls.forEach(el => {\n document.head.removeChild(el);\n });\n scriptEls.length = 0;\n }\n\n let { script: scripts } = ev.data.args;\n if (typeof scripts === 'string') scripts = [scripts];\n\n for (const script of scripts) {\n const scriptEl = document.createElement('script');\n scriptEl.setAttribute('type', 'module');\n // send ok in the module script to ensure sequential evaluation\n // of multiple proxy.eval() calls\n const done = new Promise(resolve => {\n window.__next__ = resolve;\n });\n scriptEl.innerHTML = script;\n document.head.appendChild(scriptEl);\n scriptEl.onerror = err => {\n send_error(err.message, err.stack);\n window.__next__?.(); // unblock queue on error\n };\n scriptEls.push(scriptEl);\n await done;\n }\n window.__next__ = undefined;\n send_ok();\n } catch (e) {\n send_error(e.message, e.stack);\n }\n }\n\n function handle_message(ev) {\n const { action, cmd_id } = ev.data;\n const send_message = payload => parent.postMessage({ ...payload }, ev.origin);\n const send_reply = payload => send_message({ ...payload, cmd_id });\n const send_ok = () => send_reply({ action: 'cmd_ok' });\n const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });\n\n if (action === 'eval') {\n // Queue eval requests to ensure sequential execution\n evalQueue = evalQueue.then(() => processEval(ev));\n return;\n }\n\n if (action === 'catch_clicks') {\n try {\n const top_origin = ev.origin;\n document.body.addEventListener('click', event => {\n if (event.which !== 1) return;\n if (event.metaKey || event.ctrlKey || event.shiftKey) return;\n if (event.defaultPrevented) return;\n\n // ensure target is a link\n let el = event.target;\n while (el && el.nodeName !== 'A') el = el.parentNode;\n if (!el || el.nodeName !== 'A') return;\n\n if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;\n\n event.preventDefault();\n\n if (el.href.startsWith(top_origin)) {\n const url = new URL(el.href);\n if (url.hash[0] === '#') {\n window.location.hash = url.hash;\n return;\n }\n }\n\n window.open(el.href, '_blank');\n });\n send_ok();\n } catch (e) {\n send_error(e.message, e.stack);\n }\n }\n }\n\n window.addEventListener('message', handle_message, false);\n\n window.onerror = function (msg, url, lineNo, columnNo, error) {\n try {\n parent.postMessage({ action: 'error', value: error }, '*');\n } catch (e) {\n parent.postMessage({ action: 'error', value: msg }, '*');\n }\n window.__next__?.(); // unblock eval queue on error\n };\n\n window.addEventListener('unhandledrejection', event => {\n if (event.reason?.message.includes('Cross-origin')) {\n event.preventDefault();\n return;\n }\n try {\n parent.postMessage({ action: 'unhandledrejection', value: event.reason }, '*');\n } catch (e) {\n parent.postMessage({ action: 'unhandledrejection', value: event.reason.message }, '*');\n }\n });\n\n let previous = { level: null, args: null };\n\n ['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach(level => {\n const original = console[level];\n console[level] = (...args) => {\n const msg = String(args[0]);\n try {\n const stringifiedArgs = stringify(args);\n if (previous.level === level && previous.args && previous.args === stringifiedArgs) {\n parent.postMessage({ action: 'console', level, args, duplicate: true }, '*');\n } else {\n previous = { level, args: stringifiedArgs };\n parent.postMessage({ action: 'console', level, args }, '*');\n }\n } catch (err) {\n parent.postMessage(\n {\n action: 'console',\n level,\n args: args.map(a => {\n return a instanceof Error ? a.message : String(a);\n })\n },\n '*'\n );\n }\n\n original(...args);\n };\n });\n\n [\n { method: 'group', action: 'console_group' },\n { method: 'groupEnd', action: 'console_group_end' },\n { method: 'groupCollapsed', action: 'console_group_collapsed' }\n ].forEach(group_action => {\n const original = console[group_action.method];\n console[group_action.method] = label => {\n parent.postMessage({ action: group_action.action, label }, '*');\n\n original(label);\n };\n });\n\n const timers = new Map();\n const original_time = console.time;\n const original_timelog = console.timeLog;\n const original_timeend = console.timeEnd;\n\n console.time = (label = 'default') => {\n original_time(label);\n timers.set(label, performance.now());\n };\n console.timeLog = (label = 'default') => {\n original_timelog(label);\n const now = performance.now();\n if (timers.has(label))\n parent.postMessage(\n {\n action: 'console',\n level: 'system-log',\n args: [`${label}: ${now - timers.get(label)}ms`]\n },\n '*'\n );\n else\n parent.postMessage(\n {\n action: 'console',\n level: 'system-warn',\n args: [`Timer '${label}' does not exist`]\n },\n '*'\n );\n };\n console.timeEnd = (label = 'default') => {\n original_timeend(label);\n const now = performance.now();\n if (timers.has(label))\n parent.postMessage(\n {\n action: 'console',\n level: 'system-log',\n args: [`${label}: ${now - timers.get(label)}ms`]\n },\n '*'\n );\n else\n parent.postMessage(\n {\n action: 'console',\n level: 'system-warn',\n args: [`Timer '${label}' does not exist`]\n },\n '*'\n );\n\n timers.delete(label);\n };\n\n const original_assert = console.assert;\n console.assert = (condition, ...args) => {\n if (condition) {\n const stack = new Error().stack;\n parent.postMessage({ action: 'console', level: 'assert', args, stack }, '*');\n }\n original_assert(condition, ...args);\n };\n\n const counter = new Map();\n const original_count = console.count;\n const original_countreset = console.countReset;\n\n console.count = (label = 'default') => {\n counter.set(label, (counter.get(label) || 0) + 1);\n parent.postMessage(\n {\n action: 'console',\n level: 'system-log',\n args: `${label}: ${counter.get(label)}`\n },\n '*'\n );\n original_count(label);\n };\n\n console.countReset = (label = 'default') => {\n if (counter.has(label)) counter.set(label, 0);\n else\n parent.postMessage(\n {\n action: 'console',\n level: 'system-warn',\n args: `Count for '${label}' does not exist`\n },\n '*'\n );\n\n original_countreset(label);\n };\n\n const original_trace = console.trace;\n\n console.trace = (...args) => {\n const stack = new Error().stack;\n parent.postMessage({ action: 'console', level: 'trace', args, stack }, '*');\n original_trace(...args);\n };\n\n function stringify(args) {\n try {\n return JSON.stringify(args);\n } catch (error) {\n return null;\n }\n }\n })();\n <\/script>\n </head>\n <body>\n <div id=\"root\"></div>\n </body>\n</html>\n";
|
|
1184
1194
|
|
|
1185
1195
|
//#endregion
|
|
1186
1196
|
//#region src/lib/preview-proxy/index.ts
|
|
@@ -1307,6 +1317,8 @@ var PreviewProxy = class {
|
|
|
1307
1317
|
script.type = "importmap";
|
|
1308
1318
|
script.textContent = JSON.stringify({ imports: imports || {} });
|
|
1309
1319
|
doc.head.insertBefore(script, doc.head.firstChild);
|
|
1320
|
+
console.log(doc.querySelector("html").outerHTML);
|
|
1321
|
+
this.iframe.src = URL.createObjectURL(new Blob([doc.querySelector("html").outerHTML], { type: "text/html" }));
|
|
1310
1322
|
}
|
|
1311
1323
|
};
|
|
1312
1324
|
function usePreview(options) {
|
|
@@ -1315,6 +1327,7 @@ function usePreview(options) {
|
|
|
1315
1327
|
const proxyRef = useRef(void 0);
|
|
1316
1328
|
const iframeRef = useRef(null);
|
|
1317
1329
|
const readyRef = useRef(Promise.withResolvers());
|
|
1330
|
+
const isFirst = useRef(true);
|
|
1318
1331
|
const preview = async (...code) => {
|
|
1319
1332
|
try {
|
|
1320
1333
|
setRunning(true);
|
|
@@ -1322,13 +1335,23 @@ function usePreview(options) {
|
|
|
1322
1335
|
await proxyRef.current?.eval(code);
|
|
1323
1336
|
} catch (error) {
|
|
1324
1337
|
onError?.(error);
|
|
1338
|
+
} finally {
|
|
1339
|
+
isFirst.current = false;
|
|
1325
1340
|
}
|
|
1326
1341
|
};
|
|
1342
|
+
useUpdateEffect(() => {
|
|
1343
|
+
if (isFirst.current) return;
|
|
1344
|
+
readyRef.current.promise.then(() => {
|
|
1345
|
+
proxyRef.current?.setImportMap(imports);
|
|
1346
|
+
});
|
|
1347
|
+
}, [JSON.stringify(imports)]);
|
|
1327
1348
|
useEffect(() => {
|
|
1328
1349
|
if (iframeRef.current) {
|
|
1329
1350
|
const proxy = new PreviewProxy({
|
|
1330
1351
|
root: iframeRef.current,
|
|
1331
1352
|
defaultTheme: theme,
|
|
1353
|
+
defaultImports: imports,
|
|
1354
|
+
defaultPresets: presets,
|
|
1332
1355
|
handlers: {
|
|
1333
1356
|
on_console: onConsole,
|
|
1334
1357
|
on_fetch_progress: onFetchProgress,
|
|
@@ -1362,11 +1385,6 @@ function usePreview(options) {
|
|
|
1362
1385
|
proxyRef.current?.injectTags(presets);
|
|
1363
1386
|
});
|
|
1364
1387
|
}, [presets?.join("\n")]);
|
|
1365
|
-
useEffect(() => {
|
|
1366
|
-
readyRef.current.promise.then(() => {
|
|
1367
|
-
proxyRef.current?.setImportMap(imports);
|
|
1368
|
-
});
|
|
1369
|
-
}, [JSON.stringify(imports)]);
|
|
1370
1388
|
return {
|
|
1371
1389
|
iframeRef,
|
|
1372
1390
|
proxyRef,
|
|
@@ -1574,6 +1592,41 @@ function useTailwindCSS() {
|
|
|
1574
1592
|
|
|
1575
1593
|
//#endregion
|
|
1576
1594
|
//#region src/components/preview/index.tsx
|
|
1595
|
+
const PREFLIGHT_STYLE = `
|
|
1596
|
+
:root {
|
|
1597
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
1598
|
+
font-size: 14px;
|
|
1599
|
+
line-height: 1.5;
|
|
1600
|
+
font-synthesis: none;
|
|
1601
|
+
text-rendering: optimizeLegibility;
|
|
1602
|
+
-webkit-font-smoothing: antialiased;
|
|
1603
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
html,
|
|
1607
|
+
body {
|
|
1608
|
+
margin: 0;
|
|
1609
|
+
overflow: hidden;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
html {
|
|
1613
|
+
width: 100vw;
|
|
1614
|
+
height: 100vh;
|
|
1615
|
+
padding: 0;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
body {
|
|
1619
|
+
height: 100%;
|
|
1620
|
+
padding: 12px;
|
|
1621
|
+
overflow: auto;
|
|
1622
|
+
box-sizing: border-box;
|
|
1623
|
+
display: flex;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
#root {
|
|
1627
|
+
margin: auto;
|
|
1628
|
+
}
|
|
1629
|
+
`;
|
|
1577
1630
|
/**
|
|
1578
1631
|
* CodesparkPreview - A sandboxed preview component that renders compiled code in an iframe.
|
|
1579
1632
|
*
|
|
@@ -1584,20 +1637,22 @@ function useTailwindCSS() {
|
|
|
1584
1637
|
function CodesparkPreview(props) {
|
|
1585
1638
|
const { imports: globalImports, theme: globalTheme } = useConfig();
|
|
1586
1639
|
const { workspace: contextWorkspace, imports: contextImports, theme: contextTheme, framework: contextFramework } = useCodespark() || {};
|
|
1587
|
-
const { code = "", framework = contextFramework, className, tailwindcss: tailwindcss$1 = true, imports, theme = contextTheme ?? globalTheme ?? "light", children, height
|
|
1640
|
+
const { code = "", framework = contextFramework, className, tailwindcss: tailwindcss$1 = true, preflight = true, imports, theme = contextTheme ?? globalTheme ?? "light", children, height, onError, onLoad, onRendered, onConsole } = props;
|
|
1588
1641
|
const { compiled, vendor, workspace } = useWorkspace(props.workspace ?? contextWorkspace ?? new Workspace({
|
|
1589
1642
|
entry: "./App.tsx",
|
|
1590
1643
|
files: { "./App.tsx": code },
|
|
1591
1644
|
framework
|
|
1592
1645
|
}));
|
|
1593
1646
|
const { mount: mountTailwind, unmount: unmountTailwind } = useTailwindCSS();
|
|
1647
|
+
const injections = useInjections(children);
|
|
1594
1648
|
const { iframeRef, readyRef, preview, running } = usePreview({
|
|
1595
1649
|
theme,
|
|
1596
1650
|
presets: [
|
|
1597
|
-
|
|
1651
|
+
preflight ? `<style>${PREFLIGHT_STYLE}</style>` : "",
|
|
1652
|
+
...injections,
|
|
1598
1653
|
...vendor.styles.map(({ content, attributes }) => `<style${serializeAttributes(attributes)}>${content}</style>`),
|
|
1599
1654
|
...vendor.scripts.map(({ content, attributes }) => `<script${serializeAttributes(attributes)}>${content}<\/script>`)
|
|
1600
|
-
],
|
|
1655
|
+
].filter(Boolean),
|
|
1601
1656
|
imports: {
|
|
1602
1657
|
...vendor.imports,
|
|
1603
1658
|
...globalImports,
|
|
@@ -1627,7 +1682,7 @@ function CodesparkPreview(props) {
|
|
|
1627
1682
|
preview(compiled);
|
|
1628
1683
|
}, [compiled]);
|
|
1629
1684
|
return /* @__PURE__ */ jsxs("div", {
|
|
1630
|
-
className: cn("relative flex items-center justify-center", className),
|
|
1685
|
+
className: cn("relative flex h-[200px] items-center justify-center", className),
|
|
1631
1686
|
style: { height },
|
|
1632
1687
|
children: [running ? /* @__PURE__ */ jsx("div", {
|
|
1633
1688
|
className: "absolute right-2 bottom-2 z-10 h-8 w-8 **:box-border",
|
|
@@ -1663,7 +1718,7 @@ registerFramework(react);
|
|
|
1663
1718
|
* Supports both single-file mode (via `code` prop) and multi-file mode (via `files` prop).
|
|
1664
1719
|
*/
|
|
1665
1720
|
function Codespark(props) {
|
|
1666
|
-
const { code, files, name = "./App.tsx", theme, editor, framework = "react", showEditor = true, showPreview = true, showFileExplorer = true, readonly: readOnly, className, toolbox, tailwindcss: tailwindcss$1, onConsole, onError, children, defaultExpanded, getWorkspace, editorHeight, previewHeight, orientation = "vertical" } = props;
|
|
1721
|
+
const { code, files, name = "./App.tsx", theme, editor, framework = "react", showEditor = true, showPreview = true, showFileExplorer = true, readonly: readOnly, className, toolbox, tailwindcss: tailwindcss$1, onConsole, onError, children, defaultExpanded, getWorkspace, editorHeight, previewHeight, orientation = "vertical", preflight } = props;
|
|
1667
1722
|
const { workspace, fileTree, compileError } = useWorkspace({
|
|
1668
1723
|
entry: name,
|
|
1669
1724
|
files: files ?? { [name]: code || "" },
|
|
@@ -1723,6 +1778,7 @@ function Codespark(props) {
|
|
|
1723
1778
|
setRuntimeError(error);
|
|
1724
1779
|
},
|
|
1725
1780
|
height: previewHeight,
|
|
1781
|
+
preflight,
|
|
1726
1782
|
children
|
|
1727
1783
|
}) : null, runtimeError ? /* @__PURE__ */ jsxs("div", {
|
|
1728
1784
|
className: "bg-background absolute inset-0 z-20 overflow-auto p-6",
|
package/dist/monaco.js
CHANGED
|
@@ -54,7 +54,72 @@ var MonacoEditorAdapter = class {
|
|
|
54
54
|
//#region src/components/editor/monaco/index.tsx
|
|
55
55
|
const addedLibs = /* @__PURE__ */ new Set();
|
|
56
56
|
const dtsCacheMap = /* @__PURE__ */ new Map();
|
|
57
|
-
const
|
|
57
|
+
const suggestionPathsMap = /* @__PURE__ */ new Map();
|
|
58
|
+
let suggestionProvider = null;
|
|
59
|
+
const getRelativePath = (from, to) => {
|
|
60
|
+
const fromParts = from.replace(/^\.\//, "").split("/").slice(0, -1);
|
|
61
|
+
const toParts = to.replace(/^\.\//, "").split("/");
|
|
62
|
+
let commonLength = 0;
|
|
63
|
+
while (commonLength < fromParts.length && commonLength < toParts.length - 1 && fromParts[commonLength] === toParts[commonLength]) commonLength++;
|
|
64
|
+
const upCount = fromParts.length - commonLength;
|
|
65
|
+
return (upCount > 0 ? Array(upCount).fill("..") : ["."]).concat(toParts.slice(commonLength)).join("/");
|
|
66
|
+
};
|
|
67
|
+
const ensureSuggestionProvider = (monacoInst) => {
|
|
68
|
+
if (suggestionProvider) return;
|
|
69
|
+
suggestionProvider = monacoInst.languages.registerCompletionItemProvider([
|
|
70
|
+
"typescript",
|
|
71
|
+
"typescriptreact",
|
|
72
|
+
"javascript",
|
|
73
|
+
"javascriptreact"
|
|
74
|
+
], {
|
|
75
|
+
triggerCharacters: [
|
|
76
|
+
"/",
|
|
77
|
+
"'",
|
|
78
|
+
"\""
|
|
79
|
+
],
|
|
80
|
+
provideCompletionItems(model, position) {
|
|
81
|
+
const importMatch = model.getValueInRange({
|
|
82
|
+
startLineNumber: position.lineNumber,
|
|
83
|
+
startColumn: 1,
|
|
84
|
+
endLineNumber: position.lineNumber,
|
|
85
|
+
endColumn: position.column
|
|
86
|
+
}).match(/(?:import\s+.*?\s+from\s+|import\s+)(['"])(\.[^'"]*?)$/);
|
|
87
|
+
if (!importMatch) return { suggestions: [] };
|
|
88
|
+
const editorId = model.uri.path.split("/")[1];
|
|
89
|
+
const paths = suggestionPathsMap.get(editorId) || [];
|
|
90
|
+
const typedPath = importMatch[2];
|
|
91
|
+
const filePaths = paths.filter((p) => !p.endsWith("/"));
|
|
92
|
+
const suggestions = [];
|
|
93
|
+
const addedPaths = /* @__PURE__ */ new Set();
|
|
94
|
+
const currentPath = model.uri.path.replace(/^\/[^/]+\//, "./");
|
|
95
|
+
for (const filePath of filePaths) {
|
|
96
|
+
if (filePath === currentPath) continue;
|
|
97
|
+
const relativePath = getRelativePath(currentPath, filePath);
|
|
98
|
+
if (!relativePath.startsWith(typedPath)) continue;
|
|
99
|
+
let displayPath = relativePath;
|
|
100
|
+
if (/\.(tsx?|jsx?)$/.test(displayPath)) displayPath = displayPath.replace(/\.(tsx?|jsx?)$/, "");
|
|
101
|
+
if (!addedPaths.has(displayPath)) {
|
|
102
|
+
addedPaths.add(displayPath);
|
|
103
|
+
suggestions.push({
|
|
104
|
+
label: displayPath,
|
|
105
|
+
kind: monacoInst.languages.CompletionItemKind.File,
|
|
106
|
+
insertText: displayPath.slice(typedPath.length),
|
|
107
|
+
range: {
|
|
108
|
+
startLineNumber: position.lineNumber,
|
|
109
|
+
startColumn: position.column,
|
|
110
|
+
endLineNumber: position.lineNumber,
|
|
111
|
+
endColumn: position.column
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { suggestions };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
let setupPromise = null;
|
|
121
|
+
const setup = () => setupPromise ??= setupOnce();
|
|
122
|
+
const setupOnce = async () => {
|
|
58
123
|
if (typeof window === "undefined") return;
|
|
59
124
|
const [mod, highlighter] = await Promise.all([import("@monaco-editor/react"), createHighlighterCore({
|
|
60
125
|
themes: [import("shiki/themes/vitesse-light.mjs"), import("shiki/themes/vitesse-dark.mjs")],
|
|
@@ -95,7 +160,8 @@ const setup = async () => {
|
|
|
95
160
|
moduleResolution: Monaco$1.typescript.ModuleResolutionKind.NodeJs,
|
|
96
161
|
module: Monaco$1.typescript.ModuleKind.ESNext,
|
|
97
162
|
noEmit: true,
|
|
98
|
-
jsx: Monaco$1.typescript.JsxEmit.
|
|
163
|
+
jsx: Monaco$1.typescript.JsxEmit.ReactJSX,
|
|
164
|
+
jsxImportSource: "react",
|
|
99
165
|
esModuleInterop: true
|
|
100
166
|
});
|
|
101
167
|
Monaco$1.languages.registerDocumentFormattingEditProvider("typescript", { async provideDocumentFormattingEdits(model, options) {
|
|
@@ -172,7 +238,14 @@ const Monaco = {
|
|
|
172
238
|
Object.entries(dts).forEach(([module, content]) => {
|
|
173
239
|
if (addedLibs.has(module)) return;
|
|
174
240
|
if (module.startsWith("http://") || module.startsWith("https://")) monacoInstance.typescript.typescriptDefaults.addExtraLib(`declare module '${module}' { ${content} }`, module);
|
|
175
|
-
else
|
|
241
|
+
else {
|
|
242
|
+
const parts = module.split("/");
|
|
243
|
+
const isScoped = module.startsWith("@");
|
|
244
|
+
const packageName = isScoped ? parts.slice(0, 2).join("/") : parts[0];
|
|
245
|
+
const subpath = isScoped ? parts.slice(2).join("/") : parts.slice(1).join("/");
|
|
246
|
+
const filePath = subpath ? `file:///node_modules/${packageName}/${subpath}.d.ts` : `file:///node_modules/${module}/index.d.ts`;
|
|
247
|
+
monacoInstance.typescript.typescriptDefaults.addExtraLib(content || `declare module '${module}'`, filePath);
|
|
248
|
+
}
|
|
176
249
|
addedLibs.add(module);
|
|
177
250
|
});
|
|
178
251
|
};
|
|
@@ -196,63 +269,9 @@ const Monaco = {
|
|
|
196
269
|
}
|
|
197
270
|
});
|
|
198
271
|
};
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const toParts = to.replace(/^\.\//, "").split("/");
|
|
203
|
-
let commonLength = 0;
|
|
204
|
-
while (commonLength < fromParts.length && commonLength < toParts.length - 1 && fromParts[commonLength] === toParts[commonLength]) commonLength++;
|
|
205
|
-
const upCount = fromParts.length - commonLength;
|
|
206
|
-
return (upCount > 0 ? Array(upCount).fill("..") : ["."]).concat(toParts.slice(commonLength)).join("/");
|
|
207
|
-
};
|
|
208
|
-
return monacoInstance.languages.registerCompletionItemProvider([
|
|
209
|
-
"typescript",
|
|
210
|
-
"typescriptreact",
|
|
211
|
-
"javascript",
|
|
212
|
-
"javascriptreact"
|
|
213
|
-
], {
|
|
214
|
-
triggerCharacters: [
|
|
215
|
-
"/",
|
|
216
|
-
"'",
|
|
217
|
-
"\""
|
|
218
|
-
],
|
|
219
|
-
provideCompletionItems(model, position) {
|
|
220
|
-
const importMatch = model.getValueInRange({
|
|
221
|
-
startLineNumber: position.lineNumber,
|
|
222
|
-
startColumn: 1,
|
|
223
|
-
endLineNumber: position.lineNumber,
|
|
224
|
-
endColumn: position.column
|
|
225
|
-
}).match(/(?:import\s+.*?\s+from\s+|import\s+)(['"])(\.[^'"]*?)$/);
|
|
226
|
-
if (!importMatch) return { suggestions: [] };
|
|
227
|
-
const typedPath = importMatch[2];
|
|
228
|
-
const filePaths = paths.filter((p) => !p.endsWith("/"));
|
|
229
|
-
const suggestions = [];
|
|
230
|
-
const addedPaths = /* @__PURE__ */ new Set();
|
|
231
|
-
const currentPath = model.uri.path.replace(/^\/[^/]+\//, "./");
|
|
232
|
-
for (const filePath of filePaths) {
|
|
233
|
-
if (filePath === currentPath) continue;
|
|
234
|
-
const relativePath = getRelativePath(currentPath, filePath);
|
|
235
|
-
if (!relativePath.startsWith(typedPath)) continue;
|
|
236
|
-
let displayPath = relativePath;
|
|
237
|
-
if (/\.(tsx?|jsx?)$/.test(displayPath)) displayPath = displayPath.replace(/\.(tsx?|jsx?)$/, "");
|
|
238
|
-
if (!addedPaths.has(displayPath)) {
|
|
239
|
-
addedPaths.add(displayPath);
|
|
240
|
-
suggestions.push({
|
|
241
|
-
label: displayPath,
|
|
242
|
-
kind: monacoInstance.languages.CompletionItemKind.File,
|
|
243
|
-
insertText: displayPath.slice(typedPath.length),
|
|
244
|
-
range: {
|
|
245
|
-
startLineNumber: position.lineNumber,
|
|
246
|
-
startColumn: position.column,
|
|
247
|
-
endLineNumber: position.lineNumber,
|
|
248
|
-
endColumn: position.column
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return { suggestions };
|
|
254
|
-
}
|
|
255
|
-
});
|
|
272
|
+
const updateSuggestionPaths = (editorId, paths) => {
|
|
273
|
+
suggestionPathsMap.set(editorId, paths);
|
|
274
|
+
ensureSuggestionProvider(monacoInstance);
|
|
256
275
|
};
|
|
257
276
|
useEffect(() => {
|
|
258
277
|
(async () => {
|
|
@@ -265,8 +284,10 @@ const Monaco = {
|
|
|
265
284
|
useEffect(() => {
|
|
266
285
|
if (typeof window === "undefined" || !monacoInstance || !id) return;
|
|
267
286
|
createModels(files);
|
|
268
|
-
|
|
269
|
-
return () =>
|
|
287
|
+
updateSuggestionPaths(id, Object.keys(files || {}));
|
|
288
|
+
return () => {
|
|
289
|
+
suggestionPathsMap.delete(id);
|
|
290
|
+
};
|
|
270
291
|
}, [files, monacoInstance]);
|
|
271
292
|
useEffect(() => {
|
|
272
293
|
if (typeof window === "undefined" || !monacoInstance) return;
|