@ox-content/code-play 3.0.0-alpha.1 → 3.0.0-alpha.10
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/README.md +19 -6
- package/dist/browser.mjs +481 -57
- package/dist/client.mjs +189 -28
- package/dist/client.mjs.map +1 -1
- package/dist/config.d.mts +15 -2
- package/dist/config.d.mts.map +1 -1
- package/dist/hydrate.d.mts +3 -2
- package/dist/hydrate.d.mts.map +1 -1
- package/dist/hydrate.mjs +2 -2
- package/dist/hydrate2.d.mts +2 -2
- package/dist/hydrate2.mjs +295 -32
- package/dist/hydrate2.mjs.map +1 -1
- package/dist/index.d.mts +24 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/payload.mjs +10 -2
- package/dist/payload.mjs.map +1 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin2.mjs +378 -144
- package/dist/plugin2.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser.mjs
CHANGED
|
@@ -692,10 +692,115 @@ function buildJavaScriptSandboxDocument(code, messageId) {
|
|
|
692
692
|
})();
|
|
693
693
|
<\/script></body></html>`;
|
|
694
694
|
}
|
|
695
|
+
function buildJavaScriptWorkerSource() {
|
|
696
|
+
return `
|
|
697
|
+
(function () {
|
|
698
|
+
function format(args) {
|
|
699
|
+
return Array.prototype.map.call(args, function (value) {
|
|
700
|
+
if (typeof value === "string") return value;
|
|
701
|
+
if (value === undefined) return "undefined";
|
|
702
|
+
if (value === null) return "null";
|
|
703
|
+
try { return JSON.stringify(value); } catch (error) { return String(value); }
|
|
704
|
+
}).join(" ") + "\\n";
|
|
705
|
+
}
|
|
706
|
+
self.onmessage = function (event) {
|
|
707
|
+
var data = event.data || {};
|
|
708
|
+
if (!data.id) return;
|
|
709
|
+
var stdout = [];
|
|
710
|
+
var stderr = [];
|
|
711
|
+
var consoleLike = {
|
|
712
|
+
log: function () { stdout.push(format(arguments)); },
|
|
713
|
+
info: function () { stdout.push(format(arguments)); },
|
|
714
|
+
warn: function () { stderr.push(format(arguments)); },
|
|
715
|
+
error: function () { stderr.push(format(arguments)); }
|
|
716
|
+
};
|
|
717
|
+
try {
|
|
718
|
+
var run = new Function("console", '"use strict";\\n' + String(data.code || ""));
|
|
719
|
+
var value = run(consoleLike);
|
|
720
|
+
self.postMessage({
|
|
721
|
+
id: data.id,
|
|
722
|
+
stdout: stdout,
|
|
723
|
+
stderr: stderr,
|
|
724
|
+
value: value === undefined ? undefined : String(value)
|
|
725
|
+
});
|
|
726
|
+
} catch (error) {
|
|
727
|
+
var message = error && error.message ? String(error.message) : String(error);
|
|
728
|
+
self.postMessage({ id: data.id, stdout: stdout, stderr: stderr, error: message });
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
})();
|
|
732
|
+
`;
|
|
733
|
+
}
|
|
695
734
|
function applySandboxStreams(stdio, message) {
|
|
696
735
|
for (const text of message.stdout ?? []) stdio.push("stdout", text);
|
|
697
736
|
for (const text of message.stderr ?? []) stdio.push("stderr", text);
|
|
698
737
|
}
|
|
738
|
+
var WORKER_UNAVAILABLE_CODE = "ERR_SCRIPT_WORKER_UNAVAILABLE";
|
|
739
|
+
function workerUnavailableError(error) {
|
|
740
|
+
const message = error instanceof Error && error.message ? error.message : "JavaScript worker sandbox is unavailable.";
|
|
741
|
+
return Object.assign(new Error(message), { code: WORKER_UNAVAILABLE_CODE });
|
|
742
|
+
}
|
|
743
|
+
function isSandboxWorkerUnavailable(error) {
|
|
744
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === WORKER_UNAVAILABLE_CODE);
|
|
745
|
+
}
|
|
746
|
+
function canUseSandboxWorker() {
|
|
747
|
+
return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function";
|
|
748
|
+
}
|
|
749
|
+
async function executeInSandboxWorker(code, timeoutMs, stdio, signal) {
|
|
750
|
+
if (!canUseSandboxWorker()) throw workerUnavailableError(/* @__PURE__ */ new Error("JavaScript worker sandbox needs Worker, Blob, and object URLs."));
|
|
751
|
+
if (signal?.aborted) throw abortError();
|
|
752
|
+
const messageId = `ox-code-play-${Math.random().toString(36).slice(2)}`;
|
|
753
|
+
const url = URL.createObjectURL(new Blob([buildJavaScriptWorkerSource()], { type: "text/javascript" }));
|
|
754
|
+
let worker;
|
|
755
|
+
try {
|
|
756
|
+
worker = new Worker(url);
|
|
757
|
+
} catch (error) {
|
|
758
|
+
URL.revokeObjectURL(url);
|
|
759
|
+
throw workerUnavailableError(error);
|
|
760
|
+
}
|
|
761
|
+
return new Promise((resolve, reject) => {
|
|
762
|
+
let settled = false;
|
|
763
|
+
const cleanup = () => {
|
|
764
|
+
if (settled) return;
|
|
765
|
+
settled = true;
|
|
766
|
+
clearTimeout(timer);
|
|
767
|
+
signal?.removeEventListener("abort", onAbort);
|
|
768
|
+
worker.onmessage = null;
|
|
769
|
+
worker.onerror = null;
|
|
770
|
+
worker.terminate();
|
|
771
|
+
URL.revokeObjectURL(url);
|
|
772
|
+
};
|
|
773
|
+
const onAbort = () => {
|
|
774
|
+
cleanup();
|
|
775
|
+
reject(abortError());
|
|
776
|
+
};
|
|
777
|
+
const onMessage = (event) => {
|
|
778
|
+
if (event.data?.id !== messageId) return;
|
|
779
|
+
cleanup();
|
|
780
|
+
applySandboxStreams(stdio, event.data);
|
|
781
|
+
if (event.data.error) {
|
|
782
|
+
reject(new Error(event.data.error));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
resolve(event.data.value);
|
|
786
|
+
};
|
|
787
|
+
const onError = (event) => {
|
|
788
|
+
cleanup();
|
|
789
|
+
reject(workerUnavailableError(new Error(event.message || "JavaScript worker sandbox failed.")));
|
|
790
|
+
};
|
|
791
|
+
const timer = setTimeout(() => {
|
|
792
|
+
cleanup();
|
|
793
|
+
reject(Object.assign(/* @__PURE__ */ new Error("JavaScript execution timed out."), { code: "ERR_SCRIPT_EXECUTION_TIMEOUT" }));
|
|
794
|
+
}, timeoutMs);
|
|
795
|
+
worker.onmessage = onMessage;
|
|
796
|
+
worker.onerror = onError;
|
|
797
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
798
|
+
worker.postMessage({
|
|
799
|
+
id: messageId,
|
|
800
|
+
code
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
}
|
|
699
804
|
async function executeInSandboxIframe(code, timeoutMs, stdio, signal) {
|
|
700
805
|
if (typeof document === "undefined" || typeof window === "undefined") throw new Error("JavaScript sandbox iframe needs a document.");
|
|
701
806
|
if (signal?.aborted) throw abortError();
|
|
@@ -747,24 +852,23 @@ async function runJavaScript(request) {
|
|
|
747
852
|
const tracker = new PhaseTracker();
|
|
748
853
|
tracker.start("execute", "Execute");
|
|
749
854
|
const stdio = new StdioBuffer(tracker.startedAt);
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
runtime: hasNodeVm() ? "node:vm" : "iframe",
|
|
753
|
-
sandbox: hasNodeVm() ? "vm" : "srcdoc"
|
|
754
|
-
} };
|
|
855
|
+
const runtime = currentJavaScriptRuntime();
|
|
856
|
+
let executedRuntime = runtime;
|
|
755
857
|
try {
|
|
756
|
-
const
|
|
858
|
+
const result = await executeScriptWithRuntime(request.code, request.timeoutMs, stdio, request.signal, runtime);
|
|
859
|
+
executedRuntime = result.runtime;
|
|
757
860
|
tracker.stop();
|
|
758
861
|
return {
|
|
759
862
|
status: "ok",
|
|
760
863
|
stdio: stdio.snapshot(),
|
|
761
864
|
diagnostics: [],
|
|
762
|
-
provenance,
|
|
865
|
+
provenance: { execute: javascriptRuntimeProvenance(executedRuntime) },
|
|
763
866
|
timing: tracker.report(),
|
|
764
|
-
value: value === void 0 ? void 0 : String(value)
|
|
867
|
+
value: result.value === void 0 ? void 0 : String(result.value)
|
|
765
868
|
};
|
|
766
869
|
} catch (error) {
|
|
767
870
|
if (isAbortError(error) || request.signal?.aborted) throw error;
|
|
871
|
+
executedRuntime = executionRuntimeFromError(error) ?? executedRuntime;
|
|
768
872
|
tracker.stop();
|
|
769
873
|
const diagnostic = toDiagnostic(error);
|
|
770
874
|
stdio.push("stderr", `${diagnostic.message}\n`);
|
|
@@ -772,12 +876,30 @@ async function runJavaScript(request) {
|
|
|
772
876
|
status: isTimeout(error) ? "timeout" : "error",
|
|
773
877
|
stdio: stdio.snapshot(),
|
|
774
878
|
diagnostics: [diagnostic],
|
|
775
|
-
provenance,
|
|
879
|
+
provenance: { execute: javascriptRuntimeProvenance(executedRuntime) },
|
|
776
880
|
timing: tracker.report()
|
|
777
881
|
};
|
|
778
882
|
}
|
|
779
883
|
}
|
|
780
|
-
async function
|
|
884
|
+
async function executeScriptWithRuntime(code, timeoutMs, stdio, signal, runtime = currentJavaScriptRuntime()) {
|
|
885
|
+
try {
|
|
886
|
+
return {
|
|
887
|
+
value: await executeScriptInRuntime(code, timeoutMs, stdio, signal, runtime),
|
|
888
|
+
runtime
|
|
889
|
+
};
|
|
890
|
+
} catch (error) {
|
|
891
|
+
if (runtime === "worker" && isSandboxWorkerUnavailable(error) && typeof document !== "undefined") try {
|
|
892
|
+
return {
|
|
893
|
+
value: await executeScriptInRuntime(code, timeoutMs, stdio, signal, "iframe"),
|
|
894
|
+
runtime: "iframe"
|
|
895
|
+
};
|
|
896
|
+
} catch (fallbackError) {
|
|
897
|
+
throw withExecutionRuntime(fallbackError, "iframe");
|
|
898
|
+
}
|
|
899
|
+
throw withExecutionRuntime(error, runtime);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async function executeScriptInRuntime(code, timeoutMs, stdio, signal, runtime) {
|
|
781
903
|
const consoleLike = {
|
|
782
904
|
log: (...args) => stdio.push("stdout", formatConsoleArgs(args)),
|
|
783
905
|
info: (...args) => stdio.push("stdout", formatConsoleArgs(args)),
|
|
@@ -785,7 +907,7 @@ async function executeScript(code, timeoutMs, stdio, signal) {
|
|
|
785
907
|
error: (...args) => stdio.push("stderr", formatConsoleArgs(args))
|
|
786
908
|
};
|
|
787
909
|
if (signal?.aborted) throw abortError();
|
|
788
|
-
if (
|
|
910
|
+
if (runtime === "vm") {
|
|
789
911
|
const vm = await import("node:vm");
|
|
790
912
|
const context = vm.createContext({ console: consoleLike });
|
|
791
913
|
return vm.runInContext(code, context, {
|
|
@@ -793,12 +915,45 @@ async function executeScript(code, timeoutMs, stdio, signal) {
|
|
|
793
915
|
displayErrors: true
|
|
794
916
|
});
|
|
795
917
|
}
|
|
918
|
+
if (runtime === "worker") return executeInSandboxWorker(code, timeoutMs, stdio, signal);
|
|
796
919
|
return executeInSandboxIframe(code, timeoutMs, stdio, signal);
|
|
797
920
|
}
|
|
798
|
-
function
|
|
921
|
+
function currentJavaScriptRuntime() {
|
|
922
|
+
return javascriptExecuteRuntime(hasNodeVm(), canUseSandboxWorker(), typeof document !== "undefined");
|
|
923
|
+
}
|
|
924
|
+
function javascriptExecuteRuntime(hasVm, hasWorker, hasDocument) {
|
|
799
925
|
if (hasVm) return "vm";
|
|
926
|
+
if (hasWorker) return "worker";
|
|
800
927
|
if (hasDocument) return "iframe";
|
|
801
|
-
throw new Error("JavaScript execute needs node:vm or a document for the sandbox iframe.");
|
|
928
|
+
throw new Error("JavaScript execute needs node:vm, a browser worker sandbox, or a document for the sandbox iframe.");
|
|
929
|
+
}
|
|
930
|
+
function javascriptRuntimeProvenance(runtime) {
|
|
931
|
+
if (runtime === "vm") return {
|
|
932
|
+
host: "local",
|
|
933
|
+
runtime: "node:vm",
|
|
934
|
+
sandbox: "vm"
|
|
935
|
+
};
|
|
936
|
+
if (runtime === "worker") return {
|
|
937
|
+
host: "local",
|
|
938
|
+
runtime: "web-worker",
|
|
939
|
+
sandbox: "worker"
|
|
940
|
+
};
|
|
941
|
+
return {
|
|
942
|
+
host: "local",
|
|
943
|
+
runtime: "iframe",
|
|
944
|
+
sandbox: "srcdoc"
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function withExecutionRuntime(error, runtime) {
|
|
948
|
+
if (error instanceof Error) return Object.assign(error, { executionRuntime: runtime });
|
|
949
|
+
if (isErrorLike(error)) return Object.assign(error, { executionRuntime: runtime });
|
|
950
|
+
return Object.assign(new Error(String(error)), { executionRuntime: runtime });
|
|
951
|
+
}
|
|
952
|
+
function executionRuntimeFromError(error) {
|
|
953
|
+
if (error && typeof error === "object" && "executionRuntime" in error && isJavaScriptExecutionRuntime(error.executionRuntime)) return error.executionRuntime;
|
|
954
|
+
}
|
|
955
|
+
function isJavaScriptExecutionRuntime(value) {
|
|
956
|
+
return value === "vm" || value === "worker" || value === "iframe";
|
|
802
957
|
}
|
|
803
958
|
function isTimeout(error) {
|
|
804
959
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT");
|
|
@@ -1065,9 +1220,12 @@ async function runTypeScript(request) {
|
|
|
1065
1220
|
const stdio = new StdioBuffer(tracker.startedAt);
|
|
1066
1221
|
tracker.start("compile", "Strip types");
|
|
1067
1222
|
const javascript = stripTypeScript(request.code);
|
|
1223
|
+
const runtime = currentJavaScriptRuntime();
|
|
1224
|
+
let executedRuntime = runtime;
|
|
1068
1225
|
tracker.start("execute", "Execute");
|
|
1069
1226
|
try {
|
|
1070
|
-
const
|
|
1227
|
+
const result = await executeScriptWithRuntime(javascript, request.timeoutMs, stdio, request.signal, runtime);
|
|
1228
|
+
executedRuntime = result.runtime;
|
|
1071
1229
|
tracker.stop();
|
|
1072
1230
|
return {
|
|
1073
1231
|
status: "ok",
|
|
@@ -1078,14 +1236,10 @@ async function runTypeScript(request) {
|
|
|
1078
1236
|
host: "local",
|
|
1079
1237
|
runtime: "strip-types"
|
|
1080
1238
|
},
|
|
1081
|
-
execute:
|
|
1082
|
-
host: "local",
|
|
1083
|
-
runtime: hasNodeVm() ? "node:vm" : "iframe",
|
|
1084
|
-
sandbox: hasNodeVm() ? "vm" : "srcdoc"
|
|
1085
|
-
}
|
|
1239
|
+
execute: javascriptRuntimeProvenance(executedRuntime)
|
|
1086
1240
|
},
|
|
1087
1241
|
timing: tracker.report(),
|
|
1088
|
-
value: value === void 0 ? void 0 : String(value)
|
|
1242
|
+
value: result.value === void 0 ? void 0 : String(result.value)
|
|
1089
1243
|
};
|
|
1090
1244
|
} catch (error) {
|
|
1091
1245
|
if (isAbortError(error) || request.signal?.aborted) throw error;
|
|
@@ -1105,11 +1259,7 @@ async function runTypeScript(request) {
|
|
|
1105
1259
|
host: "local",
|
|
1106
1260
|
runtime: "strip-types"
|
|
1107
1261
|
},
|
|
1108
|
-
execute:
|
|
1109
|
-
host: "local",
|
|
1110
|
-
runtime: hasNodeVm() ? "node:vm" : "iframe",
|
|
1111
|
-
sandbox: hasNodeVm() ? "vm" : "srcdoc"
|
|
1112
|
-
}
|
|
1262
|
+
execute: javascriptRuntimeProvenance(executedRuntime)
|
|
1113
1263
|
},
|
|
1114
1264
|
timing: tracker.report()
|
|
1115
1265
|
};
|
|
@@ -1282,9 +1432,12 @@ function errorMessage(error) {
|
|
|
1282
1432
|
}
|
|
1283
1433
|
function friendlyTransportMessage(error) {
|
|
1284
1434
|
const message = errorMessage(error);
|
|
1285
|
-
if ((error
|
|
1435
|
+
if (isOfflineError(error)) return "The executor is offline or unreachable from this page (for example, CORS). Set endpoints to a host that allows browser POST, or use the Vite dev proxy.";
|
|
1286
1436
|
return message;
|
|
1287
1437
|
}
|
|
1438
|
+
function transportFailureStatus(error) {
|
|
1439
|
+
return isOfflineError(error) ? "offline" : "error";
|
|
1440
|
+
}
|
|
1288
1441
|
function errorResult(message, source = "code-play", status = "error") {
|
|
1289
1442
|
return withStdioText({
|
|
1290
1443
|
status,
|
|
@@ -1298,6 +1451,14 @@ function errorResult(message, source = "code-play", status = "error") {
|
|
|
1298
1451
|
timing: emptyTiming()
|
|
1299
1452
|
});
|
|
1300
1453
|
}
|
|
1454
|
+
function isOfflineError(error) {
|
|
1455
|
+
const message = errorMessage(error);
|
|
1456
|
+
const name = error && typeof error === "object" && "name" in error ? String(error.name) : void 0;
|
|
1457
|
+
const typeError = error instanceof TypeError || name === "TypeError";
|
|
1458
|
+
if (name === "MissingTransportError") return true;
|
|
1459
|
+
if (typeError && /failed to fetch|networkerror|load failed|network request failed/i.test(message)) return true;
|
|
1460
|
+
return /\boffline\b|no code play transport|network request failed/i.test(message);
|
|
1461
|
+
}
|
|
1301
1462
|
//#endregion
|
|
1302
1463
|
//#region src/session.ts
|
|
1303
1464
|
var CodePlaySession = class {
|
|
@@ -1375,7 +1536,7 @@ var CodePlaySession = class {
|
|
|
1375
1536
|
return this.finish(result);
|
|
1376
1537
|
} catch (error) {
|
|
1377
1538
|
if (signal.aborted || isAbortError(error)) return this.finish(errorResult("Run cancelled.", "code-play", "cancelled"));
|
|
1378
|
-
return this.finish(errorResult(friendlyTransportMessage(error)));
|
|
1539
|
+
return this.finish(errorResult(friendlyTransportMessage(error), "code-play", transportFailureStatus(error)));
|
|
1379
1540
|
}
|
|
1380
1541
|
}
|
|
1381
1542
|
finish(result) {
|
|
@@ -1460,12 +1621,39 @@ async function runPlayAction(input) {
|
|
|
1460
1621
|
input.setBusy(false);
|
|
1461
1622
|
}
|
|
1462
1623
|
}
|
|
1624
|
+
function idleRunActionState() {
|
|
1625
|
+
return { phase: "idle" };
|
|
1626
|
+
}
|
|
1627
|
+
function runningRunActionState(action, startedAtMs = Date.now()) {
|
|
1628
|
+
return {
|
|
1629
|
+
phase: "running",
|
|
1630
|
+
action,
|
|
1631
|
+
startedAtMs
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
function resultRunActionState(action, result, finishedAtMs = Date.now()) {
|
|
1635
|
+
return {
|
|
1636
|
+
phase: result.status === "offline" ? "offline" : result.status === "ok" || result.status === "cancelled" ? "result" : "error",
|
|
1637
|
+
action,
|
|
1638
|
+
result,
|
|
1639
|
+
message: result.diagnostics[0]?.message,
|
|
1640
|
+
finishedAtMs
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function errorRunActionState(action, error, finishedAtMs = Date.now()) {
|
|
1644
|
+
return {
|
|
1645
|
+
phase: "error",
|
|
1646
|
+
action,
|
|
1647
|
+
message: errorMessage(error),
|
|
1648
|
+
finishedAtMs
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1463
1651
|
//#endregion
|
|
1464
1652
|
//#region src/styles.ts
|
|
1465
1653
|
var CODE_PLAY_STYLES = `
|
|
1466
1654
|
.ox-code-play {
|
|
1467
1655
|
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
|
|
1468
|
-
border-radius:
|
|
1656
|
+
border-radius: 8px;
|
|
1469
1657
|
background: var(--octc-color-bg-alt, var(--octc-color-bg, Canvas));
|
|
1470
1658
|
color: var(--octc-color-text, CanvasText);
|
|
1471
1659
|
overflow: hidden;
|
|
@@ -1479,22 +1667,89 @@ var CODE_PLAY_STYLES = `
|
|
|
1479
1667
|
padding: 0.6rem 0.8rem;
|
|
1480
1668
|
border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 12%, transparent));
|
|
1481
1669
|
}
|
|
1670
|
+
.ox-code-play__summary {
|
|
1671
|
+
display: inline-flex;
|
|
1672
|
+
flex: 1 1 12rem;
|
|
1673
|
+
min-width: 9rem;
|
|
1674
|
+
gap: 0.45rem;
|
|
1675
|
+
align-items: baseline;
|
|
1676
|
+
}
|
|
1482
1677
|
.ox-code-play__lang {
|
|
1483
1678
|
font: 600 0.8rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1484
|
-
margin-right: auto;
|
|
1485
1679
|
color: var(--octc-color-text, CanvasText);
|
|
1486
1680
|
}
|
|
1681
|
+
.ox-code-play__title {
|
|
1682
|
+
min-width: 0;
|
|
1683
|
+
overflow-wrap: anywhere;
|
|
1684
|
+
font: 500 0.78rem/1.25 ui-sans-serif, system-ui, sans-serif;
|
|
1685
|
+
opacity: 0.72;
|
|
1686
|
+
}
|
|
1687
|
+
.ox-code-play__status {
|
|
1688
|
+
min-width: 5.25rem;
|
|
1689
|
+
text-align: center;
|
|
1690
|
+
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
|
|
1691
|
+
border-radius: 6px;
|
|
1692
|
+
padding: 0.18rem 0.5rem;
|
|
1693
|
+
font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
|
|
1694
|
+
color: var(--octc-color-text, CanvasText);
|
|
1695
|
+
background: var(--octc-color-bg, Canvas);
|
|
1696
|
+
}
|
|
1697
|
+
.ox-code-play[data-ox-run-state="running"] .ox-code-play__status {
|
|
1698
|
+
color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
|
|
1699
|
+
}
|
|
1700
|
+
.ox-code-play[data-ox-run-state="error"] .ox-code-play__status,
|
|
1701
|
+
.ox-code-play[data-ox-run-state="offline"] .ox-code-play__status {
|
|
1702
|
+
color: var(--octc-danger, #b42318);
|
|
1703
|
+
}
|
|
1487
1704
|
.ox-code-play__toolbar button {
|
|
1488
1705
|
appearance: none;
|
|
1489
1706
|
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 20%, transparent));
|
|
1490
1707
|
background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
|
|
1491
1708
|
color: var(--octc-color-text, CanvasText);
|
|
1492
|
-
border-radius:
|
|
1709
|
+
border-radius: 8px;
|
|
1493
1710
|
padding: 0.25rem 0.75rem;
|
|
1494
1711
|
font: 600 0.75rem/1.4 ui-sans-serif, system-ui, sans-serif;
|
|
1495
1712
|
cursor: pointer;
|
|
1496
1713
|
}
|
|
1497
1714
|
.ox-code-play__toolbar button:disabled { opacity: 0.55; cursor: progress; }
|
|
1715
|
+
.ox-code-play__toolbar button:focus-visible,
|
|
1716
|
+
.ox-code-play__tabs button:focus-visible,
|
|
1717
|
+
.ox-code-play__field input:focus-visible,
|
|
1718
|
+
.ox-code-play__field select:focus-visible,
|
|
1719
|
+
.ox-code-play__panel:focus-visible {
|
|
1720
|
+
outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
|
|
1721
|
+
outline-offset: 2px;
|
|
1722
|
+
}
|
|
1723
|
+
.ox-code-play__runtime {
|
|
1724
|
+
display: flex;
|
|
1725
|
+
flex-wrap: wrap;
|
|
1726
|
+
gap: 0.45rem;
|
|
1727
|
+
align-items: center;
|
|
1728
|
+
padding: 0.55rem 0.8rem;
|
|
1729
|
+
border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 10%, transparent));
|
|
1730
|
+
background: color-mix(in srgb, var(--octc-color-text, CanvasText) 4%, transparent);
|
|
1731
|
+
}
|
|
1732
|
+
.ox-code-play__runtime-chip {
|
|
1733
|
+
display: inline-grid;
|
|
1734
|
+
gap: 0.1rem;
|
|
1735
|
+
min-width: 7.5rem;
|
|
1736
|
+
padding: 0.35rem 0.5rem;
|
|
1737
|
+
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
|
|
1738
|
+
border-radius: 6px;
|
|
1739
|
+
background: var(--octc-color-bg, Canvas);
|
|
1740
|
+
}
|
|
1741
|
+
.ox-code-play__runtime-chip span {
|
|
1742
|
+
font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
|
|
1743
|
+
text-transform: uppercase;
|
|
1744
|
+
letter-spacing: 0.04em;
|
|
1745
|
+
opacity: 0.62;
|
|
1746
|
+
}
|
|
1747
|
+
.ox-code-play__runtime-chip strong {
|
|
1748
|
+
font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1749
|
+
}
|
|
1750
|
+
.ox-code-play__runtime-chip--ok strong { color: var(--octc-color-primary, var(--octc-accent, #4f46e5)); }
|
|
1751
|
+
.ox-code-play__runtime-chip--warn strong { color: var(--octc-warning, #b54708); }
|
|
1752
|
+
.ox-code-play__runtime-chip--muted strong { opacity: 0.72; }
|
|
1498
1753
|
.ox-code-play .ox-code { margin: 0; }
|
|
1499
1754
|
.ox-code-play__source pre { margin: 0; border: 0; border-radius: 0; }
|
|
1500
1755
|
.ox-code-play__tabs {
|
|
@@ -1508,7 +1763,7 @@ var CODE_PLAY_STYLES = `
|
|
|
1508
1763
|
background: transparent;
|
|
1509
1764
|
color: var(--octc-color-text, CanvasText);
|
|
1510
1765
|
padding: 0.35rem 0.55rem;
|
|
1511
|
-
border-radius:
|
|
1766
|
+
border-radius: 6px 6px 0 0;
|
|
1512
1767
|
font: 600 0.75rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1513
1768
|
cursor: pointer;
|
|
1514
1769
|
}
|
|
@@ -1535,7 +1790,7 @@ var CODE_PLAY_STYLES = `
|
|
|
1535
1790
|
.ox-code-play__field input, .ox-code-play__field select {
|
|
1536
1791
|
font: inherit;
|
|
1537
1792
|
padding: 0.3rem 0.45rem;
|
|
1538
|
-
border-radius:
|
|
1793
|
+
border-radius: 6px;
|
|
1539
1794
|
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 18%, transparent));
|
|
1540
1795
|
background: var(--octc-color-bg, Canvas);
|
|
1541
1796
|
color: var(--octc-color-text, CanvasText);
|
|
@@ -1630,42 +1885,127 @@ function renderPlayUi(state) {
|
|
|
1630
1885
|
if (preset === "headless") return "";
|
|
1631
1886
|
const panel = state.panel ?? "stdio";
|
|
1632
1887
|
const canTypecheck = state.payload.capabilities.typecheck;
|
|
1888
|
+
const runState = state.runState ?? (state.busy ? {
|
|
1889
|
+
phase: "running",
|
|
1890
|
+
action: "execute"
|
|
1891
|
+
} : idleRunActionState());
|
|
1892
|
+
const isBusy = Boolean(state.busy) || runState.phase === "running";
|
|
1633
1893
|
const tabs = renderTabs(state, panel);
|
|
1634
1894
|
const viewers = state.payload.viewers;
|
|
1635
|
-
|
|
1895
|
+
const label = widgetLabel(state.payload, definition);
|
|
1896
|
+
return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui data-ox-run-state="${runState.phase}" role="region" aria-label="${escapeHtml(label)}" aria-busy="${isBusy ? "true" : "false"}">
|
|
1636
1897
|
<div class="ox-code-play__toolbar">
|
|
1637
|
-
<span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>
|
|
1638
|
-
<
|
|
1639
|
-
|
|
1640
|
-
|
|
1898
|
+
<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>
|
|
1899
|
+
<span class="ox-code-play__status" data-ox-status role="status" aria-live="polite">${renderRunStatusText(runState)}</span>
|
|
1900
|
+
<button type="button" data-ox-action="run" aria-label="${escapeHtml(`Run ${label}`)}"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
|
|
1901
|
+
${canTypecheck ? `<button type="button" data-ox-action="typecheck" aria-label="${escapeHtml(`Typecheck ${label}`)}"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
|
|
1902
|
+
<button type="button" data-ox-action="cancel" aria-label="${escapeHtml(`Cancel ${label}`)}"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
|
|
1641
1903
|
</div>
|
|
1904
|
+
${renderRuntimeStrip(state.payload, definition)}
|
|
1642
1905
|
<div class="ox-code-play__source"></div>
|
|
1643
1906
|
${tabs}
|
|
1644
|
-
${viewers.stdio ?
|
|
1645
|
-
${viewers.stderr ?
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1907
|
+
${viewers.stdio ? renderPanel("stdio", "Stdio", panel, preset, `${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}`) : ""}
|
|
1908
|
+
${viewers.stderr ? renderPanel("stderr", "Stderr", panel, preset, renderStderrHtml(state.result)) : ""}
|
|
1909
|
+
${viewers.config ? renderPanel("config", "Config", panel, preset, renderConfigHtml(definition?.configSchema ?? [], state.payload.config)) : ""}
|
|
1910
|
+
${viewers.provenance ? renderPanel("provenance", "Provenance", panel, preset, renderProvenanceHtml(state.result?.provenance)) : ""}
|
|
1911
|
+
${viewers.timing ? renderPanel("timing", "Timing", panel, preset, renderTimingHtml(state.result?.timing)) : ""}
|
|
1649
1912
|
</div>`;
|
|
1650
1913
|
}
|
|
1914
|
+
function renderRunStatusText(state) {
|
|
1915
|
+
if (state.phase === "running") return state.action === "typecheck" ? "Typechecking" : "Running";
|
|
1916
|
+
if (state.phase === "offline") return "Offline";
|
|
1917
|
+
if (state.phase === "error") {
|
|
1918
|
+
if (state.result?.status === "timeout") return "Timed out";
|
|
1919
|
+
if (state.result?.status === "unsupported") return "Unsupported";
|
|
1920
|
+
return "Error";
|
|
1921
|
+
}
|
|
1922
|
+
if (state.phase === "result") {
|
|
1923
|
+
if (state.result?.status === "cancelled") return "Cancelled";
|
|
1924
|
+
return "Done";
|
|
1925
|
+
}
|
|
1926
|
+
return "Ready";
|
|
1927
|
+
}
|
|
1928
|
+
function renderRuntimeStrip(payload, definition) {
|
|
1929
|
+
const runtime = runtimeLabel(payload, definition);
|
|
1930
|
+
const executor = executorLabel(payload, definition);
|
|
1931
|
+
const checks = payload.capabilities.typecheck ? "Typecheck ready" : "Run only";
|
|
1932
|
+
return `<div class="ox-code-play__runtime" aria-label="Code Play runtime">${[
|
|
1933
|
+
runtimeChip("Runtime", runtime.label, runtime.kind),
|
|
1934
|
+
runtimeChip("Executor", executor.label, executor.kind),
|
|
1935
|
+
runtimeChip("Checks", checks, payload.capabilities.typecheck ? "ok" : "muted")
|
|
1936
|
+
].join("")}</div>`;
|
|
1937
|
+
}
|
|
1938
|
+
function runtimeLabel(payload, definition) {
|
|
1939
|
+
switch (definition?.backend) {
|
|
1940
|
+
case "javascript": return {
|
|
1941
|
+
label: "Browser sandbox",
|
|
1942
|
+
kind: "ok"
|
|
1943
|
+
};
|
|
1944
|
+
case "typescript": return {
|
|
1945
|
+
label: "TypeScript sandbox",
|
|
1946
|
+
kind: "ok"
|
|
1947
|
+
};
|
|
1948
|
+
case "framework": return {
|
|
1949
|
+
label: `${definition.name} iframe preview`,
|
|
1950
|
+
kind: "ok"
|
|
1951
|
+
};
|
|
1952
|
+
case "rust-playground": return {
|
|
1953
|
+
label: "Rust Playground",
|
|
1954
|
+
kind: payload.endpoints?.rust ? "ok" : "warn"
|
|
1955
|
+
};
|
|
1956
|
+
case "go-playground": return {
|
|
1957
|
+
label: "Go Playground",
|
|
1958
|
+
kind: payload.endpoints?.go ? "ok" : "warn"
|
|
1959
|
+
};
|
|
1960
|
+
case "remote": return payload.endpoint ? {
|
|
1961
|
+
label: "Piston-compatible",
|
|
1962
|
+
kind: "ok"
|
|
1963
|
+
} : {
|
|
1964
|
+
label: "Endpoint missing",
|
|
1965
|
+
kind: "warn"
|
|
1966
|
+
};
|
|
1967
|
+
default: return {
|
|
1968
|
+
label: definition?.name ?? payload.language,
|
|
1969
|
+
kind: "muted"
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
function executorLabel(payload, definition) {
|
|
1974
|
+
if (!payload.capabilities.execute) return {
|
|
1975
|
+
label: "Disabled",
|
|
1976
|
+
kind: "warn"
|
|
1977
|
+
};
|
|
1978
|
+
if (definition?.backend === "remote" && !payload.endpoint) return {
|
|
1979
|
+
label: "Configure endpoint",
|
|
1980
|
+
kind: "warn"
|
|
1981
|
+
};
|
|
1982
|
+
return {
|
|
1983
|
+
label: "On demand",
|
|
1984
|
+
kind: "ok"
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
function runtimeChip(label, value, kind) {
|
|
1988
|
+
return `<span class="ox-code-play__runtime-chip ox-code-play__runtime-chip--${kind}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></span>`;
|
|
1989
|
+
}
|
|
1651
1990
|
function renderTabs(state, panel) {
|
|
1652
1991
|
if (state.payload.ui === "compact") return "";
|
|
1653
1992
|
const viewers = state.payload.viewers;
|
|
1654
1993
|
const buttons = [
|
|
1655
|
-
viewers.stdio ? tab("stdio", "
|
|
1656
|
-
viewers.stderr ? tab("stderr", "
|
|
1657
|
-
viewers.config ? tab("config", "
|
|
1658
|
-
viewers.provenance ? tab("provenance", "
|
|
1659
|
-
viewers.timing ? tab("timing", "
|
|
1994
|
+
viewers.stdio ? tab("stdio", "Stdio", panel) : "",
|
|
1995
|
+
viewers.stderr ? tab("stderr", "Stderr", panel) : "",
|
|
1996
|
+
viewers.config ? tab("config", "Config", panel) : "",
|
|
1997
|
+
viewers.provenance ? tab("provenance", "Provenance", panel) : "",
|
|
1998
|
+
viewers.timing ? tab("timing", "Timing", panel) : ""
|
|
1660
1999
|
].filter(Boolean).join("");
|
|
1661
2000
|
return buttons ? `<div class="ox-code-play__tabs" role="tablist">${buttons}</div>` : "";
|
|
1662
2001
|
}
|
|
1663
2002
|
function tab(id, label, selected) {
|
|
1664
|
-
|
|
2003
|
+
const isSelected = selected === id;
|
|
2004
|
+
return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${isSelected ? "true" : "false"}" tabindex="${isSelected ? "0" : "-1"}">${label}</button>`;
|
|
1665
2005
|
}
|
|
1666
2006
|
function actionButtonAttrs(action, busy) {
|
|
1667
2007
|
const state = actionBusyState(action, busy);
|
|
1668
|
-
return `${state.disabled ? " disabled" : ""}${state.hidden ? " hidden" : ""}`;
|
|
2008
|
+
return `${state.disabled ? " disabled aria-disabled=\"true\"" : ""}${state.hidden ? " hidden" : ""}`;
|
|
1669
2009
|
}
|
|
1670
2010
|
function actionBusyState(action, busy) {
|
|
1671
2011
|
if (action === "cancel") return {
|
|
@@ -1682,15 +2022,30 @@ function applyActionBusy(root, busy) {
|
|
|
1682
2022
|
const state = actionBusyState(button.dataset.oxAction ?? "", busy);
|
|
1683
2023
|
button.disabled = state.disabled;
|
|
1684
2024
|
button.hidden = state.hidden;
|
|
2025
|
+
button.setAttribute("aria-disabled", state.disabled ? "true" : "false");
|
|
1685
2026
|
}
|
|
2027
|
+
for (const widget of queryPlayWidgets(root)) widget.setAttribute("aria-busy", busy ? "true" : "false");
|
|
2028
|
+
}
|
|
2029
|
+
function renderPanel(id, label, current, preset, html) {
|
|
2030
|
+
return `<div class="ox-code-play__panel" role="tabpanel" tabindex="0" aria-label="${escapeHtml(label)}" data-panel="${id}"${hidden(current, id, preset)}>${html}</div>`;
|
|
1686
2031
|
}
|
|
1687
2032
|
function hidden(current, id, preset) {
|
|
1688
2033
|
if (preset === "compact" && (id === "stdio" || id === "stderr")) return "";
|
|
1689
2034
|
return current === id ? "" : " hidden";
|
|
1690
2035
|
}
|
|
2036
|
+
function widgetLabel(payload, definition) {
|
|
2037
|
+
const language = definition?.name ?? payload.language;
|
|
2038
|
+
return payload.title ? `${payload.title} (${language})` : `${language} Code Play`;
|
|
2039
|
+
}
|
|
2040
|
+
function queryPlayWidgets(root) {
|
|
2041
|
+
const widgets = [...root.querySelectorAll(".ox-code-play")];
|
|
2042
|
+
if (typeof HTMLElement !== "undefined" && root instanceof HTMLElement && root.matches(".ox-code-play")) widgets.unshift(root);
|
|
2043
|
+
return widgets;
|
|
2044
|
+
}
|
|
1691
2045
|
//#endregion
|
|
1692
2046
|
//#region src/hydrate.ts
|
|
1693
2047
|
var STYLE_ID = "ox-code-play-styles";
|
|
2048
|
+
var widgetId = 0;
|
|
1694
2049
|
function hydrateCodePlay(root = defaultRoot(), options = {}) {
|
|
1695
2050
|
ensureStyles();
|
|
1696
2051
|
const client = options.client ?? createCodePlayFromPayloads(root);
|
|
@@ -1703,7 +2058,7 @@ function mountCodePlay(element, options = {}) {
|
|
|
1703
2058
|
const payload = readPlayPayload(element.getAttribute("data-ox-code-play") ?? "");
|
|
1704
2059
|
if (!payload || payload.ui === "headless") return;
|
|
1705
2060
|
const client = options.client ?? createCodePlay({
|
|
1706
|
-
languages: { [payload.language]:
|
|
2061
|
+
languages: { [payload.language]: languageEnableFromPayload(payload) },
|
|
1707
2062
|
endpoints: payload.endpoints
|
|
1708
2063
|
});
|
|
1709
2064
|
const source = element.innerHTML;
|
|
@@ -1715,7 +2070,9 @@ function mountCodePlay(element, options = {}) {
|
|
|
1715
2070
|
if (sourceSlot) sourceSlot.innerHTML = source;
|
|
1716
2071
|
element.replaceChildren(widget);
|
|
1717
2072
|
element.dataset.oxCodePlayMounted = "true";
|
|
2073
|
+
element.removeAttribute("inert");
|
|
1718
2074
|
bindWidget(element, payload, client);
|
|
2075
|
+
prepareA11y(element);
|
|
1719
2076
|
}
|
|
1720
2077
|
function bindWidget(element, payload, client) {
|
|
1721
2078
|
let current = payload;
|
|
@@ -1730,6 +2087,9 @@ function bindWidget(element, payload, client) {
|
|
|
1730
2087
|
runButton?.addEventListener("click", () => void run("execute"));
|
|
1731
2088
|
checkButton?.addEventListener("click", () => void run("typecheck"));
|
|
1732
2089
|
cancelButton?.addEventListener("click", () => session.cancel());
|
|
2090
|
+
element.addEventListener("keydown", (event) => {
|
|
2091
|
+
if (event.target instanceof HTMLElement && event.target.matches("[role=\"tab\"]")) handleTabKeydown(element, event);
|
|
2092
|
+
});
|
|
1733
2093
|
element.addEventListener("click", (event) => {
|
|
1734
2094
|
const target = event.target;
|
|
1735
2095
|
if (!(target instanceof HTMLElement)) return;
|
|
@@ -1748,13 +2108,20 @@ function bindWidget(element, payload, client) {
|
|
|
1748
2108
|
async function run(action) {
|
|
1749
2109
|
await runPlayAction({
|
|
1750
2110
|
action: () => action === "typecheck" ? session.typecheck() : session.run(),
|
|
1751
|
-
setBusy: (busy) =>
|
|
1752
|
-
|
|
1753
|
-
|
|
2111
|
+
setBusy: (busy) => {
|
|
2112
|
+
applyActionBusy(element, busy);
|
|
2113
|
+
if (busy) paintRunState(element, runningRunActionState(action));
|
|
2114
|
+
},
|
|
2115
|
+
onResult: (result) => paintResult(element, current, result, action),
|
|
2116
|
+
onError: (error) => {
|
|
2117
|
+
paintRunState(element, errorRunActionState(action, error));
|
|
2118
|
+
paintResult(element, current, errorResult(errorMessage(error)), action);
|
|
2119
|
+
}
|
|
1754
2120
|
});
|
|
1755
2121
|
}
|
|
1756
2122
|
}
|
|
1757
|
-
function paintResult(element, _payload, result) {
|
|
2123
|
+
function paintResult(element, _payload, result, action) {
|
|
2124
|
+
paintRunState(element, resultRunActionState(action, result));
|
|
1758
2125
|
const stdio = element.querySelector("[data-panel=\"stdio\"]");
|
|
1759
2126
|
if (stdio) {
|
|
1760
2127
|
stdio.innerHTML = `${renderDiagnosticsHtml(result)}${renderStdioHtml(result.stdio)}`;
|
|
@@ -1775,11 +2142,27 @@ function paintResult(element, _payload, result) {
|
|
|
1775
2142
|
if (timing) timing.innerHTML = renderTimingHtml(result.timing);
|
|
1776
2143
|
const stderr = element.querySelector("[data-panel=\"stderr\"]");
|
|
1777
2144
|
if (stderr) stderr.innerHTML = renderStderrHtml(result);
|
|
1778
|
-
showPanel(element,
|
|
2145
|
+
showPanel(element, resultPanelToShow(result, Boolean(stderr)));
|
|
2146
|
+
}
|
|
2147
|
+
function paintRunState(element, state) {
|
|
2148
|
+
const widget = element.querySelector(".ox-code-play");
|
|
2149
|
+
widget?.setAttribute("data-ox-run-state", state.phase);
|
|
2150
|
+
widget?.setAttribute("aria-busy", state.phase === "running" ? "true" : "false");
|
|
2151
|
+
const status = element.querySelector("[data-ox-status]");
|
|
2152
|
+
if (status) status.textContent = renderRunStatusText(state);
|
|
2153
|
+
}
|
|
2154
|
+
function resultPanelToShow(result, hasStderrPanel) {
|
|
2155
|
+
if (!hasStderrPanel) return "stdio";
|
|
2156
|
+
if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.status !== "ok" && Boolean(result.stderr)) return "stderr";
|
|
2157
|
+
return "stdio";
|
|
1779
2158
|
}
|
|
1780
2159
|
function showPanel(element, panel) {
|
|
1781
2160
|
const compact = Boolean(element.querySelector(".ox-code-play--compact"));
|
|
1782
|
-
for (const tab of element.querySelectorAll("[data-ox-panel]"))
|
|
2161
|
+
for (const tab of element.querySelectorAll("[data-ox-panel]")) {
|
|
2162
|
+
const selected = tab.getAttribute("data-ox-panel") === panel;
|
|
2163
|
+
tab.setAttribute("aria-selected", selected ? "true" : "false");
|
|
2164
|
+
tab.tabIndex = selected ? 0 : -1;
|
|
2165
|
+
}
|
|
1783
2166
|
for (const node of element.querySelectorAll(".ox-code-play__panel")) {
|
|
1784
2167
|
const id = node.dataset.panel;
|
|
1785
2168
|
if (compact && (id === "stdio" || id === "stderr")) {
|
|
@@ -1789,6 +2172,38 @@ function showPanel(element, panel) {
|
|
|
1789
2172
|
node.hidden = id !== panel;
|
|
1790
2173
|
}
|
|
1791
2174
|
}
|
|
2175
|
+
function handleTabKeydown(element, event) {
|
|
2176
|
+
if (![
|
|
2177
|
+
"ArrowLeft",
|
|
2178
|
+
"ArrowRight",
|
|
2179
|
+
"Home",
|
|
2180
|
+
"End"
|
|
2181
|
+
].includes(event.key)) return;
|
|
2182
|
+
const tabs = [...element.querySelectorAll("[data-ox-panel]")];
|
|
2183
|
+
const current = tabs.indexOf(event.target);
|
|
2184
|
+
if (current === -1) return;
|
|
2185
|
+
event.preventDefault();
|
|
2186
|
+
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];
|
|
2187
|
+
const panel = next?.dataset.oxPanel;
|
|
2188
|
+
if (!next || !panel) return;
|
|
2189
|
+
showPanel(element, panel);
|
|
2190
|
+
next.focus();
|
|
2191
|
+
}
|
|
2192
|
+
function prepareA11y(element) {
|
|
2193
|
+
const widget = element.querySelector(".ox-code-play");
|
|
2194
|
+
if (!widget) return;
|
|
2195
|
+
if (!widget.id) widget.id = `ox-code-play-${++widgetId}`;
|
|
2196
|
+
for (const tab of element.querySelectorAll("[data-ox-panel]")) {
|
|
2197
|
+
const panelName = tab.dataset.oxPanel;
|
|
2198
|
+
const panel = panelName ? element.querySelector(`[data-panel="${panelName}"]`) : null;
|
|
2199
|
+
if (!panelName || !panel) continue;
|
|
2200
|
+
tab.id ||= `${widget.id}-${panelName}-tab`;
|
|
2201
|
+
panel.id ||= `${widget.id}-${panelName}-panel`;
|
|
2202
|
+
tab.setAttribute("aria-controls", panel.id);
|
|
2203
|
+
panel.setAttribute("aria-labelledby", tab.id);
|
|
2204
|
+
panel.removeAttribute("aria-label");
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
1792
2207
|
function readForm(form) {
|
|
1793
2208
|
const data = new FormData(form);
|
|
1794
2209
|
const config = {};
|
|
@@ -1801,7 +2216,7 @@ function createCodePlayFromPayloads(root) {
|
|
|
1801
2216
|
let endpoints;
|
|
1802
2217
|
for (const element of queryWidgets(root)) try {
|
|
1803
2218
|
const payload = decodePayload(element.getAttribute("data-ox-code-play") ?? "");
|
|
1804
|
-
languages[payload.language] =
|
|
2219
|
+
languages[payload.language] = languageEnableFromPayload(payload);
|
|
1805
2220
|
endpoints = payload.endpoints ?? endpoints;
|
|
1806
2221
|
} catch {}
|
|
1807
2222
|
return createCodePlay({
|
|
@@ -1809,6 +2224,15 @@ function createCodePlayFromPayloads(root) {
|
|
|
1809
2224
|
endpoints
|
|
1810
2225
|
});
|
|
1811
2226
|
}
|
|
2227
|
+
function languageEnableFromPayload(payload) {
|
|
2228
|
+
const enable = {
|
|
2229
|
+
execute: payload.capabilities.execute,
|
|
2230
|
+
typecheck: payload.capabilities.typecheck,
|
|
2231
|
+
config: payload.config
|
|
2232
|
+
};
|
|
2233
|
+
if (payload.endpoint) enable.endpoint = payload.endpoint;
|
|
2234
|
+
return enable;
|
|
2235
|
+
}
|
|
1812
2236
|
function queryWidgets(root) {
|
|
1813
2237
|
return [...root.querySelectorAll("[data-ox-code-play]")];
|
|
1814
2238
|
}
|