@ox-content/code-play 3.0.0-alpha.8 → 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/hydrate2.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as errorResult, i as errorMessage, n as createCodePlay, o as JS_SANDBOX_FLAGS, u as selectStream } from "./client.mjs";
2
- import { a as escapeHtml, p as resolveLanguage, t as decodePayload } from "./payload.mjs";
2
+ import { a as escapeHtml, h as resolveLanguage, i as escapeAttribute, t as decodePayload } from "./payload.mjs";
3
3
  //#region src/hydrate-action.ts
4
4
  function readPlayPayload(encoded) {
5
5
  try {
@@ -18,62 +18,221 @@ async function runPlayAction(input) {
18
18
  input.setBusy(false);
19
19
  }
20
20
  }
21
+ function idleRunActionState() {
22
+ return { phase: "idle" };
23
+ }
24
+ function runningRunActionState(action, startedAtMs = Date.now()) {
25
+ return {
26
+ phase: "running",
27
+ action,
28
+ startedAtMs
29
+ };
30
+ }
31
+ function resultRunActionState(action, result, finishedAtMs = Date.now()) {
32
+ return {
33
+ phase: result.status === "offline" ? "offline" : result.status === "ok" || result.status === "cancelled" ? "result" : "error",
34
+ action,
35
+ result,
36
+ message: result.diagnostics[0]?.message,
37
+ finishedAtMs
38
+ };
39
+ }
40
+ function errorRunActionState(action, error, finishedAtMs = Date.now()) {
41
+ return {
42
+ phase: "error",
43
+ action,
44
+ message: errorMessage(error),
45
+ finishedAtMs
46
+ };
47
+ }
21
48
  //#endregion
22
49
  //#region src/styles.ts
23
50
  const CODE_PLAY_STYLES = `
24
51
  .ox-code-play {
25
52
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
26
- border-radius: 12px;
27
- background: var(--octc-color-bg-alt, var(--octc-color-bg, Canvas));
53
+ border-radius: 6px;
54
+ background: var(--octc-color-bg, Canvas);
28
55
  color: var(--octc-color-text, CanvasText);
29
56
  overflow: hidden;
30
- margin: 1.25rem 0;
57
+ margin: 1rem 0;
31
58
  }
32
59
  .ox-code-play__toolbar {
33
60
  display: flex;
34
61
  flex-wrap: wrap;
35
- gap: 0.5rem;
62
+ gap: 0.4rem;
36
63
  align-items: center;
37
- padding: 0.6rem 0.8rem;
64
+ padding: 0.45rem 0.6rem;
38
65
  border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 12%, transparent));
66
+ background: color-mix(in srgb, var(--octc-color-bg-alt, var(--octc-color-bg, Canvas)) 64%, transparent);
67
+ }
68
+ .ox-code-play__summary {
69
+ display: inline-flex;
70
+ flex: 1 1 12rem;
71
+ min-width: 9rem;
72
+ gap: 0.45rem;
73
+ align-items: baseline;
39
74
  }
40
75
  .ox-code-play__lang {
41
76
  font: 600 0.8rem/1.2 ui-sans-serif, system-ui, sans-serif;
42
- margin-right: auto;
43
77
  color: var(--octc-color-text, CanvasText);
44
78
  }
79
+ .ox-code-play__title {
80
+ min-width: 0;
81
+ overflow-wrap: anywhere;
82
+ font: 500 0.78rem/1.25 ui-sans-serif, system-ui, sans-serif;
83
+ opacity: 0.72;
84
+ }
85
+ .ox-code-play__status {
86
+ min-width: 4.6rem;
87
+ text-align: center;
88
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
89
+ border-radius: 999px;
90
+ padding: 0.12rem 0.45rem;
91
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
92
+ color: var(--octc-color-text, CanvasText);
93
+ background: var(--octc-color-bg, Canvas);
94
+ }
95
+ .ox-code-play[data-ox-run-state="running"] .ox-code-play__status {
96
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
97
+ }
98
+ .ox-code-play[data-ox-run-state="error"] .ox-code-play__status,
99
+ .ox-code-play[data-ox-run-state="offline"] .ox-code-play__status {
100
+ color: var(--octc-danger, #b42318);
101
+ }
45
102
  .ox-code-play__toolbar button {
46
103
  appearance: none;
47
104
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 20%, transparent));
48
105
  background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
49
106
  color: var(--octc-color-text, CanvasText);
50
- border-radius: 999px;
51
- padding: 0.25rem 0.75rem;
107
+ border-radius: 6px;
108
+ min-height: 1.9rem;
109
+ padding: 0.18rem 0.62rem;
52
110
  font: 600 0.75rem/1.4 ui-sans-serif, system-ui, sans-serif;
53
111
  cursor: pointer;
54
112
  }
55
113
  .ox-code-play__toolbar button:disabled { opacity: 0.55; cursor: progress; }
114
+ .ox-code-play__toolbar button:focus-visible,
115
+ .ox-code-play__tabs button:focus-visible,
116
+ .ox-code-play__field input:focus-visible,
117
+ .ox-code-play__field select:focus-visible,
118
+ .ox-code-play__panel:focus-visible {
119
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
120
+ outline-offset: 2px;
121
+ }
122
+ .ox-code-play__runtime {
123
+ display: flex;
124
+ flex-wrap: wrap;
125
+ gap: 0.4rem 0.7rem;
126
+ align-items: center;
127
+ padding: 0.38rem 0.6rem;
128
+ border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 10%, transparent));
129
+ background: color-mix(in srgb, var(--octc-color-bg-alt, var(--octc-color-bg, Canvas)) 56%, transparent);
130
+ }
131
+ .ox-code-play__runtime-chip {
132
+ display: inline-flex;
133
+ gap: 0.3rem;
134
+ align-items: baseline;
135
+ min-width: 0;
136
+ padding: 0;
137
+ border: 0;
138
+ background: transparent;
139
+ }
140
+ .ox-code-play__runtime-chip span {
141
+ font: 650 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
142
+ text-transform: uppercase;
143
+ letter-spacing: 0.03em;
144
+ opacity: 0.62;
145
+ }
146
+ .ox-code-play__runtime-chip strong {
147
+ font: 650 0.72rem/1.2 ui-sans-serif, system-ui, sans-serif;
148
+ }
149
+ .ox-code-play__runtime-chip--ok strong { color: var(--octc-color-primary, var(--octc-accent, #4f46e5)); }
150
+ .ox-code-play__runtime-chip--warn strong { color: var(--octc-warning, #b54708); }
151
+ .ox-code-play__runtime-chip--muted strong { opacity: 0.72; }
152
+ .ox-code-play__project {
153
+ display: inline-flex;
154
+ flex: 1 1 18rem;
155
+ min-width: min(100%, 16rem);
156
+ gap: 0.45rem;
157
+ align-items: center;
158
+ flex-wrap: wrap;
159
+ margin-left: auto;
160
+ padding: 0.28rem 0.42rem;
161
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
162
+ border-radius: 6px;
163
+ background: var(--octc-color-bg, Canvas);
164
+ }
165
+ .ox-code-play__project-main,
166
+ .ox-code-play__project-entry {
167
+ display: inline-grid;
168
+ gap: 0.1rem;
169
+ min-width: 0;
170
+ }
171
+ .ox-code-play__project-main { flex: 1 1 8.5rem; }
172
+ .ox-code-play__project-entry { flex: 999 1 9rem; }
173
+ .ox-code-play__project-main span,
174
+ .ox-code-play__project-entry span {
175
+ font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
176
+ text-transform: uppercase;
177
+ letter-spacing: 0.04em;
178
+ opacity: 0.62;
179
+ }
180
+ .ox-code-play__project-main strong,
181
+ .ox-code-play__project-entry strong {
182
+ min-width: 0;
183
+ overflow-wrap: anywhere;
184
+ font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
185
+ }
186
+ .ox-code-play__project-files,
187
+ .ox-code-play__project-warning,
188
+ .ox-code-play__project-link {
189
+ flex: 0 0 auto;
190
+ border-radius: 6px;
191
+ padding: 0.18rem 0.45rem;
192
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
193
+ }
194
+ .ox-code-play__project-files {
195
+ background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
196
+ }
197
+ .ox-code-play__project-warning {
198
+ color: var(--octc-warning, #b54708);
199
+ background: color-mix(in srgb, var(--octc-warning, #b54708) 12%, transparent);
200
+ }
201
+ .ox-code-play__project-link {
202
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
203
+ background: color-mix(in srgb, var(--octc-color-primary, var(--octc-accent, #4f46e5)) 12%, transparent);
204
+ text-decoration: none;
205
+ }
206
+ .ox-code-play__project-link:focus-visible {
207
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
208
+ outline-offset: 2px;
209
+ }
56
210
  .ox-code-play .ox-code { margin: 0; }
57
- .ox-code-play__source pre { margin: 0; border: 0; border-radius: 0; }
211
+ .ox-code-play__source pre {
212
+ margin: 0;
213
+ border: 0;
214
+ border-radius: 0;
215
+ padding: 0.75rem 0.8rem;
216
+ }
58
217
  .ox-code-play__tabs {
59
218
  display: flex;
60
- gap: 0.25rem;
61
- padding: 0.4rem 0.7rem 0;
219
+ gap: 0.15rem;
220
+ padding: 0.35rem 0.55rem 0;
62
221
  }
63
222
  .ox-code-play__tabs button {
64
223
  appearance: none;
65
224
  border: 0;
66
225
  background: transparent;
67
226
  color: var(--octc-color-text, CanvasText);
68
- padding: 0.35rem 0.55rem;
69
- border-radius: 8px 8px 0 0;
227
+ padding: 0.28rem 0.48rem;
228
+ border-radius: 5px 5px 0 0;
70
229
  font: 600 0.75rem/1.2 ui-sans-serif, system-ui, sans-serif;
71
230
  cursor: pointer;
72
231
  }
73
232
  .ox-code-play__tabs button[aria-selected="true"] {
74
233
  background: color-mix(in srgb, var(--octc-color-text, CanvasText) 10%, transparent);
75
234
  }
76
- .ox-code-play__panel { padding: 0.7rem 0.8rem 0.9rem; color: var(--octc-color-text, CanvasText); }
235
+ .ox-code-play__panel { padding: 0.55rem 0.65rem 0.7rem; color: var(--octc-color-text, CanvasText); }
77
236
  .ox-code-play__panel pre {
78
237
  background: transparent !important;
79
238
  color: inherit !important;
@@ -84,7 +243,7 @@ const CODE_PLAY_STYLES = `
84
243
  }
85
244
  .ox-code-play__empty { margin: 0; opacity: 0.7; font-size: 0.85rem; }
86
245
  .ox-code-play__stdio { font: 12px/1.45 ui-monospace, SFMono-Regular, monospace; }
87
- .ox-code-play__stdio-line { display: grid; grid-template-columns: 8.5rem 1fr; gap: 0.6rem; white-space: pre-wrap; }
246
+ .ox-code-play__stdio-line { display: grid; grid-template-columns: 5.4rem 1fr; gap: 0.55rem; white-space: pre-wrap; }
88
247
  .ox-code-play__stdio-line--stderr { color: var(--octc-danger, #b42318); }
89
248
  .ox-code-play__stdio-line--stdin { opacity: 0.75; }
90
249
  .ox-code-play__stdio-meta { opacity: 0.6; }
@@ -93,7 +252,7 @@ const CODE_PLAY_STYLES = `
93
252
  .ox-code-play__field input, .ox-code-play__field select {
94
253
  font: inherit;
95
254
  padding: 0.3rem 0.45rem;
96
- border-radius: 8px;
255
+ border-radius: 6px;
97
256
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 18%, transparent));
98
257
  background: var(--octc-color-bg, Canvas);
99
258
  color: var(--octc-color-text, CanvasText);
@@ -115,6 +274,20 @@ const CODE_PLAY_STYLES = `
115
274
  .ox-code-play__diag--warning { color: var(--octc-warning, #b54708); }
116
275
  .ox-code-play--compact .ox-code-play__tabs { display: none; }
117
276
  .ox-code-play--compact .ox-code-play__panel[data-panel]:not([data-panel="stdio"]):not([data-panel="stderr"]) { display: none; }
277
+ @media (max-width: 640px) {
278
+ .ox-code-play__toolbar {
279
+ align-items: flex-start;
280
+ }
281
+ .ox-code-play__runtime,
282
+ .ox-code-play__tabs,
283
+ .ox-code-play__panel {
284
+ padding-inline: 0.55rem;
285
+ }
286
+ .ox-code-play__stdio-line {
287
+ grid-template-columns: 1fr;
288
+ gap: 0.15rem;
289
+ }
290
+ }
118
291
  `;
119
292
  //#endregion
120
293
  //#region src/viewers.ts
@@ -188,42 +361,141 @@ function renderPlayUi(state) {
188
361
  if (preset === "headless") return "";
189
362
  const panel = state.panel ?? "stdio";
190
363
  const canTypecheck = state.payload.capabilities.typecheck;
364
+ const runState = state.runState ?? (state.busy ? {
365
+ phase: "running",
366
+ action: "execute"
367
+ } : idleRunActionState());
368
+ const isBusy = Boolean(state.busy) || runState.phase === "running";
191
369
  const tabs = renderTabs(state, panel);
192
370
  const viewers = state.payload.viewers;
193
- return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui>
371
+ const label = widgetLabel(state.payload, definition);
372
+ 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"}">
194
373
  <div class="ox-code-play__toolbar">
195
- <span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>
196
- <button type="button" data-ox-action="run"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
197
- ${canTypecheck ? `<button type="button" data-ox-action="typecheck"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
198
- <button type="button" data-ox-action="cancel"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
374
+ <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>
375
+ <span class="ox-code-play__status" data-ox-status role="status" aria-live="polite">${renderRunStatusText(runState)}</span>
376
+ <button type="button" data-ox-action="run" aria-label="${escapeHtml(`Run ${label}`)}"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
377
+ ${canTypecheck ? `<button type="button" data-ox-action="typecheck" aria-label="${escapeHtml(`Typecheck ${label}`)}"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
378
+ <button type="button" data-ox-action="cancel" aria-label="${escapeHtml(`Cancel ${label}`)}"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
199
379
  </div>
380
+ ${renderRuntimeStrip(state.payload, definition)}
200
381
  <div class="ox-code-play__source"></div>
201
382
  ${tabs}
202
- ${viewers.stdio ? `<div class="ox-code-play__panel" data-panel="stdio">${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}</div>` : ""}
203
- ${viewers.stderr ? `<div class="ox-code-play__panel" data-panel="stderr"${hidden(panel, "stderr", preset)}>${renderStderrHtml(state.result)}</div>` : ""}
204
- <div class="ox-code-play__panel" data-panel="config"${hidden(panel, "config", preset)}>${renderConfigHtml(definition?.configSchema ?? [], state.payload.config)}</div>
205
- <div class="ox-code-play__panel" data-panel="provenance"${hidden(panel, "provenance", preset)}>${renderProvenanceHtml(state.result?.provenance)}</div>
206
- <div class="ox-code-play__panel" data-panel="timing"${hidden(panel, "timing", preset)}>${renderTimingHtml(state.result?.timing)}</div>
383
+ ${viewers.stdio ? renderPanel("stdio", "Stdio", panel, preset, `${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}`) : ""}
384
+ ${viewers.stderr ? renderPanel("stderr", "Stderr", panel, preset, renderStderrHtml(state.result)) : ""}
385
+ ${viewers.config ? renderPanel("config", "Config", panel, preset, renderConfigHtml(definition?.configSchema ?? [], state.payload.config)) : ""}
386
+ ${viewers.provenance ? renderPanel("provenance", "Provenance", panel, preset, renderProvenanceHtml(state.result?.provenance)) : ""}
387
+ ${viewers.timing ? renderPanel("timing", "Timing", panel, preset, renderTimingHtml(state.result?.timing)) : ""}
207
388
  </div>`;
208
389
  }
390
+ function renderRunStatusText(state) {
391
+ if (state.phase === "running") return state.action === "typecheck" ? "Typechecking" : "Running";
392
+ if (state.phase === "offline") return "Offline";
393
+ if (state.phase === "error") {
394
+ if (state.result?.status === "timeout") return "Timed out";
395
+ if (state.result?.status === "unsupported") return "Unsupported";
396
+ return "Error";
397
+ }
398
+ if (state.phase === "result") {
399
+ if (state.result?.status === "cancelled") return "Cancelled";
400
+ return "Done";
401
+ }
402
+ return "Ready";
403
+ }
404
+ function renderRuntimeStrip(payload, definition) {
405
+ const runtime = runtimeLabel(payload, definition);
406
+ const executor = executorLabel(payload, definition);
407
+ const checks = payload.capabilities.typecheck ? "Typecheck ready" : "Run only";
408
+ return `<div class="ox-code-play__runtime" aria-label="Code Play runtime">${[
409
+ runtimeChip("Runtime", runtime.label, runtime.kind),
410
+ runtimeChip("Executor", executor.label, executor.kind),
411
+ runtimeChip("Checks", checks, payload.capabilities.typecheck ? "ok" : "muted")
412
+ ].join("")}${renderProjectSandboxHtml(payload.project)}</div>`;
413
+ }
414
+ function runtimeLabel(payload, definition) {
415
+ switch (definition?.backend) {
416
+ case "javascript": return {
417
+ label: "Browser sandbox",
418
+ kind: "ok"
419
+ };
420
+ case "typescript": return {
421
+ label: "TypeScript sandbox",
422
+ kind: "ok"
423
+ };
424
+ case "framework": return {
425
+ label: `${definition.name} iframe preview`,
426
+ kind: "ok"
427
+ };
428
+ case "rust-playground": return {
429
+ label: "Rust Playground",
430
+ kind: payload.endpoints?.rust ? "ok" : "warn"
431
+ };
432
+ case "go-playground": return {
433
+ label: "Go Playground",
434
+ kind: payload.endpoints?.go ? "ok" : "warn"
435
+ };
436
+ case "remote": return payload.endpoint ? {
437
+ label: "Piston-compatible",
438
+ kind: "ok"
439
+ } : {
440
+ label: "Endpoint missing",
441
+ kind: "warn"
442
+ };
443
+ default: return {
444
+ label: definition?.name ?? payload.language,
445
+ kind: "muted"
446
+ };
447
+ }
448
+ }
449
+ function executorLabel(payload, definition) {
450
+ if (!payload.capabilities.execute) return {
451
+ label: "Disabled",
452
+ kind: "warn"
453
+ };
454
+ if (definition?.backend === "remote" && !payload.endpoint) return {
455
+ label: "Configure endpoint",
456
+ kind: "warn"
457
+ };
458
+ return {
459
+ label: "On demand",
460
+ kind: "ok"
461
+ };
462
+ }
463
+ function runtimeChip(label, value, kind) {
464
+ return `<span class="ox-code-play__runtime-chip ox-code-play__runtime-chip--${kind}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></span>`;
465
+ }
466
+ function renderProjectSandboxHtml(project) {
467
+ if (!project) return "";
468
+ const fileCount = `${project.files.length} ${project.files.length === 1 ? "file" : "files"}`;
469
+ const target = project.target === "browser" ? "Browser project" : project.target === "node" ? "Node-like project" : "External project";
470
+ const url = project.openUrl ?? project.fallbackUrl;
471
+ const warnings = project.warnings?.length ? `<span class="ox-code-play__project-warning" title="${escapeAttribute(project.warnings.join("\n"))}">Warnings</span>` : "";
472
+ const link = url ? `<a class="ox-code-play__project-link" href="${escapeAttribute(url)}" target="_blank" rel="noopener noreferrer">Open</a>` : "";
473
+ return `<span class="ox-code-play__project" data-ox-project-provider="${escapeAttribute(project.provider)}">
474
+ <span class="ox-code-play__project-main"><span>${escapeHtml(project.label)}</span><strong>${escapeHtml(target)}</strong></span>
475
+ ${project.entry ? `<span class="ox-code-play__project-entry"><span>Entry</span><strong>${escapeHtml(project.entry)}</strong></span>` : ""}
476
+ <span class="ox-code-play__project-files">${escapeHtml(fileCount)}</span>
477
+ ${warnings}${link}
478
+ </span>`;
479
+ }
209
480
  function renderTabs(state, panel) {
210
481
  if (state.payload.ui === "compact") return "";
211
482
  const viewers = state.payload.viewers;
212
483
  const buttons = [
213
- viewers.stdio ? tab("stdio", "stdio", panel) : "",
214
- viewers.stderr ? tab("stderr", "stderr", panel) : "",
215
- viewers.config ? tab("config", "config", panel) : "",
216
- viewers.provenance ? tab("provenance", "provenance", panel) : "",
217
- viewers.timing ? tab("timing", "timing", panel) : ""
484
+ viewers.stdio ? tab("stdio", "Stdio", panel) : "",
485
+ viewers.stderr ? tab("stderr", "Stderr", panel) : "",
486
+ viewers.config ? tab("config", "Config", panel) : "",
487
+ viewers.provenance ? tab("provenance", "Provenance", panel) : "",
488
+ viewers.timing ? tab("timing", "Timing", panel) : ""
218
489
  ].filter(Boolean).join("");
219
490
  return buttons ? `<div class="ox-code-play__tabs" role="tablist">${buttons}</div>` : "";
220
491
  }
221
492
  function tab(id, label, selected) {
222
- return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${selected === id ? "true" : "false"}">${label}</button>`;
493
+ const isSelected = selected === id;
494
+ return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${isSelected ? "true" : "false"}" tabindex="${isSelected ? "0" : "-1"}">${label}</button>`;
223
495
  }
224
496
  function actionButtonAttrs(action, busy) {
225
497
  const state = actionBusyState(action, busy);
226
- return `${state.disabled ? " disabled" : ""}${state.hidden ? " hidden" : ""}`;
498
+ return `${state.disabled ? " disabled aria-disabled=\"true\"" : ""}${state.hidden ? " hidden" : ""}`;
227
499
  }
228
500
  function actionBusyState(action, busy) {
229
501
  if (action === "cancel") return {
@@ -240,15 +512,30 @@ function applyActionBusy(root, busy) {
240
512
  const state = actionBusyState(button.dataset.oxAction ?? "", busy);
241
513
  button.disabled = state.disabled;
242
514
  button.hidden = state.hidden;
515
+ button.setAttribute("aria-disabled", state.disabled ? "true" : "false");
243
516
  }
517
+ for (const widget of queryPlayWidgets(root)) widget.setAttribute("aria-busy", busy ? "true" : "false");
518
+ }
519
+ function renderPanel(id, label, current, preset, html) {
520
+ return `<div class="ox-code-play__panel" role="tabpanel" tabindex="0" aria-label="${escapeHtml(label)}" data-panel="${id}"${hidden(current, id, preset)}>${html}</div>`;
244
521
  }
245
522
  function hidden(current, id, preset) {
246
523
  if (preset === "compact" && (id === "stdio" || id === "stderr")) return "";
247
524
  return current === id ? "" : " hidden";
248
525
  }
526
+ function widgetLabel(payload, definition) {
527
+ const language = definition?.name ?? payload.language;
528
+ return payload.title ? `${payload.title} (${language})` : `${language} Code Play`;
529
+ }
530
+ function queryPlayWidgets(root) {
531
+ const widgets = [...root.querySelectorAll(".ox-code-play")];
532
+ if (typeof HTMLElement !== "undefined" && root instanceof HTMLElement && root.matches(".ox-code-play")) widgets.unshift(root);
533
+ return widgets;
534
+ }
249
535
  //#endregion
250
536
  //#region src/hydrate.ts
251
537
  const STYLE_ID = "ox-code-play-styles";
538
+ let widgetId = 0;
252
539
  function hydrateCodePlay(root = defaultRoot(), options = {}) {
253
540
  ensureStyles();
254
541
  const client = options.client ?? createCodePlayFromPayloads(root);
@@ -261,7 +548,7 @@ function mountCodePlay(element, options = {}) {
261
548
  const payload = readPlayPayload(element.getAttribute("data-ox-code-play") ?? "");
262
549
  if (!payload || payload.ui === "headless") return;
263
550
  const client = options.client ?? createCodePlay({
264
- languages: { [payload.language]: true },
551
+ languages: { [payload.language]: languageEnableFromPayload(payload) },
265
552
  endpoints: payload.endpoints
266
553
  });
267
554
  const source = element.innerHTML;
@@ -273,14 +560,17 @@ function mountCodePlay(element, options = {}) {
273
560
  if (sourceSlot) sourceSlot.innerHTML = source;
274
561
  element.replaceChildren(widget);
275
562
  element.dataset.oxCodePlayMounted = "true";
563
+ element.removeAttribute("inert");
276
564
  bindWidget(element, payload, client);
565
+ prepareA11y(element);
277
566
  }
278
567
  function bindWidget(element, payload, client) {
279
568
  let current = payload;
280
569
  const session = client.createSession({
281
570
  language: payload.language,
282
571
  code: payload.code,
283
- config: payload.config
572
+ config: payload.config,
573
+ project: payload.project
284
574
  });
285
575
  const runButton = element.querySelector("[data-ox-action=\"run\"]");
286
576
  const checkButton = element.querySelector("[data-ox-action=\"typecheck\"]");
@@ -288,6 +578,9 @@ function bindWidget(element, payload, client) {
288
578
  runButton?.addEventListener("click", () => void run("execute"));
289
579
  checkButton?.addEventListener("click", () => void run("typecheck"));
290
580
  cancelButton?.addEventListener("click", () => session.cancel());
581
+ element.addEventListener("keydown", (event) => {
582
+ if (event.target instanceof HTMLElement && event.target.matches("[role=\"tab\"]")) handleTabKeydown(element, event);
583
+ });
291
584
  element.addEventListener("click", (event) => {
292
585
  const target = event.target;
293
586
  if (!(target instanceof HTMLElement)) return;
@@ -306,13 +599,20 @@ function bindWidget(element, payload, client) {
306
599
  async function run(action) {
307
600
  await runPlayAction({
308
601
  action: () => action === "typecheck" ? session.typecheck() : session.run(),
309
- setBusy: (busy) => applyActionBusy(element, busy),
310
- onResult: (result) => paintResult(element, current, result),
311
- onError: (error) => paintResult(element, current, errorResult(errorMessage(error)))
602
+ setBusy: (busy) => {
603
+ applyActionBusy(element, busy);
604
+ if (busy) paintRunState(element, runningRunActionState(action));
605
+ },
606
+ onResult: (result) => paintResult(element, current, result, action),
607
+ onError: (error) => {
608
+ paintRunState(element, errorRunActionState(action, error));
609
+ paintResult(element, current, errorResult(errorMessage(error)), action);
610
+ }
312
611
  });
313
612
  }
314
613
  }
315
- function paintResult(element, _payload, result) {
614
+ function paintResult(element, _payload, result, action) {
615
+ paintRunState(element, resultRunActionState(action, result));
316
616
  const stdio = element.querySelector("[data-panel=\"stdio\"]");
317
617
  if (stdio) {
318
618
  stdio.innerHTML = `${renderDiagnosticsHtml(result)}${renderStdioHtml(result.stdio)}`;
@@ -335,6 +635,13 @@ function paintResult(element, _payload, result) {
335
635
  if (stderr) stderr.innerHTML = renderStderrHtml(result);
336
636
  showPanel(element, resultPanelToShow(result, Boolean(stderr)));
337
637
  }
638
+ function paintRunState(element, state) {
639
+ const widget = element.querySelector(".ox-code-play");
640
+ widget?.setAttribute("data-ox-run-state", state.phase);
641
+ widget?.setAttribute("aria-busy", state.phase === "running" ? "true" : "false");
642
+ const status = element.querySelector("[data-ox-status]");
643
+ if (status) status.textContent = renderRunStatusText(state);
644
+ }
338
645
  function resultPanelToShow(result, hasStderrPanel) {
339
646
  if (!hasStderrPanel) return "stdio";
340
647
  if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.status !== "ok" && Boolean(result.stderr)) return "stderr";
@@ -342,7 +649,11 @@ function resultPanelToShow(result, hasStderrPanel) {
342
649
  }
343
650
  function showPanel(element, panel) {
344
651
  const compact = Boolean(element.querySelector(".ox-code-play--compact"));
345
- for (const tab of element.querySelectorAll("[data-ox-panel]")) tab.setAttribute("aria-selected", tab.getAttribute("data-ox-panel") === panel ? "true" : "false");
652
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
653
+ const selected = tab.getAttribute("data-ox-panel") === panel;
654
+ tab.setAttribute("aria-selected", selected ? "true" : "false");
655
+ tab.tabIndex = selected ? 0 : -1;
656
+ }
346
657
  for (const node of element.querySelectorAll(".ox-code-play__panel")) {
347
658
  const id = node.dataset.panel;
348
659
  if (compact && (id === "stdio" || id === "stderr")) {
@@ -352,6 +663,38 @@ function showPanel(element, panel) {
352
663
  node.hidden = id !== panel;
353
664
  }
354
665
  }
666
+ function handleTabKeydown(element, event) {
667
+ if (![
668
+ "ArrowLeft",
669
+ "ArrowRight",
670
+ "Home",
671
+ "End"
672
+ ].includes(event.key)) return;
673
+ const tabs = [...element.querySelectorAll("[data-ox-panel]")];
674
+ const current = tabs.indexOf(event.target);
675
+ if (current === -1) return;
676
+ event.preventDefault();
677
+ 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];
678
+ const panel = next?.dataset.oxPanel;
679
+ if (!next || !panel) return;
680
+ showPanel(element, panel);
681
+ next.focus();
682
+ }
683
+ function prepareA11y(element) {
684
+ const widget = element.querySelector(".ox-code-play");
685
+ if (!widget) return;
686
+ if (!widget.id) widget.id = `ox-code-play-${++widgetId}`;
687
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
688
+ const panelName = tab.dataset.oxPanel;
689
+ const panel = panelName ? element.querySelector(`[data-panel="${panelName}"]`) : null;
690
+ if (!panelName || !panel) continue;
691
+ tab.id ||= `${widget.id}-${panelName}-tab`;
692
+ panel.id ||= `${widget.id}-${panelName}-panel`;
693
+ tab.setAttribute("aria-controls", panel.id);
694
+ panel.setAttribute("aria-labelledby", tab.id);
695
+ panel.removeAttribute("aria-label");
696
+ }
697
+ }
355
698
  function readForm(form) {
356
699
  const data = new FormData(form);
357
700
  const config = {};
@@ -364,7 +707,7 @@ function createCodePlayFromPayloads(root) {
364
707
  let endpoints;
365
708
  for (const element of queryWidgets(root)) try {
366
709
  const payload = decodePayload(element.getAttribute("data-ox-code-play") ?? "");
367
- languages[payload.language] = true;
710
+ languages[payload.language] = languageEnableFromPayload(payload);
368
711
  endpoints = payload.endpoints ?? endpoints;
369
712
  } catch {}
370
713
  return createCodePlay({
@@ -372,6 +715,15 @@ function createCodePlayFromPayloads(root) {
372
715
  endpoints
373
716
  });
374
717
  }
718
+ function languageEnableFromPayload(payload) {
719
+ const enable = {
720
+ execute: payload.capabilities.execute,
721
+ typecheck: payload.capabilities.typecheck,
722
+ config: payload.config
723
+ };
724
+ if (payload.endpoint) enable.endpoint = payload.endpoint;
725
+ return enable;
726
+ }
375
727
  function queryWidgets(root) {
376
728
  return [...root.querySelectorAll("[data-ox-code-play]")];
377
729
  }
@@ -387,6 +739,6 @@ function defaultRoot() {
387
739
  return document;
388
740
  }
389
741
  //#endregion
390
- export { renderConfigHtml as a, renderStderrHtml as c, CODE_PLAY_STYLES as d, renderPlayUi as i, renderStdioHtml as l, mountCodePlay as n, renderDiagnosticsHtml as o, resultPanelToShow as r, renderProvenanceHtml as s, hydrateCodePlay as t, renderTimingHtml as u };
742
+ export { resultRunActionState as _, renderProjectSandboxHtml as a, renderDiagnosticsHtml as c, renderStdioHtml as d, renderTimingHtml as f, readPlayPayload as g, idleRunActionState as h, renderPlayUi as i, renderProvenanceHtml as l, errorRunActionState as m, mountCodePlay as n, renderRunStatusText as o, CODE_PLAY_STYLES as p, resultPanelToShow as r, renderConfigHtml as s, hydrateCodePlay as t, renderStderrHtml as u, runPlayAction as v, runningRunActionState as y };
391
743
 
392
744
  //# sourceMappingURL=hydrate2.mjs.map