@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/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,12 +18,39 @@ 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;
53
+ border-radius: 8px;
27
54
  background: var(--octc-color-bg-alt, var(--octc-color-bg, Canvas));
28
55
  color: var(--octc-color-text, CanvasText);
29
56
  overflow: hidden;
@@ -37,22 +64,147 @@ const CODE_PLAY_STYLES = `
37
64
  padding: 0.6rem 0.8rem;
38
65
  border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 12%, transparent));
39
66
  }
67
+ .ox-code-play__summary {
68
+ display: inline-flex;
69
+ flex: 1 1 12rem;
70
+ min-width: 9rem;
71
+ gap: 0.45rem;
72
+ align-items: baseline;
73
+ }
40
74
  .ox-code-play__lang {
41
75
  font: 600 0.8rem/1.2 ui-sans-serif, system-ui, sans-serif;
42
- margin-right: auto;
43
76
  color: var(--octc-color-text, CanvasText);
44
77
  }
78
+ .ox-code-play__title {
79
+ min-width: 0;
80
+ overflow-wrap: anywhere;
81
+ font: 500 0.78rem/1.25 ui-sans-serif, system-ui, sans-serif;
82
+ opacity: 0.72;
83
+ }
84
+ .ox-code-play__status {
85
+ min-width: 5.25rem;
86
+ text-align: center;
87
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
88
+ border-radius: 6px;
89
+ padding: 0.18rem 0.5rem;
90
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
91
+ color: var(--octc-color-text, CanvasText);
92
+ background: var(--octc-color-bg, Canvas);
93
+ }
94
+ .ox-code-play[data-ox-run-state="running"] .ox-code-play__status {
95
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
96
+ }
97
+ .ox-code-play[data-ox-run-state="error"] .ox-code-play__status,
98
+ .ox-code-play[data-ox-run-state="offline"] .ox-code-play__status {
99
+ color: var(--octc-danger, #b42318);
100
+ }
45
101
  .ox-code-play__toolbar button {
46
102
  appearance: none;
47
103
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 20%, transparent));
48
104
  background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
49
105
  color: var(--octc-color-text, CanvasText);
50
- border-radius: 999px;
106
+ border-radius: 8px;
51
107
  padding: 0.25rem 0.75rem;
52
108
  font: 600 0.75rem/1.4 ui-sans-serif, system-ui, sans-serif;
53
109
  cursor: pointer;
54
110
  }
55
111
  .ox-code-play__toolbar button:disabled { opacity: 0.55; cursor: progress; }
112
+ .ox-code-play__toolbar button:focus-visible,
113
+ .ox-code-play__tabs button:focus-visible,
114
+ .ox-code-play__field input:focus-visible,
115
+ .ox-code-play__field select:focus-visible,
116
+ .ox-code-play__panel:focus-visible {
117
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
118
+ outline-offset: 2px;
119
+ }
120
+ .ox-code-play__runtime {
121
+ display: flex;
122
+ flex-wrap: wrap;
123
+ gap: 0.45rem;
124
+ align-items: center;
125
+ padding: 0.55rem 0.8rem;
126
+ border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 10%, transparent));
127
+ background: color-mix(in srgb, var(--octc-color-text, CanvasText) 4%, transparent);
128
+ }
129
+ .ox-code-play__runtime-chip {
130
+ display: inline-grid;
131
+ gap: 0.1rem;
132
+ min-width: 7.5rem;
133
+ padding: 0.35rem 0.5rem;
134
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
135
+ border-radius: 6px;
136
+ background: var(--octc-color-bg, Canvas);
137
+ }
138
+ .ox-code-play__runtime-chip span {
139
+ font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
140
+ text-transform: uppercase;
141
+ letter-spacing: 0.04em;
142
+ opacity: 0.62;
143
+ }
144
+ .ox-code-play__runtime-chip strong {
145
+ font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
146
+ }
147
+ .ox-code-play__runtime-chip--ok strong { color: var(--octc-color-primary, var(--octc-accent, #4f46e5)); }
148
+ .ox-code-play__runtime-chip--warn strong { color: var(--octc-warning, #b54708); }
149
+ .ox-code-play__runtime-chip--muted strong { opacity: 0.72; }
150
+ .ox-code-play__project {
151
+ display: inline-flex;
152
+ flex: 1 1 18rem;
153
+ min-width: min(100%, 16rem);
154
+ gap: 0.45rem;
155
+ align-items: center;
156
+ flex-wrap: wrap;
157
+ margin-left: auto;
158
+ padding: 0.35rem 0.5rem;
159
+ border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
160
+ border-radius: 6px;
161
+ background: var(--octc-color-bg, Canvas);
162
+ }
163
+ .ox-code-play__project-main,
164
+ .ox-code-play__project-entry {
165
+ display: inline-grid;
166
+ gap: 0.1rem;
167
+ min-width: 0;
168
+ }
169
+ .ox-code-play__project-main { flex: 1 1 8.5rem; }
170
+ .ox-code-play__project-entry { flex: 999 1 9rem; }
171
+ .ox-code-play__project-main span,
172
+ .ox-code-play__project-entry span {
173
+ font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
174
+ text-transform: uppercase;
175
+ letter-spacing: 0.04em;
176
+ opacity: 0.62;
177
+ }
178
+ .ox-code-play__project-main strong,
179
+ .ox-code-play__project-entry strong {
180
+ min-width: 0;
181
+ overflow-wrap: anywhere;
182
+ font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
183
+ }
184
+ .ox-code-play__project-files,
185
+ .ox-code-play__project-warning,
186
+ .ox-code-play__project-link {
187
+ flex: 0 0 auto;
188
+ border-radius: 6px;
189
+ padding: 0.18rem 0.45rem;
190
+ font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
191
+ }
192
+ .ox-code-play__project-files {
193
+ background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
194
+ }
195
+ .ox-code-play__project-warning {
196
+ color: var(--octc-warning, #b54708);
197
+ background: color-mix(in srgb, var(--octc-warning, #b54708) 12%, transparent);
198
+ }
199
+ .ox-code-play__project-link {
200
+ color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
201
+ background: color-mix(in srgb, var(--octc-color-primary, var(--octc-accent, #4f46e5)) 12%, transparent);
202
+ text-decoration: none;
203
+ }
204
+ .ox-code-play__project-link:focus-visible {
205
+ outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
206
+ outline-offset: 2px;
207
+ }
56
208
  .ox-code-play .ox-code { margin: 0; }
57
209
  .ox-code-play__source pre { margin: 0; border: 0; border-radius: 0; }
58
210
  .ox-code-play__tabs {
@@ -66,7 +218,7 @@ const CODE_PLAY_STYLES = `
66
218
  background: transparent;
67
219
  color: var(--octc-color-text, CanvasText);
68
220
  padding: 0.35rem 0.55rem;
69
- border-radius: 8px 8px 0 0;
221
+ border-radius: 6px 6px 0 0;
70
222
  font: 600 0.75rem/1.2 ui-sans-serif, system-ui, sans-serif;
71
223
  cursor: pointer;
72
224
  }
@@ -93,7 +245,7 @@ const CODE_PLAY_STYLES = `
93
245
  .ox-code-play__field input, .ox-code-play__field select {
94
246
  font: inherit;
95
247
  padding: 0.3rem 0.45rem;
96
- border-radius: 8px;
248
+ border-radius: 6px;
97
249
  border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 18%, transparent));
98
250
  background: var(--octc-color-bg, Canvas);
99
251
  color: var(--octc-color-text, CanvasText);
@@ -188,42 +340,141 @@ function renderPlayUi(state) {
188
340
  if (preset === "headless") return "";
189
341
  const panel = state.panel ?? "stdio";
190
342
  const canTypecheck = state.payload.capabilities.typecheck;
343
+ const runState = state.runState ?? (state.busy ? {
344
+ phase: "running",
345
+ action: "execute"
346
+ } : idleRunActionState());
347
+ const isBusy = Boolean(state.busy) || runState.phase === "running";
191
348
  const tabs = renderTabs(state, panel);
192
349
  const viewers = state.payload.viewers;
193
- return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui>
350
+ const label = widgetLabel(state.payload, definition);
351
+ 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
352
  <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>
353
+ <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>
354
+ <span class="ox-code-play__status" data-ox-status role="status" aria-live="polite">${renderRunStatusText(runState)}</span>
355
+ <button type="button" data-ox-action="run" aria-label="${escapeHtml(`Run ${label}`)}"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
356
+ ${canTypecheck ? `<button type="button" data-ox-action="typecheck" aria-label="${escapeHtml(`Typecheck ${label}`)}"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
357
+ <button type="button" data-ox-action="cancel" aria-label="${escapeHtml(`Cancel ${label}`)}"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
199
358
  </div>
359
+ ${renderRuntimeStrip(state.payload, definition)}
200
360
  <div class="ox-code-play__source"></div>
201
361
  ${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>
362
+ ${viewers.stdio ? renderPanel("stdio", "Stdio", panel, preset, `${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}`) : ""}
363
+ ${viewers.stderr ? renderPanel("stderr", "Stderr", panel, preset, renderStderrHtml(state.result)) : ""}
364
+ ${viewers.config ? renderPanel("config", "Config", panel, preset, renderConfigHtml(definition?.configSchema ?? [], state.payload.config)) : ""}
365
+ ${viewers.provenance ? renderPanel("provenance", "Provenance", panel, preset, renderProvenanceHtml(state.result?.provenance)) : ""}
366
+ ${viewers.timing ? renderPanel("timing", "Timing", panel, preset, renderTimingHtml(state.result?.timing)) : ""}
207
367
  </div>`;
208
368
  }
369
+ function renderRunStatusText(state) {
370
+ if (state.phase === "running") return state.action === "typecheck" ? "Typechecking" : "Running";
371
+ if (state.phase === "offline") return "Offline";
372
+ if (state.phase === "error") {
373
+ if (state.result?.status === "timeout") return "Timed out";
374
+ if (state.result?.status === "unsupported") return "Unsupported";
375
+ return "Error";
376
+ }
377
+ if (state.phase === "result") {
378
+ if (state.result?.status === "cancelled") return "Cancelled";
379
+ return "Done";
380
+ }
381
+ return "Ready";
382
+ }
383
+ function renderRuntimeStrip(payload, definition) {
384
+ const runtime = runtimeLabel(payload, definition);
385
+ const executor = executorLabel(payload, definition);
386
+ const checks = payload.capabilities.typecheck ? "Typecheck ready" : "Run only";
387
+ return `<div class="ox-code-play__runtime" aria-label="Code Play runtime">${[
388
+ runtimeChip("Runtime", runtime.label, runtime.kind),
389
+ runtimeChip("Executor", executor.label, executor.kind),
390
+ runtimeChip("Checks", checks, payload.capabilities.typecheck ? "ok" : "muted")
391
+ ].join("")}${renderProjectSandboxHtml(payload.project)}</div>`;
392
+ }
393
+ function runtimeLabel(payload, definition) {
394
+ switch (definition?.backend) {
395
+ case "javascript": return {
396
+ label: "Browser sandbox",
397
+ kind: "ok"
398
+ };
399
+ case "typescript": return {
400
+ label: "TypeScript sandbox",
401
+ kind: "ok"
402
+ };
403
+ case "framework": return {
404
+ label: `${definition.name} iframe preview`,
405
+ kind: "ok"
406
+ };
407
+ case "rust-playground": return {
408
+ label: "Rust Playground",
409
+ kind: payload.endpoints?.rust ? "ok" : "warn"
410
+ };
411
+ case "go-playground": return {
412
+ label: "Go Playground",
413
+ kind: payload.endpoints?.go ? "ok" : "warn"
414
+ };
415
+ case "remote": return payload.endpoint ? {
416
+ label: "Piston-compatible",
417
+ kind: "ok"
418
+ } : {
419
+ label: "Endpoint missing",
420
+ kind: "warn"
421
+ };
422
+ default: return {
423
+ label: definition?.name ?? payload.language,
424
+ kind: "muted"
425
+ };
426
+ }
427
+ }
428
+ function executorLabel(payload, definition) {
429
+ if (!payload.capabilities.execute) return {
430
+ label: "Disabled",
431
+ kind: "warn"
432
+ };
433
+ if (definition?.backend === "remote" && !payload.endpoint) return {
434
+ label: "Configure endpoint",
435
+ kind: "warn"
436
+ };
437
+ return {
438
+ label: "On demand",
439
+ kind: "ok"
440
+ };
441
+ }
442
+ function runtimeChip(label, value, kind) {
443
+ return `<span class="ox-code-play__runtime-chip ox-code-play__runtime-chip--${kind}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></span>`;
444
+ }
445
+ function renderProjectSandboxHtml(project) {
446
+ if (!project) return "";
447
+ const fileCount = `${project.files.length} ${project.files.length === 1 ? "file" : "files"}`;
448
+ const target = project.target === "browser" ? "Browser project" : project.target === "node" ? "Node-like project" : "External project";
449
+ const url = project.openUrl ?? project.fallbackUrl;
450
+ const warnings = project.warnings?.length ? `<span class="ox-code-play__project-warning" title="${escapeAttribute(project.warnings.join("\n"))}">Warnings</span>` : "";
451
+ const link = url ? `<a class="ox-code-play__project-link" href="${escapeAttribute(url)}" target="_blank" rel="noopener noreferrer">Open</a>` : "";
452
+ return `<span class="ox-code-play__project" data-ox-project-provider="${escapeAttribute(project.provider)}">
453
+ <span class="ox-code-play__project-main"><span>${escapeHtml(project.label)}</span><strong>${escapeHtml(target)}</strong></span>
454
+ ${project.entry ? `<span class="ox-code-play__project-entry"><span>Entry</span><strong>${escapeHtml(project.entry)}</strong></span>` : ""}
455
+ <span class="ox-code-play__project-files">${escapeHtml(fileCount)}</span>
456
+ ${warnings}${link}
457
+ </span>`;
458
+ }
209
459
  function renderTabs(state, panel) {
210
460
  if (state.payload.ui === "compact") return "";
211
461
  const viewers = state.payload.viewers;
212
462
  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) : ""
463
+ viewers.stdio ? tab("stdio", "Stdio", panel) : "",
464
+ viewers.stderr ? tab("stderr", "Stderr", panel) : "",
465
+ viewers.config ? tab("config", "Config", panel) : "",
466
+ viewers.provenance ? tab("provenance", "Provenance", panel) : "",
467
+ viewers.timing ? tab("timing", "Timing", panel) : ""
218
468
  ].filter(Boolean).join("");
219
469
  return buttons ? `<div class="ox-code-play__tabs" role="tablist">${buttons}</div>` : "";
220
470
  }
221
471
  function tab(id, label, selected) {
222
- return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${selected === id ? "true" : "false"}">${label}</button>`;
472
+ const isSelected = selected === id;
473
+ return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${isSelected ? "true" : "false"}" tabindex="${isSelected ? "0" : "-1"}">${label}</button>`;
223
474
  }
224
475
  function actionButtonAttrs(action, busy) {
225
476
  const state = actionBusyState(action, busy);
226
- return `${state.disabled ? " disabled" : ""}${state.hidden ? " hidden" : ""}`;
477
+ return `${state.disabled ? " disabled aria-disabled=\"true\"" : ""}${state.hidden ? " hidden" : ""}`;
227
478
  }
228
479
  function actionBusyState(action, busy) {
229
480
  if (action === "cancel") return {
@@ -240,15 +491,30 @@ function applyActionBusy(root, busy) {
240
491
  const state = actionBusyState(button.dataset.oxAction ?? "", busy);
241
492
  button.disabled = state.disabled;
242
493
  button.hidden = state.hidden;
494
+ button.setAttribute("aria-disabled", state.disabled ? "true" : "false");
243
495
  }
496
+ for (const widget of queryPlayWidgets(root)) widget.setAttribute("aria-busy", busy ? "true" : "false");
497
+ }
498
+ function renderPanel(id, label, current, preset, html) {
499
+ 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
500
  }
245
501
  function hidden(current, id, preset) {
246
502
  if (preset === "compact" && (id === "stdio" || id === "stderr")) return "";
247
503
  return current === id ? "" : " hidden";
248
504
  }
505
+ function widgetLabel(payload, definition) {
506
+ const language = definition?.name ?? payload.language;
507
+ return payload.title ? `${payload.title} (${language})` : `${language} Code Play`;
508
+ }
509
+ function queryPlayWidgets(root) {
510
+ const widgets = [...root.querySelectorAll(".ox-code-play")];
511
+ if (typeof HTMLElement !== "undefined" && root instanceof HTMLElement && root.matches(".ox-code-play")) widgets.unshift(root);
512
+ return widgets;
513
+ }
249
514
  //#endregion
250
515
  //#region src/hydrate.ts
251
516
  const STYLE_ID = "ox-code-play-styles";
517
+ let widgetId = 0;
252
518
  function hydrateCodePlay(root = defaultRoot(), options = {}) {
253
519
  ensureStyles();
254
520
  const client = options.client ?? createCodePlayFromPayloads(root);
@@ -261,7 +527,7 @@ function mountCodePlay(element, options = {}) {
261
527
  const payload = readPlayPayload(element.getAttribute("data-ox-code-play") ?? "");
262
528
  if (!payload || payload.ui === "headless") return;
263
529
  const client = options.client ?? createCodePlay({
264
- languages: { [payload.language]: true },
530
+ languages: { [payload.language]: languageEnableFromPayload(payload) },
265
531
  endpoints: payload.endpoints
266
532
  });
267
533
  const source = element.innerHTML;
@@ -273,14 +539,17 @@ function mountCodePlay(element, options = {}) {
273
539
  if (sourceSlot) sourceSlot.innerHTML = source;
274
540
  element.replaceChildren(widget);
275
541
  element.dataset.oxCodePlayMounted = "true";
542
+ element.removeAttribute("inert");
276
543
  bindWidget(element, payload, client);
544
+ prepareA11y(element);
277
545
  }
278
546
  function bindWidget(element, payload, client) {
279
547
  let current = payload;
280
548
  const session = client.createSession({
281
549
  language: payload.language,
282
550
  code: payload.code,
283
- config: payload.config
551
+ config: payload.config,
552
+ project: payload.project
284
553
  });
285
554
  const runButton = element.querySelector("[data-ox-action=\"run\"]");
286
555
  const checkButton = element.querySelector("[data-ox-action=\"typecheck\"]");
@@ -288,6 +557,9 @@ function bindWidget(element, payload, client) {
288
557
  runButton?.addEventListener("click", () => void run("execute"));
289
558
  checkButton?.addEventListener("click", () => void run("typecheck"));
290
559
  cancelButton?.addEventListener("click", () => session.cancel());
560
+ element.addEventListener("keydown", (event) => {
561
+ if (event.target instanceof HTMLElement && event.target.matches("[role=\"tab\"]")) handleTabKeydown(element, event);
562
+ });
291
563
  element.addEventListener("click", (event) => {
292
564
  const target = event.target;
293
565
  if (!(target instanceof HTMLElement)) return;
@@ -306,13 +578,20 @@ function bindWidget(element, payload, client) {
306
578
  async function run(action) {
307
579
  await runPlayAction({
308
580
  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)))
581
+ setBusy: (busy) => {
582
+ applyActionBusy(element, busy);
583
+ if (busy) paintRunState(element, runningRunActionState(action));
584
+ },
585
+ onResult: (result) => paintResult(element, current, result, action),
586
+ onError: (error) => {
587
+ paintRunState(element, errorRunActionState(action, error));
588
+ paintResult(element, current, errorResult(errorMessage(error)), action);
589
+ }
312
590
  });
313
591
  }
314
592
  }
315
- function paintResult(element, _payload, result) {
593
+ function paintResult(element, _payload, result, action) {
594
+ paintRunState(element, resultRunActionState(action, result));
316
595
  const stdio = element.querySelector("[data-panel=\"stdio\"]");
317
596
  if (stdio) {
318
597
  stdio.innerHTML = `${renderDiagnosticsHtml(result)}${renderStdioHtml(result.stdio)}`;
@@ -333,11 +612,27 @@ function paintResult(element, _payload, result) {
333
612
  if (timing) timing.innerHTML = renderTimingHtml(result.timing);
334
613
  const stderr = element.querySelector("[data-panel=\"stderr\"]");
335
614
  if (stderr) stderr.innerHTML = renderStderrHtml(result);
336
- showPanel(element, Boolean(stderr) && (Boolean(result.stderr) || result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) ? "stderr" : "stdio");
615
+ showPanel(element, resultPanelToShow(result, Boolean(stderr)));
616
+ }
617
+ function paintRunState(element, state) {
618
+ const widget = element.querySelector(".ox-code-play");
619
+ widget?.setAttribute("data-ox-run-state", state.phase);
620
+ widget?.setAttribute("aria-busy", state.phase === "running" ? "true" : "false");
621
+ const status = element.querySelector("[data-ox-status]");
622
+ if (status) status.textContent = renderRunStatusText(state);
623
+ }
624
+ function resultPanelToShow(result, hasStderrPanel) {
625
+ if (!hasStderrPanel) return "stdio";
626
+ if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.status !== "ok" && Boolean(result.stderr)) return "stderr";
627
+ return "stdio";
337
628
  }
338
629
  function showPanel(element, panel) {
339
630
  const compact = Boolean(element.querySelector(".ox-code-play--compact"));
340
- for (const tab of element.querySelectorAll("[data-ox-panel]")) tab.setAttribute("aria-selected", tab.getAttribute("data-ox-panel") === panel ? "true" : "false");
631
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
632
+ const selected = tab.getAttribute("data-ox-panel") === panel;
633
+ tab.setAttribute("aria-selected", selected ? "true" : "false");
634
+ tab.tabIndex = selected ? 0 : -1;
635
+ }
341
636
  for (const node of element.querySelectorAll(".ox-code-play__panel")) {
342
637
  const id = node.dataset.panel;
343
638
  if (compact && (id === "stdio" || id === "stderr")) {
@@ -347,6 +642,38 @@ function showPanel(element, panel) {
347
642
  node.hidden = id !== panel;
348
643
  }
349
644
  }
645
+ function handleTabKeydown(element, event) {
646
+ if (![
647
+ "ArrowLeft",
648
+ "ArrowRight",
649
+ "Home",
650
+ "End"
651
+ ].includes(event.key)) return;
652
+ const tabs = [...element.querySelectorAll("[data-ox-panel]")];
653
+ const current = tabs.indexOf(event.target);
654
+ if (current === -1) return;
655
+ event.preventDefault();
656
+ 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];
657
+ const panel = next?.dataset.oxPanel;
658
+ if (!next || !panel) return;
659
+ showPanel(element, panel);
660
+ next.focus();
661
+ }
662
+ function prepareA11y(element) {
663
+ const widget = element.querySelector(".ox-code-play");
664
+ if (!widget) return;
665
+ if (!widget.id) widget.id = `ox-code-play-${++widgetId}`;
666
+ for (const tab of element.querySelectorAll("[data-ox-panel]")) {
667
+ const panelName = tab.dataset.oxPanel;
668
+ const panel = panelName ? element.querySelector(`[data-panel="${panelName}"]`) : null;
669
+ if (!panelName || !panel) continue;
670
+ tab.id ||= `${widget.id}-${panelName}-tab`;
671
+ panel.id ||= `${widget.id}-${panelName}-panel`;
672
+ tab.setAttribute("aria-controls", panel.id);
673
+ panel.setAttribute("aria-labelledby", tab.id);
674
+ panel.removeAttribute("aria-label");
675
+ }
676
+ }
350
677
  function readForm(form) {
351
678
  const data = new FormData(form);
352
679
  const config = {};
@@ -359,7 +686,7 @@ function createCodePlayFromPayloads(root) {
359
686
  let endpoints;
360
687
  for (const element of queryWidgets(root)) try {
361
688
  const payload = decodePayload(element.getAttribute("data-ox-code-play") ?? "");
362
- languages[payload.language] = true;
689
+ languages[payload.language] = languageEnableFromPayload(payload);
363
690
  endpoints = payload.endpoints ?? endpoints;
364
691
  } catch {}
365
692
  return createCodePlay({
@@ -367,6 +694,15 @@ function createCodePlayFromPayloads(root) {
367
694
  endpoints
368
695
  });
369
696
  }
697
+ function languageEnableFromPayload(payload) {
698
+ const enable = {
699
+ execute: payload.capabilities.execute,
700
+ typecheck: payload.capabilities.typecheck,
701
+ config: payload.config
702
+ };
703
+ if (payload.endpoint) enable.endpoint = payload.endpoint;
704
+ return enable;
705
+ }
370
706
  function queryWidgets(root) {
371
707
  return [...root.querySelectorAll("[data-ox-code-play]")];
372
708
  }
@@ -382,6 +718,6 @@ function defaultRoot() {
382
718
  return document;
383
719
  }
384
720
  //#endregion
385
- export { renderDiagnosticsHtml as a, renderStdioHtml as c, renderConfigHtml as i, renderTimingHtml as l, mountCodePlay as n, renderProvenanceHtml as o, renderPlayUi as r, renderStderrHtml as s, hydrateCodePlay as t, CODE_PLAY_STYLES as u };
721
+ 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 };
386
722
 
387
723
  //# sourceMappingURL=hydrate2.mjs.map