@ox-content/code-play 3.0.0-alpha.9 → 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 {
@@ -1197,7 +1200,6 @@ async function typecheckTypeScript(request) {
1197
1200
  };
1198
1201
  } catch (error) {
1199
1202
  if (isAbortError(error) || request.signal?.aborted) throw error;
1200
- executedRuntime = executionRuntimeFromError(error) ?? executedRuntime;
1201
1203
  tracker.stop();
1202
1204
  const message = error instanceof Error ? error.message : String(error);
1203
1205
  return {
@@ -1433,9 +1435,12 @@ function errorMessage(error) {
1433
1435
  }
1434
1436
  function friendlyTransportMessage(error) {
1435
1437
  const message = errorMessage(error);
1436
- 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.";
1437
1439
  return message;
1438
1440
  }
1441
+ function transportFailureStatus(error) {
1442
+ return isOfflineError(error) ? "offline" : "error";
1443
+ }
1439
1444
  function errorResult(message, source = "code-play", status = "error") {
1440
1445
  return withStdioText({
1441
1446
  status,
@@ -1449,6 +1454,14 @@ function errorResult(message, source = "code-play", status = "error") {
1449
1454
  timing: emptyTiming()
1450
1455
  });
1451
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
+ }
1452
1465
  //#endregion
1453
1466
  //#region src/session.ts
1454
1467
  var CodePlaySession = class {
@@ -1468,6 +1481,7 @@ var CodePlaySession = class {
1468
1481
  timeoutMs;
1469
1482
  transport;
1470
1483
  endpoints;
1484
+ project;
1471
1485
  loadTypeScript;
1472
1486
  listeners = /* @__PURE__ */ new Map();
1473
1487
  abort;
@@ -1479,6 +1493,7 @@ var CodePlaySession = class {
1479
1493
  this.timeoutMs = input.timeoutMs;
1480
1494
  this.transport = input.transport;
1481
1495
  this.endpoints = input.endpoints;
1496
+ this.project = input.project;
1482
1497
  this.loadTypeScript = input.loadTypeScript;
1483
1498
  }
1484
1499
  on(event, listener) {
@@ -1519,6 +1534,7 @@ var CodePlaySession = class {
1519
1534
  transport: this.transport,
1520
1535
  loadTypeScript: this.loadTypeScript,
1521
1536
  endpoints: this.endpoints,
1537
+ project: this.project,
1522
1538
  signal
1523
1539
  };
1524
1540
  try {
@@ -1526,7 +1542,7 @@ var CodePlaySession = class {
1526
1542
  return this.finish(result);
1527
1543
  } catch (error) {
1528
1544
  if (signal.aborted || isAbortError(error)) return this.finish(errorResult("Run cancelled.", "code-play", "cancelled"));
1529
- return this.finish(errorResult(friendlyTransportMessage(error)));
1545
+ return this.finish(errorResult(friendlyTransportMessage(error), "code-play", transportFailureStatus(error)));
1530
1546
  }
1531
1547
  }
1532
1548
  finish(result) {
@@ -1611,62 +1627,221 @@ async function runPlayAction(input) {
1611
1627
  input.setBusy(false);
1612
1628
  }
1613
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
+ }
1614
1657
  //#endregion
1615
1658
  //#region src/styles.ts
1616
1659
  var CODE_PLAY_STYLES = `
1617
1660
  .ox-code-play {
1618
1661
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
1619
- border-radius: 12px;
1620
- background: var(--octc-color-bg-alt, var(--octc-color-bg, Canvas));
1662
+ border-radius: 6px;
1663
+ background: var(--octc-color-bg, Canvas);
1621
1664
  color: var(--octc-color-text, CanvasText);
1622
1665
  overflow: hidden;
1623
- margin: 1.25rem 0;
1666
+ margin: 1rem 0;
1624
1667
  }
1625
1668
  .ox-code-play__toolbar {
1626
1669
  display: flex;
1627
1670
  flex-wrap: wrap;
1628
- gap: 0.5rem;
1671
+ gap: 0.4rem;
1629
1672
  align-items: center;
1630
- padding: 0.6rem 0.8rem;
1673
+ padding: 0.45rem 0.6rem;
1631
1674
  border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 12%, transparent));
1675
+ background: color-mix(in srgb, var(--octc-color-bg-alt, var(--octc-color-bg, Canvas)) 64%, transparent);
1676
+ }
1677
+ .ox-code-play__summary {
1678
+ display: inline-flex;
1679
+ flex: 1 1 12rem;
1680
+ min-width: 9rem;
1681
+ gap: 0.45rem;
1682
+ align-items: baseline;
1632
1683
  }
1633
1684
  .ox-code-play__lang {
1634
1685
  font: 600 0.8rem/1.2 ui-sans-serif, system-ui, sans-serif;
1635
- margin-right: auto;
1636
1686
  color: var(--octc-color-text, CanvasText);
1637
1687
  }
1688
+ .ox-code-play__title {
1689
+ min-width: 0;
1690
+ overflow-wrap: anywhere;
1691
+ font: 500 0.78rem/1.25 ui-sans-serif, system-ui, sans-serif;
1692
+ opacity: 0.72;
1693
+ }
1694
+ .ox-code-play__status {
1695
+ min-width: 4.6rem;
1696
+ text-align: center;
1697
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
1698
+ border-radius: 999px;
1699
+ padding: 0.12rem 0.45rem;
1700
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
1701
+ color: var(--octc-color-text, CanvasText);
1702
+ background: var(--octc-color-bg, Canvas);
1703
+ }
1704
+ .ox-code-play[data-ox-run-state="running"] .ox-code-play__status {
1705
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
1706
+ }
1707
+ .ox-code-play[data-ox-run-state="error"] .ox-code-play__status,
1708
+ .ox-code-play[data-ox-run-state="offline"] .ox-code-play__status {
1709
+ color: var(--octc-danger, #b42318);
1710
+ }
1638
1711
  .ox-code-play__toolbar button {
1639
1712
  appearance: none;
1640
1713
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 20%, transparent));
1641
1714
  background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
1642
1715
  color: var(--octc-color-text, CanvasText);
1643
- border-radius: 999px;
1644
- padding: 0.25rem 0.75rem;
1716
+ border-radius: 6px;
1717
+ min-height: 1.9rem;
1718
+ padding: 0.18rem 0.62rem;
1645
1719
  font: 600 0.75rem/1.4 ui-sans-serif, system-ui, sans-serif;
1646
1720
  cursor: pointer;
1647
1721
  }
1648
1722
  .ox-code-play__toolbar button:disabled { opacity: 0.55; cursor: progress; }
1723
+ .ox-code-play__toolbar button:focus-visible,
1724
+ .ox-code-play__tabs button:focus-visible,
1725
+ .ox-code-play__field input:focus-visible,
1726
+ .ox-code-play__field select:focus-visible,
1727
+ .ox-code-play__panel:focus-visible {
1728
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
1729
+ outline-offset: 2px;
1730
+ }
1731
+ .ox-code-play__runtime {
1732
+ display: flex;
1733
+ flex-wrap: wrap;
1734
+ gap: 0.4rem 0.7rem;
1735
+ align-items: center;
1736
+ padding: 0.38rem 0.6rem;
1737
+ border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 10%, transparent));
1738
+ background: color-mix(in srgb, var(--octc-color-bg-alt, var(--octc-color-bg, Canvas)) 56%, transparent);
1739
+ }
1740
+ .ox-code-play__runtime-chip {
1741
+ display: inline-flex;
1742
+ gap: 0.3rem;
1743
+ align-items: baseline;
1744
+ min-width: 0;
1745
+ padding: 0;
1746
+ border: 0;
1747
+ background: transparent;
1748
+ }
1749
+ .ox-code-play__runtime-chip span {
1750
+ font: 650 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
1751
+ text-transform: uppercase;
1752
+ letter-spacing: 0.03em;
1753
+ opacity: 0.62;
1754
+ }
1755
+ .ox-code-play__runtime-chip strong {
1756
+ font: 650 0.72rem/1.2 ui-sans-serif, system-ui, sans-serif;
1757
+ }
1758
+ .ox-code-play__runtime-chip--ok strong { color: var(--octc-color-primary, var(--octc-accent, #4f46e5)); }
1759
+ .ox-code-play__runtime-chip--warn strong { color: var(--octc-warning, #b54708); }
1760
+ .ox-code-play__runtime-chip--muted strong { opacity: 0.72; }
1761
+ .ox-code-play__project {
1762
+ display: inline-flex;
1763
+ flex: 1 1 18rem;
1764
+ min-width: min(100%, 16rem);
1765
+ gap: 0.45rem;
1766
+ align-items: center;
1767
+ flex-wrap: wrap;
1768
+ margin-left: auto;
1769
+ padding: 0.28rem 0.42rem;
1770
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
1771
+ border-radius: 6px;
1772
+ background: var(--octc-color-bg, Canvas);
1773
+ }
1774
+ .ox-code-play__project-main,
1775
+ .ox-code-play__project-entry {
1776
+ display: inline-grid;
1777
+ gap: 0.1rem;
1778
+ min-width: 0;
1779
+ }
1780
+ .ox-code-play__project-main { flex: 1 1 8.5rem; }
1781
+ .ox-code-play__project-entry { flex: 999 1 9rem; }
1782
+ .ox-code-play__project-main span,
1783
+ .ox-code-play__project-entry span {
1784
+ font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
1785
+ text-transform: uppercase;
1786
+ letter-spacing: 0.04em;
1787
+ opacity: 0.62;
1788
+ }
1789
+ .ox-code-play__project-main strong,
1790
+ .ox-code-play__project-entry strong {
1791
+ min-width: 0;
1792
+ overflow-wrap: anywhere;
1793
+ font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
1794
+ }
1795
+ .ox-code-play__project-files,
1796
+ .ox-code-play__project-warning,
1797
+ .ox-code-play__project-link {
1798
+ flex: 0 0 auto;
1799
+ border-radius: 6px;
1800
+ padding: 0.18rem 0.45rem;
1801
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
1802
+ }
1803
+ .ox-code-play__project-files {
1804
+ background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
1805
+ }
1806
+ .ox-code-play__project-warning {
1807
+ color: var(--octc-warning, #b54708);
1808
+ background: color-mix(in srgb, var(--octc-warning, #b54708) 12%, transparent);
1809
+ }
1810
+ .ox-code-play__project-link {
1811
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
1812
+ background: color-mix(in srgb, var(--octc-color-primary, var(--octc-accent, #4f46e5)) 12%, transparent);
1813
+ text-decoration: none;
1814
+ }
1815
+ .ox-code-play__project-link:focus-visible {
1816
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
1817
+ outline-offset: 2px;
1818
+ }
1649
1819
  .ox-code-play .ox-code { margin: 0; }
1650
- .ox-code-play__source pre { margin: 0; border: 0; border-radius: 0; }
1820
+ .ox-code-play__source pre {
1821
+ margin: 0;
1822
+ border: 0;
1823
+ border-radius: 0;
1824
+ padding: 0.75rem 0.8rem;
1825
+ }
1651
1826
  .ox-code-play__tabs {
1652
1827
  display: flex;
1653
- gap: 0.25rem;
1654
- padding: 0.4rem 0.7rem 0;
1828
+ gap: 0.15rem;
1829
+ padding: 0.35rem 0.55rem 0;
1655
1830
  }
1656
1831
  .ox-code-play__tabs button {
1657
1832
  appearance: none;
1658
1833
  border: 0;
1659
1834
  background: transparent;
1660
1835
  color: var(--octc-color-text, CanvasText);
1661
- padding: 0.35rem 0.55rem;
1662
- border-radius: 8px 8px 0 0;
1836
+ padding: 0.28rem 0.48rem;
1837
+ border-radius: 5px 5px 0 0;
1663
1838
  font: 600 0.75rem/1.2 ui-sans-serif, system-ui, sans-serif;
1664
1839
  cursor: pointer;
1665
1840
  }
1666
1841
  .ox-code-play__tabs button[aria-selected="true"] {
1667
1842
  background: color-mix(in srgb, var(--octc-color-text, CanvasText) 10%, transparent);
1668
1843
  }
1669
- .ox-code-play__panel { padding: 0.7rem 0.8rem 0.9rem; color: var(--octc-color-text, CanvasText); }
1844
+ .ox-code-play__panel { padding: 0.55rem 0.65rem 0.7rem; color: var(--octc-color-text, CanvasText); }
1670
1845
  .ox-code-play__panel pre {
1671
1846
  background: transparent !important;
1672
1847
  color: inherit !important;
@@ -1677,7 +1852,7 @@ var CODE_PLAY_STYLES = `
1677
1852
  }
1678
1853
  .ox-code-play__empty { margin: 0; opacity: 0.7; font-size: 0.85rem; }
1679
1854
  .ox-code-play__stdio { font: 12px/1.45 ui-monospace, SFMono-Regular, monospace; }
1680
- .ox-code-play__stdio-line { display: grid; grid-template-columns: 8.5rem 1fr; gap: 0.6rem; white-space: pre-wrap; }
1855
+ .ox-code-play__stdio-line { display: grid; grid-template-columns: 5.4rem 1fr; gap: 0.55rem; white-space: pre-wrap; }
1681
1856
  .ox-code-play__stdio-line--stderr { color: var(--octc-danger, #b42318); }
1682
1857
  .ox-code-play__stdio-line--stdin { opacity: 0.75; }
1683
1858
  .ox-code-play__stdio-meta { opacity: 0.6; }
@@ -1686,7 +1861,7 @@ var CODE_PLAY_STYLES = `
1686
1861
  .ox-code-play__field input, .ox-code-play__field select {
1687
1862
  font: inherit;
1688
1863
  padding: 0.3rem 0.45rem;
1689
- border-radius: 8px;
1864
+ border-radius: 6px;
1690
1865
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 18%, transparent));
1691
1866
  background: var(--octc-color-bg, Canvas);
1692
1867
  color: var(--octc-color-text, CanvasText);
@@ -1708,6 +1883,20 @@ var CODE_PLAY_STYLES = `
1708
1883
  .ox-code-play__diag--warning { color: var(--octc-warning, #b54708); }
1709
1884
  .ox-code-play--compact .ox-code-play__tabs { display: none; }
1710
1885
  .ox-code-play--compact .ox-code-play__panel[data-panel]:not([data-panel="stdio"]):not([data-panel="stderr"]) { display: none; }
1886
+ @media (max-width: 640px) {
1887
+ .ox-code-play__toolbar {
1888
+ align-items: flex-start;
1889
+ }
1890
+ .ox-code-play__runtime,
1891
+ .ox-code-play__tabs,
1892
+ .ox-code-play__panel {
1893
+ padding-inline: 0.55rem;
1894
+ }
1895
+ .ox-code-play__stdio-line {
1896
+ grid-template-columns: 1fr;
1897
+ gap: 0.15rem;
1898
+ }
1899
+ }
1711
1900
  `;
1712
1901
  //#endregion
1713
1902
  //#region src/viewers.ts
@@ -1781,42 +1970,141 @@ function renderPlayUi(state) {
1781
1970
  if (preset === "headless") return "";
1782
1971
  const panel = state.panel ?? "stdio";
1783
1972
  const canTypecheck = state.payload.capabilities.typecheck;
1973
+ const runState = state.runState ?? (state.busy ? {
1974
+ phase: "running",
1975
+ action: "execute"
1976
+ } : idleRunActionState());
1977
+ const isBusy = Boolean(state.busy) || runState.phase === "running";
1784
1978
  const tabs = renderTabs(state, panel);
1785
1979
  const viewers = state.payload.viewers;
1786
- return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui>
1980
+ const label = widgetLabel(state.payload, definition);
1981
+ 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"}">
1787
1982
  <div class="ox-code-play__toolbar">
1788
- <span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>
1789
- <button type="button" data-ox-action="run"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
1790
- ${canTypecheck ? `<button type="button" data-ox-action="typecheck"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
1791
- <button type="button" data-ox-action="cancel"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
1983
+ <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>
1984
+ <span class="ox-code-play__status" data-ox-status role="status" aria-live="polite">${renderRunStatusText(runState)}</span>
1985
+ <button type="button" data-ox-action="run" aria-label="${escapeHtml(`Run ${label}`)}"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
1986
+ ${canTypecheck ? `<button type="button" data-ox-action="typecheck" aria-label="${escapeHtml(`Typecheck ${label}`)}"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
1987
+ <button type="button" data-ox-action="cancel" aria-label="${escapeHtml(`Cancel ${label}`)}"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
1792
1988
  </div>
1989
+ ${renderRuntimeStrip(state.payload, definition)}
1793
1990
  <div class="ox-code-play__source"></div>
1794
1991
  ${tabs}
1795
- ${viewers.stdio ? `<div class="ox-code-play__panel" data-panel="stdio">${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}</div>` : ""}
1796
- ${viewers.stderr ? `<div class="ox-code-play__panel" data-panel="stderr"${hidden(panel, "stderr", preset)}>${renderStderrHtml(state.result)}</div>` : ""}
1797
- <div class="ox-code-play__panel" data-panel="config"${hidden(panel, "config", preset)}>${renderConfigHtml(definition?.configSchema ?? [], state.payload.config)}</div>
1798
- <div class="ox-code-play__panel" data-panel="provenance"${hidden(panel, "provenance", preset)}>${renderProvenanceHtml(state.result?.provenance)}</div>
1799
- <div class="ox-code-play__panel" data-panel="timing"${hidden(panel, "timing", preset)}>${renderTimingHtml(state.result?.timing)}</div>
1992
+ ${viewers.stdio ? renderPanel("stdio", "Stdio", panel, preset, `${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}`) : ""}
1993
+ ${viewers.stderr ? renderPanel("stderr", "Stderr", panel, preset, renderStderrHtml(state.result)) : ""}
1994
+ ${viewers.config ? renderPanel("config", "Config", panel, preset, renderConfigHtml(definition?.configSchema ?? [], state.payload.config)) : ""}
1995
+ ${viewers.provenance ? renderPanel("provenance", "Provenance", panel, preset, renderProvenanceHtml(state.result?.provenance)) : ""}
1996
+ ${viewers.timing ? renderPanel("timing", "Timing", panel, preset, renderTimingHtml(state.result?.timing)) : ""}
1800
1997
  </div>`;
1801
1998
  }
1999
+ function renderRunStatusText(state) {
2000
+ if (state.phase === "running") return state.action === "typecheck" ? "Typechecking" : "Running";
2001
+ if (state.phase === "offline") return "Offline";
2002
+ if (state.phase === "error") {
2003
+ if (state.result?.status === "timeout") return "Timed out";
2004
+ if (state.result?.status === "unsupported") return "Unsupported";
2005
+ return "Error";
2006
+ }
2007
+ if (state.phase === "result") {
2008
+ if (state.result?.status === "cancelled") return "Cancelled";
2009
+ return "Done";
2010
+ }
2011
+ return "Ready";
2012
+ }
2013
+ function renderRuntimeStrip(payload, definition) {
2014
+ const runtime = runtimeLabel(payload, definition);
2015
+ const executor = executorLabel(payload, definition);
2016
+ const checks = payload.capabilities.typecheck ? "Typecheck ready" : "Run only";
2017
+ return `<div class="ox-code-play__runtime" aria-label="Code Play runtime">${[
2018
+ runtimeChip("Runtime", runtime.label, runtime.kind),
2019
+ runtimeChip("Executor", executor.label, executor.kind),
2020
+ runtimeChip("Checks", checks, payload.capabilities.typecheck ? "ok" : "muted")
2021
+ ].join("")}${renderProjectSandboxHtml(payload.project)}</div>`;
2022
+ }
2023
+ function runtimeLabel(payload, definition) {
2024
+ switch (definition?.backend) {
2025
+ case "javascript": return {
2026
+ label: "Browser sandbox",
2027
+ kind: "ok"
2028
+ };
2029
+ case "typescript": return {
2030
+ label: "TypeScript sandbox",
2031
+ kind: "ok"
2032
+ };
2033
+ case "framework": return {
2034
+ label: `${definition.name} iframe preview`,
2035
+ kind: "ok"
2036
+ };
2037
+ case "rust-playground": return {
2038
+ label: "Rust Playground",
2039
+ kind: payload.endpoints?.rust ? "ok" : "warn"
2040
+ };
2041
+ case "go-playground": return {
2042
+ label: "Go Playground",
2043
+ kind: payload.endpoints?.go ? "ok" : "warn"
2044
+ };
2045
+ case "remote": return payload.endpoint ? {
2046
+ label: "Piston-compatible",
2047
+ kind: "ok"
2048
+ } : {
2049
+ label: "Endpoint missing",
2050
+ kind: "warn"
2051
+ };
2052
+ default: return {
2053
+ label: definition?.name ?? payload.language,
2054
+ kind: "muted"
2055
+ };
2056
+ }
2057
+ }
2058
+ function executorLabel(payload, definition) {
2059
+ if (!payload.capabilities.execute) return {
2060
+ label: "Disabled",
2061
+ kind: "warn"
2062
+ };
2063
+ if (definition?.backend === "remote" && !payload.endpoint) return {
2064
+ label: "Configure endpoint",
2065
+ kind: "warn"
2066
+ };
2067
+ return {
2068
+ label: "On demand",
2069
+ kind: "ok"
2070
+ };
2071
+ }
2072
+ function runtimeChip(label, value, kind) {
2073
+ return `<span class="ox-code-play__runtime-chip ox-code-play__runtime-chip--${kind}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></span>`;
2074
+ }
2075
+ function renderProjectSandboxHtml(project) {
2076
+ if (!project) return "";
2077
+ const fileCount = `${project.files.length} ${project.files.length === 1 ? "file" : "files"}`;
2078
+ const target = project.target === "browser" ? "Browser project" : project.target === "node" ? "Node-like project" : "External project";
2079
+ const url = project.openUrl ?? project.fallbackUrl;
2080
+ const warnings = project.warnings?.length ? `<span class="ox-code-play__project-warning" title="${escapeAttribute(project.warnings.join("\n"))}">Warnings</span>` : "";
2081
+ const link = url ? `<a class="ox-code-play__project-link" href="${escapeAttribute(url)}" target="_blank" rel="noopener noreferrer">Open</a>` : "";
2082
+ return `<span class="ox-code-play__project" data-ox-project-provider="${escapeAttribute(project.provider)}">
2083
+ <span class="ox-code-play__project-main"><span>${escapeHtml(project.label)}</span><strong>${escapeHtml(target)}</strong></span>
2084
+ ${project.entry ? `<span class="ox-code-play__project-entry"><span>Entry</span><strong>${escapeHtml(project.entry)}</strong></span>` : ""}
2085
+ <span class="ox-code-play__project-files">${escapeHtml(fileCount)}</span>
2086
+ ${warnings}${link}
2087
+ </span>`;
2088
+ }
1802
2089
  function renderTabs(state, panel) {
1803
2090
  if (state.payload.ui === "compact") return "";
1804
2091
  const viewers = state.payload.viewers;
1805
2092
  const buttons = [
1806
- viewers.stdio ? tab("stdio", "stdio", panel) : "",
1807
- viewers.stderr ? tab("stderr", "stderr", panel) : "",
1808
- viewers.config ? tab("config", "config", panel) : "",
1809
- viewers.provenance ? tab("provenance", "provenance", panel) : "",
1810
- viewers.timing ? tab("timing", "timing", panel) : ""
2093
+ viewers.stdio ? tab("stdio", "Stdio", panel) : "",
2094
+ viewers.stderr ? tab("stderr", "Stderr", panel) : "",
2095
+ viewers.config ? tab("config", "Config", panel) : "",
2096
+ viewers.provenance ? tab("provenance", "Provenance", panel) : "",
2097
+ viewers.timing ? tab("timing", "Timing", panel) : ""
1811
2098
  ].filter(Boolean).join("");
1812
2099
  return buttons ? `<div class="ox-code-play__tabs" role="tablist">${buttons}</div>` : "";
1813
2100
  }
1814
2101
  function tab(id, label, selected) {
1815
- return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${selected === id ? "true" : "false"}">${label}</button>`;
2102
+ const isSelected = selected === id;
2103
+ return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${isSelected ? "true" : "false"}" tabindex="${isSelected ? "0" : "-1"}">${label}</button>`;
1816
2104
  }
1817
2105
  function actionButtonAttrs(action, busy) {
1818
2106
  const state = actionBusyState(action, busy);
1819
- return `${state.disabled ? " disabled" : ""}${state.hidden ? " hidden" : ""}`;
2107
+ return `${state.disabled ? " disabled aria-disabled=\"true\"" : ""}${state.hidden ? " hidden" : ""}`;
1820
2108
  }
1821
2109
  function actionBusyState(action, busy) {
1822
2110
  if (action === "cancel") return {
@@ -1833,15 +2121,30 @@ function applyActionBusy(root, busy) {
1833
2121
  const state = actionBusyState(button.dataset.oxAction ?? "", busy);
1834
2122
  button.disabled = state.disabled;
1835
2123
  button.hidden = state.hidden;
2124
+ button.setAttribute("aria-disabled", state.disabled ? "true" : "false");
1836
2125
  }
2126
+ for (const widget of queryPlayWidgets(root)) widget.setAttribute("aria-busy", busy ? "true" : "false");
2127
+ }
2128
+ function renderPanel(id, label, current, preset, html) {
2129
+ return `<div class="ox-code-play__panel" role="tabpanel" tabindex="0" aria-label="${escapeHtml(label)}" data-panel="${id}"${hidden(current, id, preset)}>${html}</div>`;
1837
2130
  }
1838
2131
  function hidden(current, id, preset) {
1839
2132
  if (preset === "compact" && (id === "stdio" || id === "stderr")) return "";
1840
2133
  return current === id ? "" : " hidden";
1841
2134
  }
2135
+ function widgetLabel(payload, definition) {
2136
+ const language = definition?.name ?? payload.language;
2137
+ return payload.title ? `${payload.title} (${language})` : `${language} Code Play`;
2138
+ }
2139
+ function queryPlayWidgets(root) {
2140
+ const widgets = [...root.querySelectorAll(".ox-code-play")];
2141
+ if (typeof HTMLElement !== "undefined" && root instanceof HTMLElement && root.matches(".ox-code-play")) widgets.unshift(root);
2142
+ return widgets;
2143
+ }
1842
2144
  //#endregion
1843
2145
  //#region src/hydrate.ts
1844
2146
  var STYLE_ID = "ox-code-play-styles";
2147
+ var widgetId = 0;
1845
2148
  function hydrateCodePlay(root = defaultRoot(), options = {}) {
1846
2149
  ensureStyles();
1847
2150
  const client = options.client ?? createCodePlayFromPayloads(root);
@@ -1854,7 +2157,7 @@ function mountCodePlay(element, options = {}) {
1854
2157
  const payload = readPlayPayload(element.getAttribute("data-ox-code-play") ?? "");
1855
2158
  if (!payload || payload.ui === "headless") return;
1856
2159
  const client = options.client ?? createCodePlay({
1857
- languages: { [payload.language]: true },
2160
+ languages: { [payload.language]: languageEnableFromPayload(payload) },
1858
2161
  endpoints: payload.endpoints
1859
2162
  });
1860
2163
  const source = element.innerHTML;
@@ -1866,14 +2169,17 @@ function mountCodePlay(element, options = {}) {
1866
2169
  if (sourceSlot) sourceSlot.innerHTML = source;
1867
2170
  element.replaceChildren(widget);
1868
2171
  element.dataset.oxCodePlayMounted = "true";
2172
+ element.removeAttribute("inert");
1869
2173
  bindWidget(element, payload, client);
2174
+ prepareA11y(element);
1870
2175
  }
1871
2176
  function bindWidget(element, payload, client) {
1872
2177
  let current = payload;
1873
2178
  const session = client.createSession({
1874
2179
  language: payload.language,
1875
2180
  code: payload.code,
1876
- config: payload.config
2181
+ config: payload.config,
2182
+ project: payload.project
1877
2183
  });
1878
2184
  const runButton = element.querySelector("[data-ox-action=\"run\"]");
1879
2185
  const checkButton = element.querySelector("[data-ox-action=\"typecheck\"]");
@@ -1881,6 +2187,9 @@ function bindWidget(element, payload, client) {
1881
2187
  runButton?.addEventListener("click", () => void run("execute"));
1882
2188
  checkButton?.addEventListener("click", () => void run("typecheck"));
1883
2189
  cancelButton?.addEventListener("click", () => session.cancel());
2190
+ element.addEventListener("keydown", (event) => {
2191
+ if (event.target instanceof HTMLElement && event.target.matches("[role=\"tab\"]")) handleTabKeydown(element, event);
2192
+ });
1884
2193
  element.addEventListener("click", (event) => {
1885
2194
  const target = event.target;
1886
2195
  if (!(target instanceof HTMLElement)) return;
@@ -1899,13 +2208,20 @@ function bindWidget(element, payload, client) {
1899
2208
  async function run(action) {
1900
2209
  await runPlayAction({
1901
2210
  action: () => action === "typecheck" ? session.typecheck() : session.run(),
1902
- setBusy: (busy) => applyActionBusy(element, busy),
1903
- onResult: (result) => paintResult(element, current, result),
1904
- onError: (error) => paintResult(element, current, errorResult(errorMessage(error)))
2211
+ setBusy: (busy) => {
2212
+ applyActionBusy(element, busy);
2213
+ if (busy) paintRunState(element, runningRunActionState(action));
2214
+ },
2215
+ onResult: (result) => paintResult(element, current, result, action),
2216
+ onError: (error) => {
2217
+ paintRunState(element, errorRunActionState(action, error));
2218
+ paintResult(element, current, errorResult(errorMessage(error)), action);
2219
+ }
1905
2220
  });
1906
2221
  }
1907
2222
  }
1908
- function paintResult(element, _payload, result) {
2223
+ function paintResult(element, _payload, result, action) {
2224
+ paintRunState(element, resultRunActionState(action, result));
1909
2225
  const stdio = element.querySelector("[data-panel=\"stdio\"]");
1910
2226
  if (stdio) {
1911
2227
  stdio.innerHTML = `${renderDiagnosticsHtml(result)}${renderStdioHtml(result.stdio)}`;
@@ -1928,6 +2244,13 @@ function paintResult(element, _payload, result) {
1928
2244
  if (stderr) stderr.innerHTML = renderStderrHtml(result);
1929
2245
  showPanel(element, resultPanelToShow(result, Boolean(stderr)));
1930
2246
  }
2247
+ function paintRunState(element, state) {
2248
+ const widget = element.querySelector(".ox-code-play");
2249
+ widget?.setAttribute("data-ox-run-state", state.phase);
2250
+ widget?.setAttribute("aria-busy", state.phase === "running" ? "true" : "false");
2251
+ const status = element.querySelector("[data-ox-status]");
2252
+ if (status) status.textContent = renderRunStatusText(state);
2253
+ }
1931
2254
  function resultPanelToShow(result, hasStderrPanel) {
1932
2255
  if (!hasStderrPanel) return "stdio";
1933
2256
  if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.status !== "ok" && Boolean(result.stderr)) return "stderr";
@@ -1935,7 +2258,11 @@ function resultPanelToShow(result, hasStderrPanel) {
1935
2258
  }
1936
2259
  function showPanel(element, panel) {
1937
2260
  const compact = Boolean(element.querySelector(".ox-code-play--compact"));
1938
- for (const tab of element.querySelectorAll("[data-ox-panel]")) tab.setAttribute("aria-selected", tab.getAttribute("data-ox-panel") === panel ? "true" : "false");
2261
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
2262
+ const selected = tab.getAttribute("data-ox-panel") === panel;
2263
+ tab.setAttribute("aria-selected", selected ? "true" : "false");
2264
+ tab.tabIndex = selected ? 0 : -1;
2265
+ }
1939
2266
  for (const node of element.querySelectorAll(".ox-code-play__panel")) {
1940
2267
  const id = node.dataset.panel;
1941
2268
  if (compact && (id === "stdio" || id === "stderr")) {
@@ -1945,6 +2272,38 @@ function showPanel(element, panel) {
1945
2272
  node.hidden = id !== panel;
1946
2273
  }
1947
2274
  }
2275
+ function handleTabKeydown(element, event) {
2276
+ if (![
2277
+ "ArrowLeft",
2278
+ "ArrowRight",
2279
+ "Home",
2280
+ "End"
2281
+ ].includes(event.key)) return;
2282
+ const tabs = [...element.querySelectorAll("[data-ox-panel]")];
2283
+ const current = tabs.indexOf(event.target);
2284
+ if (current === -1) return;
2285
+ event.preventDefault();
2286
+ 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];
2287
+ const panel = next?.dataset.oxPanel;
2288
+ if (!next || !panel) return;
2289
+ showPanel(element, panel);
2290
+ next.focus();
2291
+ }
2292
+ function prepareA11y(element) {
2293
+ const widget = element.querySelector(".ox-code-play");
2294
+ if (!widget) return;
2295
+ if (!widget.id) widget.id = `ox-code-play-${++widgetId}`;
2296
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
2297
+ const panelName = tab.dataset.oxPanel;
2298
+ const panel = panelName ? element.querySelector(`[data-panel="${panelName}"]`) : null;
2299
+ if (!panelName || !panel) continue;
2300
+ tab.id ||= `${widget.id}-${panelName}-tab`;
2301
+ panel.id ||= `${widget.id}-${panelName}-panel`;
2302
+ tab.setAttribute("aria-controls", panel.id);
2303
+ panel.setAttribute("aria-labelledby", tab.id);
2304
+ panel.removeAttribute("aria-label");
2305
+ }
2306
+ }
1948
2307
  function readForm(form) {
1949
2308
  const data = new FormData(form);
1950
2309
  const config = {};
@@ -1957,7 +2316,7 @@ function createCodePlayFromPayloads(root) {
1957
2316
  let endpoints;
1958
2317
  for (const element of queryWidgets(root)) try {
1959
2318
  const payload = decodePayload(element.getAttribute("data-ox-code-play") ?? "");
1960
- languages[payload.language] = true;
2319
+ languages[payload.language] = languageEnableFromPayload(payload);
1961
2320
  endpoints = payload.endpoints ?? endpoints;
1962
2321
  } catch {}
1963
2322
  return createCodePlay({
@@ -1965,6 +2324,15 @@ function createCodePlayFromPayloads(root) {
1965
2324
  endpoints
1966
2325
  });
1967
2326
  }
2327
+ function languageEnableFromPayload(payload) {
2328
+ const enable = {
2329
+ execute: payload.capabilities.execute,
2330
+ typecheck: payload.capabilities.typecheck,
2331
+ config: payload.config
2332
+ };
2333
+ if (payload.endpoint) enable.endpoint = payload.endpoint;
2334
+ return enable;
2335
+ }
1968
2336
  function queryWidgets(root) {
1969
2337
  return [...root.querySelectorAll("[data-ox-code-play]")];
1970
2338
  }