@codespark/react 1.0.0 → 1.0.2

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 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: name,
480
- files: { [name]: packedCode }
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 = 200, onError, onLoad, onRendered, onConsole } = props;
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
- ...useInjections(children),
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 setup = async () => {
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.Preserve,
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 monacoInstance.typescript.typescriptDefaults.addExtraLib(content || `declare module '${module}'`, `file:///node_modules/${module}/index.d.ts`);
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 addSuggestions = (paths) => {
200
- const getRelativePath = (from, to) => {
201
- const fromParts = from.replace(/^\.\//, "").split("/").slice(0, -1);
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
- const provider = addSuggestions(Object.keys(files || {}));
269
- return () => provider?.dispose();
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codespark/react",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "React components for codespark ecosystem",
6
6
  "keywords": [