@optiaxiom/proteus 3.0.6 → 3.2.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/esm/assets/src/proteus-chart/{ProteusChart.css.ts.vanilla-ByPSKREp.css → ProteusChart.css.ts.vanilla-BcGBUwbD.css} +2 -2
- package/dist/esm/assets/src/proteus-chart/{ProteusChartTooltipContent.css.ts.vanilla-DnR_C5ly.css → ProteusChartTooltipContent.css.ts.vanilla-BthlShlk.css} +2 -2
- package/dist/esm/assets/src/proteus-document/{ProteusDocumentShell.css.ts.vanilla-CVOes7es.css → ProteusDocumentShell.css.ts.vanilla-BugeP59E.css} +2 -2
- package/dist/esm/assets/src/proteus-image-carousel/{ProteusImageCarousel.css.ts.vanilla-DXF3wHYt.css → ProteusImageCarousel.css.ts.vanilla-DhXdg25-.css} +2 -2
- package/dist/esm/assets/src/proteus-markdown/{ProteusMarkdown.css.ts.vanilla-Cjmk3X5z.css → ProteusMarkdown.css.ts.vanilla-BM9-lPyj.css} +2 -2
- package/dist/esm/assets/src/proteus-question/{ProteusQuestion.css.ts.vanilla-DVFRsIPp.css → ProteusQuestion.css.ts.vanilla-1u353vmb.css} +2 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/proteus-action/ProteusAction.js +1 -1
- package/dist/esm/proteus-chart/ProteusChart-css.js +1 -1
- package/dist/esm/proteus-chart/ProteusChartTooltipContent-css.js +1 -1
- package/dist/esm/proteus-data-table/ProteusDataTable.js +3 -1
- package/dist/esm/proteus-data-table-row/ProteusDataTableRow.js +26 -0
- package/dist/esm/proteus-document/ProteusDataTableRowContext.js +12 -0
- package/dist/esm/proteus-document/ProteusDocumentShell-css.js +1 -1
- package/dist/esm/proteus-document/ProteusDocumentShell.js +70 -49
- package/dist/esm/proteus-document/isSafeUrl.js +22 -0
- package/dist/esm/proteus-document/resolveProteusProp.js +5 -6
- package/dist/esm/proteus-document/resolveProteusValue.js +26 -23
- package/dist/esm/proteus-document/useResolveProteusValues.js +3 -1
- package/dist/esm/proteus-element/ProteusElement.js +41 -3
- package/dist/esm/proteus-image-carousel/ProteusImageCarousel-css.js +1 -1
- package/dist/esm/proteus-markdown/ProteusMarkdown-css.js +1 -1
- package/dist/esm/proteus-question/ProteusQuestion-css.js +1 -1
- package/dist/esm/proteus-script/useProteusScripts.js +176 -0
- package/dist/esm/proteus-script/workerScript.js +149 -0
- package/dist/esm/proteus-show/ProteusShow.js +4 -3
- package/dist/esm/schema/public-schema.js +100 -0
- package/dist/esm/schema/runtime-schema.js +97 -0
- package/dist/index.d.ts +42 -2
- package/package.json +3 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { useEffectEvent } from "../hooks/useEffectEvent.js";
|
|
4
|
+
import { WORKER_SCRIPT } from "./workerScript.js";
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
import { get } from "jsonpointer";
|
|
7
|
+
//#region src/proteus-script/useProteusScripts.ts
|
|
8
|
+
/**
|
|
9
|
+
* How long a single handler run may take before we give up on it. A script
|
|
10
|
+
* with an infinite loop cannot block the main thread (it runs in the worker),
|
|
11
|
+
* but its `invoke` promise would otherwise never resolve — wedging the
|
|
12
|
+
* triggering control in a `loading` state forever. On timeout we resolve the
|
|
13
|
+
* run to `undefined` (the fail-silent default) and terminate the worker so the
|
|
14
|
+
* runaway handler stops burning a core; the next run lazily respawns it.
|
|
15
|
+
*/
|
|
16
|
+
const INVOKE_TIMEOUT_MS = 5e3;
|
|
17
|
+
/**
|
|
18
|
+
* Spins up a single sandbox Web Worker for the document's `scripts` and returns
|
|
19
|
+
* a `runScript(handler, params)` that invokes a named handler inside it. Events
|
|
20
|
+
* a handler or watcher emits are routed back through `onEmit` (the shell
|
|
21
|
+
* dispatcher) and their results returned to the worker, so `ctx.emit`
|
|
22
|
+
* round-trips work.
|
|
23
|
+
*
|
|
24
|
+
* Watchers (`watch(path, fn)` in a script) are driven from here: the worker
|
|
25
|
+
* reports its watched paths after `init`, and this hook edge-detects each path
|
|
26
|
+
* against `data` on every change, posting `runWatcher` when the value there
|
|
27
|
+
* changes. A watcher does NOT re-fire on data changes caused by a script's own
|
|
28
|
+
* `emit` (see `emitDepthRef`) — watchers observe user/host state changes, which
|
|
29
|
+
* both prevents emit→watcher→emit loops and gives a predictable "reacts to what
|
|
30
|
+
* the user did" semantic.
|
|
31
|
+
*
|
|
32
|
+
* Returns a no-op runner when the document ships no scripts — the worker is
|
|
33
|
+
* spawned eagerly when scripts exist (watchers can fire before any interaction).
|
|
34
|
+
*/
|
|
35
|
+
function useProteusScripts({ data, onEmit, scripts }) {
|
|
36
|
+
const dataRef = useRef(data);
|
|
37
|
+
dataRef.current = data;
|
|
38
|
+
const initialDataRef = useRef(data);
|
|
39
|
+
const emit = useEffectEvent(onEmit);
|
|
40
|
+
const hasScripts = !!scripts && Object.keys(scripts).length > 0;
|
|
41
|
+
const pending = useRef(/* @__PURE__ */ new Map()).current;
|
|
42
|
+
const nextId = useRef(1);
|
|
43
|
+
const workerRef = useRef(null);
|
|
44
|
+
const watchedPaths = useRef([]);
|
|
45
|
+
const lastEval = useRef([]);
|
|
46
|
+
const emitDepth = useRef(0);
|
|
47
|
+
/** Snapshot every watched path from `data` into `lastEval` without firing. */
|
|
48
|
+
const seedWatchers = useEffectEvent((data) => {
|
|
49
|
+
lastEval.current = watchedPaths.current.map((path) => JSON.stringify(getByPointer(data, path) ?? null));
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Compare each watched path in `data` against its `lastEval` baseline and post
|
|
53
|
+
* `runWatcher` for any that changed, advancing the baseline. Shared by the
|
|
54
|
+
* data-change effect and the `watchers` message handler (so a watcher that
|
|
55
|
+
* registers after the user already changed data still catches that edge).
|
|
56
|
+
*/
|
|
57
|
+
const runEdgeDetect = useEffectEvent((data) => {
|
|
58
|
+
const worker = workerRef.current;
|
|
59
|
+
if (!worker) return;
|
|
60
|
+
watchedPaths.current.forEach((path, watchId) => {
|
|
61
|
+
const current = getByPointer(data, path) ?? null;
|
|
62
|
+
const serialized = JSON.stringify(current);
|
|
63
|
+
const prevSerialized = lastEval.current[watchId];
|
|
64
|
+
if (serialized !== prevSerialized) {
|
|
65
|
+
lastEval.current[watchId] = serialized;
|
|
66
|
+
const previous = prevSerialized === void 0 ? void 0 : JSON.parse(prevSerialized);
|
|
67
|
+
worker.postMessage({
|
|
68
|
+
current,
|
|
69
|
+
data,
|
|
70
|
+
previous,
|
|
71
|
+
type: "runWatcher",
|
|
72
|
+
watchId
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
/** Resolve a pending run (if still pending) and clear its timeout. */
|
|
78
|
+
const settle = useEffectEvent((invokeId, result) => {
|
|
79
|
+
const entry = pending.get(invokeId);
|
|
80
|
+
if (!entry) return;
|
|
81
|
+
clearTimeout(entry.timer);
|
|
82
|
+
pending.delete(invokeId);
|
|
83
|
+
entry.resolve(result);
|
|
84
|
+
});
|
|
85
|
+
const spawnWorker = useEffectEvent(() => {
|
|
86
|
+
const url = URL.createObjectURL(new Blob([WORKER_SCRIPT], { type: "text/javascript" }));
|
|
87
|
+
const instance = new Worker(url);
|
|
88
|
+
URL.revokeObjectURL(url);
|
|
89
|
+
instance.postMessage({
|
|
90
|
+
scripts,
|
|
91
|
+
type: "init"
|
|
92
|
+
});
|
|
93
|
+
instance.addEventListener("message", async (e) => {
|
|
94
|
+
const msg = e.data;
|
|
95
|
+
if (msg.type === "emit") {
|
|
96
|
+
emitDepth.current += 1;
|
|
97
|
+
let result;
|
|
98
|
+
try {
|
|
99
|
+
result = await emit(msg.event);
|
|
100
|
+
} finally {
|
|
101
|
+
emitDepth.current -= 1;
|
|
102
|
+
}
|
|
103
|
+
instance.postMessage({
|
|
104
|
+
emitId: msg.emitId,
|
|
105
|
+
result,
|
|
106
|
+
type: "emitResult"
|
|
107
|
+
});
|
|
108
|
+
} else if (msg.type === "invokeResult") settle(msg.invokeId, msg.result);
|
|
109
|
+
else if (msg.type === "watchers") {
|
|
110
|
+
watchedPaths.current = msg.paths;
|
|
111
|
+
seedWatchers(initialDataRef.current);
|
|
112
|
+
runEdgeDetect(dataRef.current);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return instance;
|
|
116
|
+
});
|
|
117
|
+
/** Ensure the worker exists, spawning it on first need. */
|
|
118
|
+
const ensureWorker = useEffectEvent(() => {
|
|
119
|
+
if (!workerRef.current) workerRef.current = spawnWorker();
|
|
120
|
+
return workerRef.current;
|
|
121
|
+
});
|
|
122
|
+
/** Tear down the current worker and abandon every in-flight run. */
|
|
123
|
+
const teardown = useEffectEvent(() => {
|
|
124
|
+
workerRef.current?.terminate();
|
|
125
|
+
workerRef.current = null;
|
|
126
|
+
watchedPaths.current = [];
|
|
127
|
+
lastEval.current = [];
|
|
128
|
+
for (const [invokeId] of pending) settle(invokeId, void 0);
|
|
129
|
+
});
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
teardown();
|
|
132
|
+
if (hasScripts) ensureWorker();
|
|
133
|
+
return teardown;
|
|
134
|
+
}, [scripts, hasScripts]);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!workerRef.current || watchedPaths.current.length === 0) return;
|
|
137
|
+
if (emitDepth.current > 0) {
|
|
138
|
+
seedWatchers(data);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
runEdgeDetect(data);
|
|
142
|
+
}, [data]);
|
|
143
|
+
return useEffectEvent((handler, params) => {
|
|
144
|
+
if (!hasScripts) return Promise.resolve(void 0);
|
|
145
|
+
const worker = ensureWorker();
|
|
146
|
+
const invokeId = nextId.current++;
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
const timer = setTimeout(() => {
|
|
149
|
+
settle(invokeId, void 0);
|
|
150
|
+
teardown();
|
|
151
|
+
}, INVOKE_TIMEOUT_MS);
|
|
152
|
+
pending.set(invokeId, {
|
|
153
|
+
resolve,
|
|
154
|
+
timer
|
|
155
|
+
});
|
|
156
|
+
worker.postMessage({
|
|
157
|
+
data: dataRef.current,
|
|
158
|
+
handler,
|
|
159
|
+
invokeId,
|
|
160
|
+
params: params ?? {},
|
|
161
|
+
type: "invoke"
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** Read a JSON-pointer slice, matching the worker's getByPointer semantics. */
|
|
167
|
+
function getByPointer(data, path) {
|
|
168
|
+
if (!path || path === "/") return data;
|
|
169
|
+
try {
|
|
170
|
+
return get(data, path);
|
|
171
|
+
} catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
export { useProteusScripts };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
//#region src/proteus-script/workerScript.ts
|
|
2
|
+
/**
|
|
3
|
+
* Source of the sandbox Web Worker, authored as a self-contained string so it
|
|
4
|
+
* can be turned into a Blob URL and run without a separate chunk or same-origin
|
|
5
|
+
* file (mirrors how `ProteusBridge` inlines its shim script).
|
|
6
|
+
*
|
|
7
|
+
* The worker has no DOM, no `window.parent`, and communicates only via
|
|
8
|
+
* structured-clone `postMessage` — a strictly tighter sandbox than an iframe.
|
|
9
|
+
* A handler's sole capability is `ctx.emit`, which posts an existing Proteus
|
|
10
|
+
* event back to the host to be re-dispatched. See `protocol.ts` for the message
|
|
11
|
+
* shapes and `ScriptContext`.
|
|
12
|
+
*
|
|
13
|
+
* INVARIANT: script isolation depends on this worker holding no privileged
|
|
14
|
+
* globals. We `new Function(...)` the document's source below, which is only
|
|
15
|
+
* safe because there is nothing sensitive in scope. Do NOT add `importScripts`,
|
|
16
|
+
* pass host capabilities in via `postMessage`, or expose any API to handler
|
|
17
|
+
* scope — doing so turns `new Function` into an escape hatch.
|
|
18
|
+
*/
|
|
19
|
+
const WORKER_SCRIPT = `
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
const handlers = new Map();
|
|
23
|
+
const watchers = [];
|
|
24
|
+
const pendingEmits = new Map();
|
|
25
|
+
let nextEmitId = 1;
|
|
26
|
+
|
|
27
|
+
// Minimal JSON-pointer getter (same semantics as the host's getProteusValue).
|
|
28
|
+
function getByPointer(data, path) {
|
|
29
|
+
if (!path || path === "/") return data;
|
|
30
|
+
const parts = path.replace(/^\\//, "").split("/");
|
|
31
|
+
let current = data;
|
|
32
|
+
for (const raw of parts) {
|
|
33
|
+
if (current == null) return undefined;
|
|
34
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
35
|
+
current = current[key];
|
|
36
|
+
}
|
|
37
|
+
return current;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function register(name, fn) {
|
|
41
|
+
if (typeof name !== "string" || typeof fn !== "function") {
|
|
42
|
+
throw new Error("register(name, fn) requires a string name and a function");
|
|
43
|
+
}
|
|
44
|
+
handlers.set(name, fn);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function watch(path, fn) {
|
|
48
|
+
if (typeof path !== "string" || typeof fn !== "function") {
|
|
49
|
+
throw new Error("watch(path, fn) requires a string path and a function");
|
|
50
|
+
}
|
|
51
|
+
watchers.push({ fn, path });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Evaluate one module's source in a scope that exposes \`register\` and \`watch\`.
|
|
55
|
+
// Handlers are keyed by "moduleName:handlerName" via a per-module register
|
|
56
|
+
// wrapper; watchers are collected globally (order = watch id).
|
|
57
|
+
function loadModule(moduleName, source) {
|
|
58
|
+
const scopedRegister = (name, fn) => register(moduleName + ":" + name, fn);
|
|
59
|
+
// eslint-disable-next-line no-new-func
|
|
60
|
+
const factory = new Function("register", "watch", source);
|
|
61
|
+
factory(scopedRegister, watch);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function emit(event) {
|
|
65
|
+
const emitId = nextEmitId++;
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
pendingEmits.set(emitId, resolve);
|
|
68
|
+
self.postMessage({ emitId, event, type: "emit" });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
self.onmessage = async (e) => {
|
|
73
|
+
const msg = e.data;
|
|
74
|
+
|
|
75
|
+
if (msg.type === "init") {
|
|
76
|
+
for (const [moduleName, source] of Object.entries(msg.scripts ?? {})) {
|
|
77
|
+
try {
|
|
78
|
+
loadModule(moduleName, source);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// A broken module leaves its handlers unregistered; invokes for them
|
|
81
|
+
// surface a "not found" error at call time.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Report watched paths so the host can edge-detect and drive runWatcher.
|
|
85
|
+
self.postMessage({ paths: watchers.map((w) => w.path), type: "watchers" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (msg.type === "emitResult") {
|
|
90
|
+
const resolve = pendingEmits.get(msg.emitId);
|
|
91
|
+
if (resolve) {
|
|
92
|
+
pendingEmits.delete(msg.emitId);
|
|
93
|
+
resolve(msg.result);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (msg.type === "invoke") {
|
|
99
|
+
const { data, handler, invokeId, params } = msg;
|
|
100
|
+
const fn = handlers.get(handler);
|
|
101
|
+
if (!fn) {
|
|
102
|
+
self.postMessage({
|
|
103
|
+
error: 'No script handler registered for "' + handler + '"',
|
|
104
|
+
invokeId,
|
|
105
|
+
result: undefined,
|
|
106
|
+
type: "invokeResult",
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const ctx = {
|
|
111
|
+
emit,
|
|
112
|
+
getValue: (path) => getByPointer(data, path),
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
const result = await fn(ctx, params ?? {});
|
|
116
|
+
self.postMessage({ invokeId, result, type: "invokeResult" });
|
|
117
|
+
} catch (err) {
|
|
118
|
+
self.postMessage({
|
|
119
|
+
error: err && err.message ? String(err.message) : String(err),
|
|
120
|
+
invokeId,
|
|
121
|
+
result: undefined,
|
|
122
|
+
type: "invokeResult",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (msg.type === "runWatcher") {
|
|
129
|
+
const { current, data, previous, watchId } = msg;
|
|
130
|
+
const watcher = watchers[watchId];
|
|
131
|
+
if (!watcher) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const ctx = {
|
|
135
|
+
emit,
|
|
136
|
+
getValue: (path) => getByPointer(data, path),
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
// Fire-and-forget: watchers return nothing to any caller. Errors are
|
|
140
|
+
// swallowed to match the fail-silent default of the other ops.
|
|
141
|
+
await watcher.fn(ctx, current, previous);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// Intentionally ignored.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
`;
|
|
148
|
+
//#endregion
|
|
149
|
+
export { WORKER_SCRIPT };
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useProteusDocumentContext } from "../proteus-document/ProteusDocumentContext.js";
|
|
3
3
|
import { useProteusDocumentPathContext } from "../proteus-document/ProteusDocumentPathContext.js";
|
|
4
|
+
import { useProteusDataTableRowContext } from "../proteus-document/ProteusDataTableRowContext.js";
|
|
4
5
|
import { evaluateCondition } from "../proteus-document/resolveProteusValue.js";
|
|
5
6
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/proteus-show/ProteusShow.tsx
|
|
7
|
-
function ProteusShow({ children, when }) {
|
|
8
|
+
function ProteusShow({ children, else: fallback, when }) {
|
|
8
9
|
const { data } = useProteusDocumentContext("@optiaxiom/proteus/ProteusShow");
|
|
9
10
|
const { mapIndices, path: parentPath } = useProteusDocumentPathContext("@optiaxiom/proteus/ProteusShow");
|
|
10
|
-
|
|
11
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
11
|
+
const { row: dataTableRow } = useProteusDataTableRowContext("@optiaxiom/proteus/ProteusShow");
|
|
12
|
+
return /* @__PURE__ */ jsx(Fragment, { children: (Array.isArray(when) ? when : [when]).every((condition) => evaluateCondition(condition, data, parentPath, mapIndices, dataTableRow)) ? children : fallback });
|
|
12
13
|
}
|
|
13
14
|
ProteusShow.displayName = "@optiaxiom/proteus/ProteusShow";
|
|
14
15
|
//#endregion
|
|
@@ -921,6 +921,7 @@ var public_schema_default = {
|
|
|
921
921
|
{ "type": "number" },
|
|
922
922
|
{ "type": "boolean" },
|
|
923
923
|
{ "type": "null" },
|
|
924
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
924
925
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
925
926
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
926
927
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -941,6 +942,7 @@ var public_schema_default = {
|
|
|
941
942
|
{ "type": "number" },
|
|
942
943
|
{ "type": "boolean" },
|
|
943
944
|
{ "type": "null" },
|
|
945
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
944
946
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
945
947
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
946
948
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -961,6 +963,7 @@ var public_schema_default = {
|
|
|
961
963
|
{ "type": "number" },
|
|
962
964
|
{ "type": "boolean" },
|
|
963
965
|
{ "type": "null" },
|
|
966
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
964
967
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
965
968
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
966
969
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -981,6 +984,7 @@ var public_schema_default = {
|
|
|
981
984
|
{ "type": "number" },
|
|
982
985
|
{ "type": "boolean" },
|
|
983
986
|
{ "type": "null" },
|
|
987
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
984
988
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
985
989
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
986
990
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -1001,6 +1005,7 @@ var public_schema_default = {
|
|
|
1001
1005
|
{ "type": "number" },
|
|
1002
1006
|
{ "type": "boolean" },
|
|
1003
1007
|
{ "type": "null" },
|
|
1008
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
1004
1009
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
1005
1010
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
1006
1011
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -1021,6 +1026,7 @@ var public_schema_default = {
|
|
|
1021
1026
|
{ "type": "number" },
|
|
1022
1027
|
{ "type": "boolean" },
|
|
1023
1028
|
{ "type": "null" },
|
|
1029
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
1024
1030
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
1025
1031
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
1026
1032
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -1040,6 +1046,7 @@ var public_schema_default = {
|
|
|
1040
1046
|
{ "type": "number" },
|
|
1041
1047
|
{ "type": "boolean" },
|
|
1042
1048
|
{ "type": "null" },
|
|
1049
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
1043
1050
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
1044
1051
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
1045
1052
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -1057,6 +1064,7 @@ var public_schema_default = {
|
|
|
1057
1064
|
{ "type": "number" },
|
|
1058
1065
|
{ "type": "boolean" },
|
|
1059
1066
|
{ "type": "null" },
|
|
1067
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
1060
1068
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
1061
1069
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
1062
1070
|
{ "$ref": "#/definitions/ProteusValue" }
|
|
@@ -1210,6 +1218,32 @@ var public_schema_default = {
|
|
|
1210
1218
|
"gap": "16"
|
|
1211
1219
|
}],
|
|
1212
1220
|
"title": "Create Test Plan"
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
"$type": "Document",
|
|
1224
|
+
"actions": [{
|
|
1225
|
+
"$type": "Action",
|
|
1226
|
+
"appearance": "primary",
|
|
1227
|
+
"children": "Greet",
|
|
1228
|
+
"onClick": {
|
|
1229
|
+
"params": { "name": {
|
|
1230
|
+
"$type": "Value",
|
|
1231
|
+
"path": "/name"
|
|
1232
|
+
} },
|
|
1233
|
+
"script": "main:greet"
|
|
1234
|
+
}
|
|
1235
|
+
}],
|
|
1236
|
+
"appName": "Opal",
|
|
1237
|
+
"body": [{
|
|
1238
|
+
"$type": "Field",
|
|
1239
|
+
"children": {
|
|
1240
|
+
"$type": "Input",
|
|
1241
|
+
"name": "name"
|
|
1242
|
+
},
|
|
1243
|
+
"label": "Your name"
|
|
1244
|
+
}],
|
|
1245
|
+
"scripts": { "main": "register('greet', (ctx) => ctx.emit({ message: `Hello, ${ctx.params.name}!` }))" },
|
|
1246
|
+
"title": "Scripted Greeting"
|
|
1213
1247
|
}
|
|
1214
1248
|
],
|
|
1215
1249
|
"properties": {
|
|
@@ -1252,6 +1286,11 @@ var public_schema_default = {
|
|
|
1252
1286
|
"type": "object"
|
|
1253
1287
|
},
|
|
1254
1288
|
"meta": { "description": "Additional metadata not directly consumed by Proteus. Use this to pass along any extra data." },
|
|
1289
|
+
"scripts": {
|
|
1290
|
+
"additionalProperties": { "type": "string" },
|
|
1291
|
+
"description": "Map of module name to JavaScript source. Each module runs in a sandboxed Web Worker and registers named handlers via `register(name, fn)`. Trigger a handler from any event source with `{ script: 'module:handler', params }`; handlers receive a single `ctx` ({ emit, getValue, params }) and can only affect the document by emitting the existing Proteus events through `ctx.emit`.",
|
|
1292
|
+
"type": "object"
|
|
1293
|
+
},
|
|
1255
1294
|
"subtitle": {
|
|
1256
1295
|
"$ref": "#/definitions/ProteusNode",
|
|
1257
1296
|
"description": "A brief description or tagline that provides additional context about the Proteus document's purpose."
|
|
@@ -1283,6 +1322,7 @@ var public_schema_default = {
|
|
|
1283
1322
|
{ "$ref": "#/definitions/ProteusChart" },
|
|
1284
1323
|
{ "$ref": "#/definitions/ProteusConcat" },
|
|
1285
1324
|
{ "$ref": "#/definitions/ProteusDataTable" },
|
|
1325
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
1286
1326
|
{ "$ref": "#/definitions/ProteusDateInput" },
|
|
1287
1327
|
{ "$ref": "#/definitions/ProteusDisclosure" },
|
|
1288
1328
|
{ "$ref": "#/definitions/ProteusDisclosureContent" },
|
|
@@ -1338,6 +1378,23 @@ var public_schema_default = {
|
|
|
1338
1378
|
"required": ["interaction"],
|
|
1339
1379
|
"type": "object"
|
|
1340
1380
|
},
|
|
1381
|
+
{
|
|
1382
|
+
"additionalProperties": false,
|
|
1383
|
+
"description": "Scripted action - runs a named handler from the document's `scripts` map in a sandboxed Web Worker. The handler can emit the other Proteus events via `ctx.emit`.",
|
|
1384
|
+
"properties": {
|
|
1385
|
+
"params": {
|
|
1386
|
+
"additionalProperties": {},
|
|
1387
|
+
"description": "Parameters passed to the handler as `ctx.params`. Values can be ProteusExpressions that resolve at call time.",
|
|
1388
|
+
"type": "object"
|
|
1389
|
+
},
|
|
1390
|
+
"script": {
|
|
1391
|
+
"description": "Handler to run, addressed as `module:handler` (the module key comes from the document's `scripts` map).",
|
|
1392
|
+
"type": "string"
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
"required": ["script"],
|
|
1396
|
+
"type": "object"
|
|
1397
|
+
},
|
|
1341
1398
|
{
|
|
1342
1399
|
"additionalProperties": false,
|
|
1343
1400
|
"description": "Client-side message action",
|
|
@@ -1441,12 +1498,31 @@ var public_schema_default = {
|
|
|
1441
1498
|
},
|
|
1442
1499
|
"required": ["action", "path"],
|
|
1443
1500
|
"type": "object"
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
"additionalProperties": false,
|
|
1504
|
+
"description": "Runtime data operation - writes `value` at `path` in form data, replacing any existing value (e.g. `/tags/2`). Path resolves like `Value` (absolute `/x`, relative to current parentPath, or `''` for parentPath itself).",
|
|
1505
|
+
"properties": {
|
|
1506
|
+
"action": {
|
|
1507
|
+
"const": "setValue",
|
|
1508
|
+
"description": "The action type",
|
|
1509
|
+
"type": "string"
|
|
1510
|
+
},
|
|
1511
|
+
"path": {
|
|
1512
|
+
"description": "JSON pointer path to write to",
|
|
1513
|
+
"type": "string"
|
|
1514
|
+
},
|
|
1515
|
+
"value": { "description": "The value to write; primitives or objects" }
|
|
1516
|
+
},
|
|
1517
|
+
"required": ["action", "path"],
|
|
1518
|
+
"type": "object"
|
|
1444
1519
|
}
|
|
1445
1520
|
],
|
|
1446
1521
|
"description": "Handler for user interactions - a server-side interaction call, client-side message, or client-side component action"
|
|
1447
1522
|
},
|
|
1448
1523
|
"ProteusExpression": {
|
|
1449
1524
|
"anyOf": [
|
|
1525
|
+
{ "$ref": "#/definitions/ProteusDataTableRow" },
|
|
1450
1526
|
{ "$ref": "#/definitions/ProteusLength" },
|
|
1451
1527
|
{ "$ref": "#/definitions/ProteusMap" },
|
|
1452
1528
|
{ "$ref": "#/definitions/ProteusMapIndex" },
|
|
@@ -2486,6 +2562,10 @@ var public_schema_default = {
|
|
|
2486
2562
|
"description": "Key in data objects",
|
|
2487
2563
|
"type": "string"
|
|
2488
2564
|
},
|
|
2565
|
+
"cell": {
|
|
2566
|
+
"$ref": "#/definitions/ProteusNode",
|
|
2567
|
+
"description": "A Proteus node template rendered for each cell in this column. Read the current row with DataTableRow (e.g. { $type: 'DataTableRow', path: 'status' } reads the row's 'status', or omit 'path' for the whole row). Can be a single element or an array — use Show elements to conditionally render (e.g. a Badge whose intent depends on the row value). When set, takes precedence over 'format'."
|
|
2568
|
+
},
|
|
2489
2569
|
"format": {
|
|
2490
2570
|
"anyOf": [{
|
|
2491
2571
|
"description": "Formatter name",
|
|
@@ -2528,6 +2608,22 @@ var public_schema_default = {
|
|
|
2528
2608
|
"required": ["$type"],
|
|
2529
2609
|
"type": "object"
|
|
2530
2610
|
},
|
|
2611
|
+
"ProteusDataTableRow": {
|
|
2612
|
+
"additionalProperties": false,
|
|
2613
|
+
"examples": [{
|
|
2614
|
+
"$type": "DataTableRow",
|
|
2615
|
+
"path": "status"
|
|
2616
|
+
}],
|
|
2617
|
+
"properties": {
|
|
2618
|
+
"$type": { "const": "DataTableRow" },
|
|
2619
|
+
"path": {
|
|
2620
|
+
"description": "JSON pointer path to a field within the current row (e.g. 'status' or '/status'). When omitted, resolves to the whole row object. Only meaningful inside a DataTable column's `cell`.",
|
|
2621
|
+
"type": "string"
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
"required": ["$type"],
|
|
2625
|
+
"type": "object"
|
|
2626
|
+
},
|
|
2531
2627
|
"ProteusDateInput": {
|
|
2532
2628
|
"additionalProperties": false,
|
|
2533
2629
|
"examples": [{
|
|
@@ -4292,6 +4388,10 @@ var public_schema_default = {
|
|
|
4292
4388
|
"$ref": "#/definitions/ProteusNode",
|
|
4293
4389
|
"description": "Content to show when condition is true"
|
|
4294
4390
|
},
|
|
4391
|
+
"else": {
|
|
4392
|
+
"$ref": "#/definitions/ProteusNode",
|
|
4393
|
+
"description": "Content to render when the condition is false. Omitting it renders nothing. Chain nested Show/else to express multi-way choices (e.g. mapping a value to one of several outputs)."
|
|
4394
|
+
},
|
|
4295
4395
|
"when": {
|
|
4296
4396
|
"anyOf": [{ "$ref": "#/definitions/ProteusCondition" }, {
|
|
4297
4397
|
"items": { "$ref": "#/definitions/ProteusCondition" },
|