@ox-content/code-play 3.0.0-alpha.1 → 3.0.0-alpha.11

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/browser.mjs CHANGED
@@ -372,6 +372,9 @@ function escapeHtml(value) {
372
372
  }
373
373
  });
374
374
  }
375
+ function escapeAttribute(value) {
376
+ return escapeHtml(value).replace(/`/g, "`");
377
+ }
375
378
  //#endregion
376
379
  //#region src/timing.ts
377
380
  var PhaseTracker = class {
@@ -692,10 +695,115 @@ function buildJavaScriptSandboxDocument(code, messageId) {
692
695
  })();
693
696
  <\/script></body></html>`;
694
697
  }
698
+ function buildJavaScriptWorkerSource() {
699
+ return `
700
+ (function () {
701
+ function format(args) {
702
+ return Array.prototype.map.call(args, function (value) {
703
+ if (typeof value === "string") return value;
704
+ if (value === undefined) return "undefined";
705
+ if (value === null) return "null";
706
+ try { return JSON.stringify(value); } catch (error) { return String(value); }
707
+ }).join(" ") + "\\n";
708
+ }
709
+ self.onmessage = function (event) {
710
+ var data = event.data || {};
711
+ if (!data.id) return;
712
+ var stdout = [];
713
+ var stderr = [];
714
+ var consoleLike = {
715
+ log: function () { stdout.push(format(arguments)); },
716
+ info: function () { stdout.push(format(arguments)); },
717
+ warn: function () { stderr.push(format(arguments)); },
718
+ error: function () { stderr.push(format(arguments)); }
719
+ };
720
+ try {
721
+ var run = new Function("console", '"use strict";\\n' + String(data.code || ""));
722
+ var value = run(consoleLike);
723
+ self.postMessage({
724
+ id: data.id,
725
+ stdout: stdout,
726
+ stderr: stderr,
727
+ value: value === undefined ? undefined : String(value)
728
+ });
729
+ } catch (error) {
730
+ var message = error && error.message ? String(error.message) : String(error);
731
+ self.postMessage({ id: data.id, stdout: stdout, stderr: stderr, error: message });
732
+ }
733
+ };
734
+ })();
735
+ `;
736
+ }
695
737
  function applySandboxStreams(stdio, message) {
696
738
  for (const text of message.stdout ?? []) stdio.push("stdout", text);
697
739
  for (const text of message.stderr ?? []) stdio.push("stderr", text);
698
740
  }
741
+ var WORKER_UNAVAILABLE_CODE = "ERR_SCRIPT_WORKER_UNAVAILABLE";
742
+ function workerUnavailableError(error) {
743
+ const message = error instanceof Error && error.message ? error.message : "JavaScript worker sandbox is unavailable.";
744
+ return Object.assign(new Error(message), { code: WORKER_UNAVAILABLE_CODE });
745
+ }
746
+ function isSandboxWorkerUnavailable(error) {
747
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === WORKER_UNAVAILABLE_CODE);
748
+ }
749
+ function canUseSandboxWorker() {
750
+ return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function";
751
+ }
752
+ async function executeInSandboxWorker(code, timeoutMs, stdio, signal) {
753
+ if (!canUseSandboxWorker()) throw workerUnavailableError(/* @__PURE__ */ new Error("JavaScript worker sandbox needs Worker, Blob, and object URLs."));
754
+ if (signal?.aborted) throw abortError();
755
+ const messageId = `ox-code-play-${Math.random().toString(36).slice(2)}`;
756
+ const url = URL.createObjectURL(new Blob([buildJavaScriptWorkerSource()], { type: "text/javascript" }));
757
+ let worker;
758
+ try {
759
+ worker = new Worker(url);
760
+ } catch (error) {
761
+ URL.revokeObjectURL(url);
762
+ throw workerUnavailableError(error);
763
+ }
764
+ return new Promise((resolve, reject) => {
765
+ let settled = false;
766
+ const cleanup = () => {
767
+ if (settled) return;
768
+ settled = true;
769
+ clearTimeout(timer);
770
+ signal?.removeEventListener("abort", onAbort);
771
+ worker.onmessage = null;
772
+ worker.onerror = null;
773
+ worker.terminate();
774
+ URL.revokeObjectURL(url);
775
+ };
776
+ const onAbort = () => {
777
+ cleanup();
778
+ reject(abortError());
779
+ };
780
+ const onMessage = (event) => {
781
+ if (event.data?.id !== messageId) return;
782
+ cleanup();
783
+ applySandboxStreams(stdio, event.data);
784
+ if (event.data.error) {
785
+ reject(new Error(event.data.error));
786
+ return;
787
+ }
788
+ resolve(event.data.value);
789
+ };
790
+ const onError = (event) => {
791
+ cleanup();
792
+ reject(workerUnavailableError(new Error(event.message || "JavaScript worker sandbox failed.")));
793
+ };
794
+ const timer = setTimeout(() => {
795
+ cleanup();
796
+ reject(Object.assign(/* @__PURE__ */ new Error("JavaScript execution timed out."), { code: "ERR_SCRIPT_EXECUTION_TIMEOUT" }));
797
+ }, timeoutMs);
798
+ worker.onmessage = onMessage;
799
+ worker.onerror = onError;
800
+ signal?.addEventListener("abort", onAbort, { once: true });
801
+ worker.postMessage({
802
+ id: messageId,
803
+ code
804
+ });
805
+ });
806
+ }
699
807
  async function executeInSandboxIframe(code, timeoutMs, stdio, signal) {
700
808
  if (typeof document === "undefined" || typeof window === "undefined") throw new Error("JavaScript sandbox iframe needs a document.");
701
809
  if (signal?.aborted) throw abortError();
@@ -747,24 +855,23 @@ async function runJavaScript(request) {
747
855
  const tracker = new PhaseTracker();
748
856
  tracker.start("execute", "Execute");
749
857
  const stdio = new StdioBuffer(tracker.startedAt);
750
- const provenance = { execute: {
751
- host: "local",
752
- runtime: hasNodeVm() ? "node:vm" : "iframe",
753
- sandbox: hasNodeVm() ? "vm" : "srcdoc"
754
- } };
858
+ const runtime = currentJavaScriptRuntime();
859
+ let executedRuntime = runtime;
755
860
  try {
756
- const value = await executeScript(request.code, request.timeoutMs, stdio, request.signal);
861
+ const result = await executeScriptWithRuntime(request.code, request.timeoutMs, stdio, request.signal, runtime);
862
+ executedRuntime = result.runtime;
757
863
  tracker.stop();
758
864
  return {
759
865
  status: "ok",
760
866
  stdio: stdio.snapshot(),
761
867
  diagnostics: [],
762
- provenance,
868
+ provenance: { execute: javascriptRuntimeProvenance(executedRuntime) },
763
869
  timing: tracker.report(),
764
- value: value === void 0 ? void 0 : String(value)
870
+ value: result.value === void 0 ? void 0 : String(result.value)
765
871
  };
766
872
  } catch (error) {
767
873
  if (isAbortError(error) || request.signal?.aborted) throw error;
874
+ executedRuntime = executionRuntimeFromError(error) ?? executedRuntime;
768
875
  tracker.stop();
769
876
  const diagnostic = toDiagnostic(error);
770
877
  stdio.push("stderr", `${diagnostic.message}\n`);
@@ -772,12 +879,30 @@ async function runJavaScript(request) {
772
879
  status: isTimeout(error) ? "timeout" : "error",
773
880
  stdio: stdio.snapshot(),
774
881
  diagnostics: [diagnostic],
775
- provenance,
882
+ provenance: { execute: javascriptRuntimeProvenance(executedRuntime) },
776
883
  timing: tracker.report()
777
884
  };
778
885
  }
779
886
  }
780
- async function executeScript(code, timeoutMs, stdio, signal) {
887
+ async function executeScriptWithRuntime(code, timeoutMs, stdio, signal, runtime = currentJavaScriptRuntime()) {
888
+ try {
889
+ return {
890
+ value: await executeScriptInRuntime(code, timeoutMs, stdio, signal, runtime),
891
+ runtime
892
+ };
893
+ } catch (error) {
894
+ if (runtime === "worker" && isSandboxWorkerUnavailable(error) && typeof document !== "undefined") try {
895
+ return {
896
+ value: await executeScriptInRuntime(code, timeoutMs, stdio, signal, "iframe"),
897
+ runtime: "iframe"
898
+ };
899
+ } catch (fallbackError) {
900
+ throw withExecutionRuntime(fallbackError, "iframe");
901
+ }
902
+ throw withExecutionRuntime(error, runtime);
903
+ }
904
+ }
905
+ async function executeScriptInRuntime(code, timeoutMs, stdio, signal, runtime) {
781
906
  const consoleLike = {
782
907
  log: (...args) => stdio.push("stdout", formatConsoleArgs(args)),
783
908
  info: (...args) => stdio.push("stdout", formatConsoleArgs(args)),
@@ -785,7 +910,7 @@ async function executeScript(code, timeoutMs, stdio, signal) {
785
910
  error: (...args) => stdio.push("stderr", formatConsoleArgs(args))
786
911
  };
787
912
  if (signal?.aborted) throw abortError();
788
- if (javascriptExecuteRuntime(hasNodeVm(), typeof document !== "undefined") === "vm") {
913
+ if (runtime === "vm") {
789
914
  const vm = await import("node:vm");
790
915
  const context = vm.createContext({ console: consoleLike });
791
916
  return vm.runInContext(code, context, {
@@ -793,12 +918,45 @@ async function executeScript(code, timeoutMs, stdio, signal) {
793
918
  displayErrors: true
794
919
  });
795
920
  }
921
+ if (runtime === "worker") return executeInSandboxWorker(code, timeoutMs, stdio, signal);
796
922
  return executeInSandboxIframe(code, timeoutMs, stdio, signal);
797
923
  }
798
- function javascriptExecuteRuntime(hasVm, hasDocument) {
924
+ function currentJavaScriptRuntime() {
925
+ return javascriptExecuteRuntime(hasNodeVm(), canUseSandboxWorker(), typeof document !== "undefined");
926
+ }
927
+ function javascriptExecuteRuntime(hasVm, hasWorker, hasDocument) {
799
928
  if (hasVm) return "vm";
929
+ if (hasWorker) return "worker";
800
930
  if (hasDocument) return "iframe";
801
- throw new Error("JavaScript execute needs node:vm or a document for the sandbox iframe.");
931
+ throw new Error("JavaScript execute needs node:vm, a browser worker sandbox, or a document for the sandbox iframe.");
932
+ }
933
+ function javascriptRuntimeProvenance(runtime) {
934
+ if (runtime === "vm") return {
935
+ host: "local",
936
+ runtime: "node:vm",
937
+ sandbox: "vm"
938
+ };
939
+ if (runtime === "worker") return {
940
+ host: "local",
941
+ runtime: "web-worker",
942
+ sandbox: "worker"
943
+ };
944
+ return {
945
+ host: "local",
946
+ runtime: "iframe",
947
+ sandbox: "srcdoc"
948
+ };
949
+ }
950
+ function withExecutionRuntime(error, runtime) {
951
+ if (error instanceof Error) return Object.assign(error, { executionRuntime: runtime });
952
+ if (isErrorLike(error)) return Object.assign(error, { executionRuntime: runtime });
953
+ return Object.assign(new Error(String(error)), { executionRuntime: runtime });
954
+ }
955
+ function executionRuntimeFromError(error) {
956
+ if (error && typeof error === "object" && "executionRuntime" in error && isJavaScriptExecutionRuntime(error.executionRuntime)) return error.executionRuntime;
957
+ }
958
+ function isJavaScriptExecutionRuntime(value) {
959
+ return value === "vm" || value === "worker" || value === "iframe";
802
960
  }
803
961
  function isTimeout(error) {
804
962
  return Boolean(error && typeof error === "object" && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT");
@@ -1065,9 +1223,12 @@ async function runTypeScript(request) {
1065
1223
  const stdio = new StdioBuffer(tracker.startedAt);
1066
1224
  tracker.start("compile", "Strip types");
1067
1225
  const javascript = stripTypeScript(request.code);
1226
+ const runtime = currentJavaScriptRuntime();
1227
+ let executedRuntime = runtime;
1068
1228
  tracker.start("execute", "Execute");
1069
1229
  try {
1070
- const value = await executeScript(javascript, request.timeoutMs, stdio, request.signal);
1230
+ const result = await executeScriptWithRuntime(javascript, request.timeoutMs, stdio, request.signal, runtime);
1231
+ executedRuntime = result.runtime;
1071
1232
  tracker.stop();
1072
1233
  return {
1073
1234
  status: "ok",
@@ -1078,14 +1239,10 @@ async function runTypeScript(request) {
1078
1239
  host: "local",
1079
1240
  runtime: "strip-types"
1080
1241
  },
1081
- execute: {
1082
- host: "local",
1083
- runtime: hasNodeVm() ? "node:vm" : "iframe",
1084
- sandbox: hasNodeVm() ? "vm" : "srcdoc"
1085
- }
1242
+ execute: javascriptRuntimeProvenance(executedRuntime)
1086
1243
  },
1087
1244
  timing: tracker.report(),
1088
- value: value === void 0 ? void 0 : String(value)
1245
+ value: result.value === void 0 ? void 0 : String(result.value)
1089
1246
  };
1090
1247
  } catch (error) {
1091
1248
  if (isAbortError(error) || request.signal?.aborted) throw error;
@@ -1105,11 +1262,7 @@ async function runTypeScript(request) {
1105
1262
  host: "local",
1106
1263
  runtime: "strip-types"
1107
1264
  },
1108
- execute: {
1109
- host: "local",
1110
- runtime: hasNodeVm() ? "node:vm" : "iframe",
1111
- sandbox: hasNodeVm() ? "vm" : "srcdoc"
1112
- }
1265
+ execute: javascriptRuntimeProvenance(executedRuntime)
1113
1266
  },
1114
1267
  timing: tracker.report()
1115
1268
  };
@@ -1282,9 +1435,12 @@ function errorMessage(error) {
1282
1435
  }
1283
1436
  function friendlyTransportMessage(error) {
1284
1437
  const message = errorMessage(error);
1285
- if ((error instanceof TypeError || error instanceof Error && error.name === "TypeError") && /failed to fetch|networkerror|load failed|network request failed/i.test(message)) return "The executor could not be reached from this page (often CORS). Set endpoints to a host that allows browser POST, or use the Vite dev proxy.";
1438
+ if (isOfflineError(error)) return "The executor is offline or unreachable from this page (for example, CORS). Set endpoints to a host that allows browser POST, or use the Vite dev proxy.";
1286
1439
  return message;
1287
1440
  }
1441
+ function transportFailureStatus(error) {
1442
+ return isOfflineError(error) ? "offline" : "error";
1443
+ }
1288
1444
  function errorResult(message, source = "code-play", status = "error") {
1289
1445
  return withStdioText({
1290
1446
  status,
@@ -1298,6 +1454,14 @@ function errorResult(message, source = "code-play", status = "error") {
1298
1454
  timing: emptyTiming()
1299
1455
  });
1300
1456
  }
1457
+ function isOfflineError(error) {
1458
+ const message = errorMessage(error);
1459
+ const name = error && typeof error === "object" && "name" in error ? String(error.name) : void 0;
1460
+ const typeError = error instanceof TypeError || name === "TypeError";
1461
+ if (name === "MissingTransportError") return true;
1462
+ if (typeError && /failed to fetch|networkerror|load failed|network request failed/i.test(message)) return true;
1463
+ return /\boffline\b|no code play transport|network request failed/i.test(message);
1464
+ }
1301
1465
  //#endregion
1302
1466
  //#region src/session.ts
1303
1467
  var CodePlaySession = class {
@@ -1317,6 +1481,7 @@ var CodePlaySession = class {
1317
1481
  timeoutMs;
1318
1482
  transport;
1319
1483
  endpoints;
1484
+ project;
1320
1485
  loadTypeScript;
1321
1486
  listeners = /* @__PURE__ */ new Map();
1322
1487
  abort;
@@ -1328,6 +1493,7 @@ var CodePlaySession = class {
1328
1493
  this.timeoutMs = input.timeoutMs;
1329
1494
  this.transport = input.transport;
1330
1495
  this.endpoints = input.endpoints;
1496
+ this.project = input.project;
1331
1497
  this.loadTypeScript = input.loadTypeScript;
1332
1498
  }
1333
1499
  on(event, listener) {
@@ -1368,6 +1534,7 @@ var CodePlaySession = class {
1368
1534
  transport: this.transport,
1369
1535
  loadTypeScript: this.loadTypeScript,
1370
1536
  endpoints: this.endpoints,
1537
+ project: this.project,
1371
1538
  signal
1372
1539
  };
1373
1540
  try {
@@ -1375,7 +1542,7 @@ var CodePlaySession = class {
1375
1542
  return this.finish(result);
1376
1543
  } catch (error) {
1377
1544
  if (signal.aborted || isAbortError(error)) return this.finish(errorResult("Run cancelled.", "code-play", "cancelled"));
1378
- return this.finish(errorResult(friendlyTransportMessage(error)));
1545
+ return this.finish(errorResult(friendlyTransportMessage(error), "code-play", transportFailureStatus(error)));
1379
1546
  }
1380
1547
  }
1381
1548
  finish(result) {
@@ -1460,12 +1627,39 @@ async function runPlayAction(input) {
1460
1627
  input.setBusy(false);
1461
1628
  }
1462
1629
  }
1630
+ function idleRunActionState() {
1631
+ return { phase: "idle" };
1632
+ }
1633
+ function runningRunActionState(action, startedAtMs = Date.now()) {
1634
+ return {
1635
+ phase: "running",
1636
+ action,
1637
+ startedAtMs
1638
+ };
1639
+ }
1640
+ function resultRunActionState(action, result, finishedAtMs = Date.now()) {
1641
+ return {
1642
+ phase: result.status === "offline" ? "offline" : result.status === "ok" || result.status === "cancelled" ? "result" : "error",
1643
+ action,
1644
+ result,
1645
+ message: result.diagnostics[0]?.message,
1646
+ finishedAtMs
1647
+ };
1648
+ }
1649
+ function errorRunActionState(action, error, finishedAtMs = Date.now()) {
1650
+ return {
1651
+ phase: "error",
1652
+ action,
1653
+ message: errorMessage(error),
1654
+ finishedAtMs
1655
+ };
1656
+ }
1463
1657
  //#endregion
1464
1658
  //#region src/styles.ts
1465
1659
  var CODE_PLAY_STYLES = `
1466
1660
  .ox-code-play {
1467
1661
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
1468
- border-radius: 12px;
1662
+ border-radius: 8px;
1469
1663
  background: var(--octc-color-bg-alt, var(--octc-color-bg, Canvas));
1470
1664
  color: var(--octc-color-text, CanvasText);
1471
1665
  overflow: hidden;
@@ -1479,22 +1673,147 @@ var CODE_PLAY_STYLES = `
1479
1673
  padding: 0.6rem 0.8rem;
1480
1674
  border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 12%, transparent));
1481
1675
  }
1676
+ .ox-code-play__summary {
1677
+ display: inline-flex;
1678
+ flex: 1 1 12rem;
1679
+ min-width: 9rem;
1680
+ gap: 0.45rem;
1681
+ align-items: baseline;
1682
+ }
1482
1683
  .ox-code-play__lang {
1483
1684
  font: 600 0.8rem/1.2 ui-sans-serif, system-ui, sans-serif;
1484
- margin-right: auto;
1485
1685
  color: var(--octc-color-text, CanvasText);
1486
1686
  }
1687
+ .ox-code-play__title {
1688
+ min-width: 0;
1689
+ overflow-wrap: anywhere;
1690
+ font: 500 0.78rem/1.25 ui-sans-serif, system-ui, sans-serif;
1691
+ opacity: 0.72;
1692
+ }
1693
+ .ox-code-play__status {
1694
+ min-width: 5.25rem;
1695
+ text-align: center;
1696
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
1697
+ border-radius: 6px;
1698
+ padding: 0.18rem 0.5rem;
1699
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
1700
+ color: var(--octc-color-text, CanvasText);
1701
+ background: var(--octc-color-bg, Canvas);
1702
+ }
1703
+ .ox-code-play[data-ox-run-state="running"] .ox-code-play__status {
1704
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
1705
+ }
1706
+ .ox-code-play[data-ox-run-state="error"] .ox-code-play__status,
1707
+ .ox-code-play[data-ox-run-state="offline"] .ox-code-play__status {
1708
+ color: var(--octc-danger, #b42318);
1709
+ }
1487
1710
  .ox-code-play__toolbar button {
1488
1711
  appearance: none;
1489
1712
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 20%, transparent));
1490
1713
  background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
1491
1714
  color: var(--octc-color-text, CanvasText);
1492
- border-radius: 999px;
1715
+ border-radius: 8px;
1493
1716
  padding: 0.25rem 0.75rem;
1494
1717
  font: 600 0.75rem/1.4 ui-sans-serif, system-ui, sans-serif;
1495
1718
  cursor: pointer;
1496
1719
  }
1497
1720
  .ox-code-play__toolbar button:disabled { opacity: 0.55; cursor: progress; }
1721
+ .ox-code-play__toolbar button:focus-visible,
1722
+ .ox-code-play__tabs button:focus-visible,
1723
+ .ox-code-play__field input:focus-visible,
1724
+ .ox-code-play__field select:focus-visible,
1725
+ .ox-code-play__panel:focus-visible {
1726
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
1727
+ outline-offset: 2px;
1728
+ }
1729
+ .ox-code-play__runtime {
1730
+ display: flex;
1731
+ flex-wrap: wrap;
1732
+ gap: 0.45rem;
1733
+ align-items: center;
1734
+ padding: 0.55rem 0.8rem;
1735
+ border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 10%, transparent));
1736
+ background: color-mix(in srgb, var(--octc-color-text, CanvasText) 4%, transparent);
1737
+ }
1738
+ .ox-code-play__runtime-chip {
1739
+ display: inline-grid;
1740
+ gap: 0.1rem;
1741
+ min-width: 7.5rem;
1742
+ padding: 0.35rem 0.5rem;
1743
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
1744
+ border-radius: 6px;
1745
+ background: var(--octc-color-bg, Canvas);
1746
+ }
1747
+ .ox-code-play__runtime-chip span {
1748
+ font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
1749
+ text-transform: uppercase;
1750
+ letter-spacing: 0.04em;
1751
+ opacity: 0.62;
1752
+ }
1753
+ .ox-code-play__runtime-chip strong {
1754
+ font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
1755
+ }
1756
+ .ox-code-play__runtime-chip--ok strong { color: var(--octc-color-primary, var(--octc-accent, #4f46e5)); }
1757
+ .ox-code-play__runtime-chip--warn strong { color: var(--octc-warning, #b54708); }
1758
+ .ox-code-play__runtime-chip--muted strong { opacity: 0.72; }
1759
+ .ox-code-play__project {
1760
+ display: inline-flex;
1761
+ flex: 1 1 18rem;
1762
+ min-width: min(100%, 16rem);
1763
+ gap: 0.45rem;
1764
+ align-items: center;
1765
+ flex-wrap: wrap;
1766
+ margin-left: auto;
1767
+ padding: 0.35rem 0.5rem;
1768
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
1769
+ border-radius: 6px;
1770
+ background: var(--octc-color-bg, Canvas);
1771
+ }
1772
+ .ox-code-play__project-main,
1773
+ .ox-code-play__project-entry {
1774
+ display: inline-grid;
1775
+ gap: 0.1rem;
1776
+ min-width: 0;
1777
+ }
1778
+ .ox-code-play__project-main { flex: 1 1 8.5rem; }
1779
+ .ox-code-play__project-entry { flex: 999 1 9rem; }
1780
+ .ox-code-play__project-main span,
1781
+ .ox-code-play__project-entry span {
1782
+ font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
1783
+ text-transform: uppercase;
1784
+ letter-spacing: 0.04em;
1785
+ opacity: 0.62;
1786
+ }
1787
+ .ox-code-play__project-main strong,
1788
+ .ox-code-play__project-entry strong {
1789
+ min-width: 0;
1790
+ overflow-wrap: anywhere;
1791
+ font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
1792
+ }
1793
+ .ox-code-play__project-files,
1794
+ .ox-code-play__project-warning,
1795
+ .ox-code-play__project-link {
1796
+ flex: 0 0 auto;
1797
+ border-radius: 6px;
1798
+ padding: 0.18rem 0.45rem;
1799
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
1800
+ }
1801
+ .ox-code-play__project-files {
1802
+ background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
1803
+ }
1804
+ .ox-code-play__project-warning {
1805
+ color: var(--octc-warning, #b54708);
1806
+ background: color-mix(in srgb, var(--octc-warning, #b54708) 12%, transparent);
1807
+ }
1808
+ .ox-code-play__project-link {
1809
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
1810
+ background: color-mix(in srgb, var(--octc-color-primary, var(--octc-accent, #4f46e5)) 12%, transparent);
1811
+ text-decoration: none;
1812
+ }
1813
+ .ox-code-play__project-link:focus-visible {
1814
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
1815
+ outline-offset: 2px;
1816
+ }
1498
1817
  .ox-code-play .ox-code { margin: 0; }
1499
1818
  .ox-code-play__source pre { margin: 0; border: 0; border-radius: 0; }
1500
1819
  .ox-code-play__tabs {
@@ -1508,7 +1827,7 @@ var CODE_PLAY_STYLES = `
1508
1827
  background: transparent;
1509
1828
  color: var(--octc-color-text, CanvasText);
1510
1829
  padding: 0.35rem 0.55rem;
1511
- border-radius: 8px 8px 0 0;
1830
+ border-radius: 6px 6px 0 0;
1512
1831
  font: 600 0.75rem/1.2 ui-sans-serif, system-ui, sans-serif;
1513
1832
  cursor: pointer;
1514
1833
  }
@@ -1535,7 +1854,7 @@ var CODE_PLAY_STYLES = `
1535
1854
  .ox-code-play__field input, .ox-code-play__field select {
1536
1855
  font: inherit;
1537
1856
  padding: 0.3rem 0.45rem;
1538
- border-radius: 8px;
1857
+ border-radius: 6px;
1539
1858
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 18%, transparent));
1540
1859
  background: var(--octc-color-bg, Canvas);
1541
1860
  color: var(--octc-color-text, CanvasText);
@@ -1630,42 +1949,141 @@ function renderPlayUi(state) {
1630
1949
  if (preset === "headless") return "";
1631
1950
  const panel = state.panel ?? "stdio";
1632
1951
  const canTypecheck = state.payload.capabilities.typecheck;
1952
+ const runState = state.runState ?? (state.busy ? {
1953
+ phase: "running",
1954
+ action: "execute"
1955
+ } : idleRunActionState());
1956
+ const isBusy = Boolean(state.busy) || runState.phase === "running";
1633
1957
  const tabs = renderTabs(state, panel);
1634
1958
  const viewers = state.payload.viewers;
1635
- return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui>
1959
+ const label = widgetLabel(state.payload, definition);
1960
+ return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui data-ox-run-state="${runState.phase}" role="region" aria-label="${escapeHtml(label)}" aria-busy="${isBusy ? "true" : "false"}">
1636
1961
  <div class="ox-code-play__toolbar">
1637
- <span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>
1638
- <button type="button" data-ox-action="run"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
1639
- ${canTypecheck ? `<button type="button" data-ox-action="typecheck"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
1640
- <button type="button" data-ox-action="cancel"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
1962
+ <span class="ox-code-play__summary"><span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>${state.payload.title ? `<span class="ox-code-play__title">${escapeHtml(state.payload.title)}</span>` : ""}</span>
1963
+ <span class="ox-code-play__status" data-ox-status role="status" aria-live="polite">${renderRunStatusText(runState)}</span>
1964
+ <button type="button" data-ox-action="run" aria-label="${escapeHtml(`Run ${label}`)}"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
1965
+ ${canTypecheck ? `<button type="button" data-ox-action="typecheck" aria-label="${escapeHtml(`Typecheck ${label}`)}"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
1966
+ <button type="button" data-ox-action="cancel" aria-label="${escapeHtml(`Cancel ${label}`)}"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
1641
1967
  </div>
1968
+ ${renderRuntimeStrip(state.payload, definition)}
1642
1969
  <div class="ox-code-play__source"></div>
1643
1970
  ${tabs}
1644
- ${viewers.stdio ? `<div class="ox-code-play__panel" data-panel="stdio">${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}</div>` : ""}
1645
- ${viewers.stderr ? `<div class="ox-code-play__panel" data-panel="stderr"${hidden(panel, "stderr", preset)}>${renderStderrHtml(state.result)}</div>` : ""}
1646
- <div class="ox-code-play__panel" data-panel="config"${hidden(panel, "config", preset)}>${renderConfigHtml(definition?.configSchema ?? [], state.payload.config)}</div>
1647
- <div class="ox-code-play__panel" data-panel="provenance"${hidden(panel, "provenance", preset)}>${renderProvenanceHtml(state.result?.provenance)}</div>
1648
- <div class="ox-code-play__panel" data-panel="timing"${hidden(panel, "timing", preset)}>${renderTimingHtml(state.result?.timing)}</div>
1971
+ ${viewers.stdio ? renderPanel("stdio", "Stdio", panel, preset, `${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}`) : ""}
1972
+ ${viewers.stderr ? renderPanel("stderr", "Stderr", panel, preset, renderStderrHtml(state.result)) : ""}
1973
+ ${viewers.config ? renderPanel("config", "Config", panel, preset, renderConfigHtml(definition?.configSchema ?? [], state.payload.config)) : ""}
1974
+ ${viewers.provenance ? renderPanel("provenance", "Provenance", panel, preset, renderProvenanceHtml(state.result?.provenance)) : ""}
1975
+ ${viewers.timing ? renderPanel("timing", "Timing", panel, preset, renderTimingHtml(state.result?.timing)) : ""}
1649
1976
  </div>`;
1650
1977
  }
1978
+ function renderRunStatusText(state) {
1979
+ if (state.phase === "running") return state.action === "typecheck" ? "Typechecking" : "Running";
1980
+ if (state.phase === "offline") return "Offline";
1981
+ if (state.phase === "error") {
1982
+ if (state.result?.status === "timeout") return "Timed out";
1983
+ if (state.result?.status === "unsupported") return "Unsupported";
1984
+ return "Error";
1985
+ }
1986
+ if (state.phase === "result") {
1987
+ if (state.result?.status === "cancelled") return "Cancelled";
1988
+ return "Done";
1989
+ }
1990
+ return "Ready";
1991
+ }
1992
+ function renderRuntimeStrip(payload, definition) {
1993
+ const runtime = runtimeLabel(payload, definition);
1994
+ const executor = executorLabel(payload, definition);
1995
+ const checks = payload.capabilities.typecheck ? "Typecheck ready" : "Run only";
1996
+ return `<div class="ox-code-play__runtime" aria-label="Code Play runtime">${[
1997
+ runtimeChip("Runtime", runtime.label, runtime.kind),
1998
+ runtimeChip("Executor", executor.label, executor.kind),
1999
+ runtimeChip("Checks", checks, payload.capabilities.typecheck ? "ok" : "muted")
2000
+ ].join("")}${renderProjectSandboxHtml(payload.project)}</div>`;
2001
+ }
2002
+ function runtimeLabel(payload, definition) {
2003
+ switch (definition?.backend) {
2004
+ case "javascript": return {
2005
+ label: "Browser sandbox",
2006
+ kind: "ok"
2007
+ };
2008
+ case "typescript": return {
2009
+ label: "TypeScript sandbox",
2010
+ kind: "ok"
2011
+ };
2012
+ case "framework": return {
2013
+ label: `${definition.name} iframe preview`,
2014
+ kind: "ok"
2015
+ };
2016
+ case "rust-playground": return {
2017
+ label: "Rust Playground",
2018
+ kind: payload.endpoints?.rust ? "ok" : "warn"
2019
+ };
2020
+ case "go-playground": return {
2021
+ label: "Go Playground",
2022
+ kind: payload.endpoints?.go ? "ok" : "warn"
2023
+ };
2024
+ case "remote": return payload.endpoint ? {
2025
+ label: "Piston-compatible",
2026
+ kind: "ok"
2027
+ } : {
2028
+ label: "Endpoint missing",
2029
+ kind: "warn"
2030
+ };
2031
+ default: return {
2032
+ label: definition?.name ?? payload.language,
2033
+ kind: "muted"
2034
+ };
2035
+ }
2036
+ }
2037
+ function executorLabel(payload, definition) {
2038
+ if (!payload.capabilities.execute) return {
2039
+ label: "Disabled",
2040
+ kind: "warn"
2041
+ };
2042
+ if (definition?.backend === "remote" && !payload.endpoint) return {
2043
+ label: "Configure endpoint",
2044
+ kind: "warn"
2045
+ };
2046
+ return {
2047
+ label: "On demand",
2048
+ kind: "ok"
2049
+ };
2050
+ }
2051
+ function runtimeChip(label, value, kind) {
2052
+ return `<span class="ox-code-play__runtime-chip ox-code-play__runtime-chip--${kind}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></span>`;
2053
+ }
2054
+ function renderProjectSandboxHtml(project) {
2055
+ if (!project) return "";
2056
+ const fileCount = `${project.files.length} ${project.files.length === 1 ? "file" : "files"}`;
2057
+ const target = project.target === "browser" ? "Browser project" : project.target === "node" ? "Node-like project" : "External project";
2058
+ const url = project.openUrl ?? project.fallbackUrl;
2059
+ const warnings = project.warnings?.length ? `<span class="ox-code-play__project-warning" title="${escapeAttribute(project.warnings.join("\n"))}">Warnings</span>` : "";
2060
+ const link = url ? `<a class="ox-code-play__project-link" href="${escapeAttribute(url)}" target="_blank" rel="noopener noreferrer">Open</a>` : "";
2061
+ return `<span class="ox-code-play__project" data-ox-project-provider="${escapeAttribute(project.provider)}">
2062
+ <span class="ox-code-play__project-main"><span>${escapeHtml(project.label)}</span><strong>${escapeHtml(target)}</strong></span>
2063
+ ${project.entry ? `<span class="ox-code-play__project-entry"><span>Entry</span><strong>${escapeHtml(project.entry)}</strong></span>` : ""}
2064
+ <span class="ox-code-play__project-files">${escapeHtml(fileCount)}</span>
2065
+ ${warnings}${link}
2066
+ </span>`;
2067
+ }
1651
2068
  function renderTabs(state, panel) {
1652
2069
  if (state.payload.ui === "compact") return "";
1653
2070
  const viewers = state.payload.viewers;
1654
2071
  const buttons = [
1655
- viewers.stdio ? tab("stdio", "stdio", panel) : "",
1656
- viewers.stderr ? tab("stderr", "stderr", panel) : "",
1657
- viewers.config ? tab("config", "config", panel) : "",
1658
- viewers.provenance ? tab("provenance", "provenance", panel) : "",
1659
- viewers.timing ? tab("timing", "timing", panel) : ""
2072
+ viewers.stdio ? tab("stdio", "Stdio", panel) : "",
2073
+ viewers.stderr ? tab("stderr", "Stderr", panel) : "",
2074
+ viewers.config ? tab("config", "Config", panel) : "",
2075
+ viewers.provenance ? tab("provenance", "Provenance", panel) : "",
2076
+ viewers.timing ? tab("timing", "Timing", panel) : ""
1660
2077
  ].filter(Boolean).join("");
1661
2078
  return buttons ? `<div class="ox-code-play__tabs" role="tablist">${buttons}</div>` : "";
1662
2079
  }
1663
2080
  function tab(id, label, selected) {
1664
- return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${selected === id ? "true" : "false"}">${label}</button>`;
2081
+ const isSelected = selected === id;
2082
+ return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${isSelected ? "true" : "false"}" tabindex="${isSelected ? "0" : "-1"}">${label}</button>`;
1665
2083
  }
1666
2084
  function actionButtonAttrs(action, busy) {
1667
2085
  const state = actionBusyState(action, busy);
1668
- return `${state.disabled ? " disabled" : ""}${state.hidden ? " hidden" : ""}`;
2086
+ return `${state.disabled ? " disabled aria-disabled=\"true\"" : ""}${state.hidden ? " hidden" : ""}`;
1669
2087
  }
1670
2088
  function actionBusyState(action, busy) {
1671
2089
  if (action === "cancel") return {
@@ -1682,15 +2100,30 @@ function applyActionBusy(root, busy) {
1682
2100
  const state = actionBusyState(button.dataset.oxAction ?? "", busy);
1683
2101
  button.disabled = state.disabled;
1684
2102
  button.hidden = state.hidden;
2103
+ button.setAttribute("aria-disabled", state.disabled ? "true" : "false");
1685
2104
  }
2105
+ for (const widget of queryPlayWidgets(root)) widget.setAttribute("aria-busy", busy ? "true" : "false");
2106
+ }
2107
+ function renderPanel(id, label, current, preset, html) {
2108
+ return `<div class="ox-code-play__panel" role="tabpanel" tabindex="0" aria-label="${escapeHtml(label)}" data-panel="${id}"${hidden(current, id, preset)}>${html}</div>`;
1686
2109
  }
1687
2110
  function hidden(current, id, preset) {
1688
2111
  if (preset === "compact" && (id === "stdio" || id === "stderr")) return "";
1689
2112
  return current === id ? "" : " hidden";
1690
2113
  }
2114
+ function widgetLabel(payload, definition) {
2115
+ const language = definition?.name ?? payload.language;
2116
+ return payload.title ? `${payload.title} (${language})` : `${language} Code Play`;
2117
+ }
2118
+ function queryPlayWidgets(root) {
2119
+ const widgets = [...root.querySelectorAll(".ox-code-play")];
2120
+ if (typeof HTMLElement !== "undefined" && root instanceof HTMLElement && root.matches(".ox-code-play")) widgets.unshift(root);
2121
+ return widgets;
2122
+ }
1691
2123
  //#endregion
1692
2124
  //#region src/hydrate.ts
1693
2125
  var STYLE_ID = "ox-code-play-styles";
2126
+ var widgetId = 0;
1694
2127
  function hydrateCodePlay(root = defaultRoot(), options = {}) {
1695
2128
  ensureStyles();
1696
2129
  const client = options.client ?? createCodePlayFromPayloads(root);
@@ -1703,7 +2136,7 @@ function mountCodePlay(element, options = {}) {
1703
2136
  const payload = readPlayPayload(element.getAttribute("data-ox-code-play") ?? "");
1704
2137
  if (!payload || payload.ui === "headless") return;
1705
2138
  const client = options.client ?? createCodePlay({
1706
- languages: { [payload.language]: true },
2139
+ languages: { [payload.language]: languageEnableFromPayload(payload) },
1707
2140
  endpoints: payload.endpoints
1708
2141
  });
1709
2142
  const source = element.innerHTML;
@@ -1715,14 +2148,17 @@ function mountCodePlay(element, options = {}) {
1715
2148
  if (sourceSlot) sourceSlot.innerHTML = source;
1716
2149
  element.replaceChildren(widget);
1717
2150
  element.dataset.oxCodePlayMounted = "true";
2151
+ element.removeAttribute("inert");
1718
2152
  bindWidget(element, payload, client);
2153
+ prepareA11y(element);
1719
2154
  }
1720
2155
  function bindWidget(element, payload, client) {
1721
2156
  let current = payload;
1722
2157
  const session = client.createSession({
1723
2158
  language: payload.language,
1724
2159
  code: payload.code,
1725
- config: payload.config
2160
+ config: payload.config,
2161
+ project: payload.project
1726
2162
  });
1727
2163
  const runButton = element.querySelector("[data-ox-action=\"run\"]");
1728
2164
  const checkButton = element.querySelector("[data-ox-action=\"typecheck\"]");
@@ -1730,6 +2166,9 @@ function bindWidget(element, payload, client) {
1730
2166
  runButton?.addEventListener("click", () => void run("execute"));
1731
2167
  checkButton?.addEventListener("click", () => void run("typecheck"));
1732
2168
  cancelButton?.addEventListener("click", () => session.cancel());
2169
+ element.addEventListener("keydown", (event) => {
2170
+ if (event.target instanceof HTMLElement && event.target.matches("[role=\"tab\"]")) handleTabKeydown(element, event);
2171
+ });
1733
2172
  element.addEventListener("click", (event) => {
1734
2173
  const target = event.target;
1735
2174
  if (!(target instanceof HTMLElement)) return;
@@ -1748,13 +2187,20 @@ function bindWidget(element, payload, client) {
1748
2187
  async function run(action) {
1749
2188
  await runPlayAction({
1750
2189
  action: () => action === "typecheck" ? session.typecheck() : session.run(),
1751
- setBusy: (busy) => applyActionBusy(element, busy),
1752
- onResult: (result) => paintResult(element, current, result),
1753
- onError: (error) => paintResult(element, current, errorResult(errorMessage(error)))
2190
+ setBusy: (busy) => {
2191
+ applyActionBusy(element, busy);
2192
+ if (busy) paintRunState(element, runningRunActionState(action));
2193
+ },
2194
+ onResult: (result) => paintResult(element, current, result, action),
2195
+ onError: (error) => {
2196
+ paintRunState(element, errorRunActionState(action, error));
2197
+ paintResult(element, current, errorResult(errorMessage(error)), action);
2198
+ }
1754
2199
  });
1755
2200
  }
1756
2201
  }
1757
- function paintResult(element, _payload, result) {
2202
+ function paintResult(element, _payload, result, action) {
2203
+ paintRunState(element, resultRunActionState(action, result));
1758
2204
  const stdio = element.querySelector("[data-panel=\"stdio\"]");
1759
2205
  if (stdio) {
1760
2206
  stdio.innerHTML = `${renderDiagnosticsHtml(result)}${renderStdioHtml(result.stdio)}`;
@@ -1775,11 +2221,27 @@ function paintResult(element, _payload, result) {
1775
2221
  if (timing) timing.innerHTML = renderTimingHtml(result.timing);
1776
2222
  const stderr = element.querySelector("[data-panel=\"stderr\"]");
1777
2223
  if (stderr) stderr.innerHTML = renderStderrHtml(result);
1778
- showPanel(element, Boolean(stderr) && (Boolean(result.stderr) || result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) ? "stderr" : "stdio");
2224
+ showPanel(element, resultPanelToShow(result, Boolean(stderr)));
2225
+ }
2226
+ function paintRunState(element, state) {
2227
+ const widget = element.querySelector(".ox-code-play");
2228
+ widget?.setAttribute("data-ox-run-state", state.phase);
2229
+ widget?.setAttribute("aria-busy", state.phase === "running" ? "true" : "false");
2230
+ const status = element.querySelector("[data-ox-status]");
2231
+ if (status) status.textContent = renderRunStatusText(state);
2232
+ }
2233
+ function resultPanelToShow(result, hasStderrPanel) {
2234
+ if (!hasStderrPanel) return "stdio";
2235
+ if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.status !== "ok" && Boolean(result.stderr)) return "stderr";
2236
+ return "stdio";
1779
2237
  }
1780
2238
  function showPanel(element, panel) {
1781
2239
  const compact = Boolean(element.querySelector(".ox-code-play--compact"));
1782
- for (const tab of element.querySelectorAll("[data-ox-panel]")) tab.setAttribute("aria-selected", tab.getAttribute("data-ox-panel") === panel ? "true" : "false");
2240
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
2241
+ const selected = tab.getAttribute("data-ox-panel") === panel;
2242
+ tab.setAttribute("aria-selected", selected ? "true" : "false");
2243
+ tab.tabIndex = selected ? 0 : -1;
2244
+ }
1783
2245
  for (const node of element.querySelectorAll(".ox-code-play__panel")) {
1784
2246
  const id = node.dataset.panel;
1785
2247
  if (compact && (id === "stdio" || id === "stderr")) {
@@ -1789,6 +2251,38 @@ function showPanel(element, panel) {
1789
2251
  node.hidden = id !== panel;
1790
2252
  }
1791
2253
  }
2254
+ function handleTabKeydown(element, event) {
2255
+ if (![
2256
+ "ArrowLeft",
2257
+ "ArrowRight",
2258
+ "Home",
2259
+ "End"
2260
+ ].includes(event.key)) return;
2261
+ const tabs = [...element.querySelectorAll("[data-ox-panel]")];
2262
+ const current = tabs.indexOf(event.target);
2263
+ if (current === -1) return;
2264
+ event.preventDefault();
2265
+ const next = tabs[event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : event.key === "ArrowRight" ? (current + 1) % tabs.length : (current - 1 + tabs.length) % tabs.length];
2266
+ const panel = next?.dataset.oxPanel;
2267
+ if (!next || !panel) return;
2268
+ showPanel(element, panel);
2269
+ next.focus();
2270
+ }
2271
+ function prepareA11y(element) {
2272
+ const widget = element.querySelector(".ox-code-play");
2273
+ if (!widget) return;
2274
+ if (!widget.id) widget.id = `ox-code-play-${++widgetId}`;
2275
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
2276
+ const panelName = tab.dataset.oxPanel;
2277
+ const panel = panelName ? element.querySelector(`[data-panel="${panelName}"]`) : null;
2278
+ if (!panelName || !panel) continue;
2279
+ tab.id ||= `${widget.id}-${panelName}-tab`;
2280
+ panel.id ||= `${widget.id}-${panelName}-panel`;
2281
+ tab.setAttribute("aria-controls", panel.id);
2282
+ panel.setAttribute("aria-labelledby", tab.id);
2283
+ panel.removeAttribute("aria-label");
2284
+ }
2285
+ }
1792
2286
  function readForm(form) {
1793
2287
  const data = new FormData(form);
1794
2288
  const config = {};
@@ -1801,7 +2295,7 @@ function createCodePlayFromPayloads(root) {
1801
2295
  let endpoints;
1802
2296
  for (const element of queryWidgets(root)) try {
1803
2297
  const payload = decodePayload(element.getAttribute("data-ox-code-play") ?? "");
1804
- languages[payload.language] = true;
2298
+ languages[payload.language] = languageEnableFromPayload(payload);
1805
2299
  endpoints = payload.endpoints ?? endpoints;
1806
2300
  } catch {}
1807
2301
  return createCodePlay({
@@ -1809,6 +2303,15 @@ function createCodePlayFromPayloads(root) {
1809
2303
  endpoints
1810
2304
  });
1811
2305
  }
2306
+ function languageEnableFromPayload(payload) {
2307
+ const enable = {
2308
+ execute: payload.capabilities.execute,
2309
+ typecheck: payload.capabilities.typecheck,
2310
+ config: payload.config
2311
+ };
2312
+ if (payload.endpoint) enable.endpoint = payload.endpoint;
2313
+ return enable;
2314
+ }
1812
2315
  function queryWidgets(root) {
1813
2316
  return [...root.querySelectorAll("[data-ox-code-play]")];
1814
2317
  }