@jxsuite/runtime 4.0.3 → 5.1.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/runtime.js +749 -473
- package/dist/runtime.js.map +12 -12
- package/package.json +5 -4
- package/src/cookie.ts +4 -2
- package/src/css.ts +31 -18
- package/src/expression.ts +321 -232
- package/src/pointer.ts +10 -4
- package/src/runtime.ts +1399 -611
- package/src/statements.ts +58 -48
- package/src/types.ts +114 -13
package/src/statements.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `dispatchEvent` with `CustomEventInit` members, `stopPropagation` and `preventDefault`.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { isFunction, isNonNullObject } from "@jxsuite/schema/guards";
|
|
13
14
|
import {
|
|
14
15
|
compileExpression,
|
|
15
16
|
compileOperandSource,
|
|
@@ -17,13 +18,19 @@ import {
|
|
|
17
18
|
evaluateOperand,
|
|
18
19
|
} from "./expression.ts";
|
|
19
20
|
|
|
20
|
-
import type {
|
|
21
|
-
|
|
21
|
+
import type {
|
|
22
|
+
JxDispatchStatement,
|
|
23
|
+
JxExpressionNode,
|
|
24
|
+
JxIfStatement,
|
|
25
|
+
JxStatement,
|
|
26
|
+
JxSwitchStatement,
|
|
27
|
+
} from "@jxsuite/schema/types";
|
|
28
|
+
import type { CompileOpts, IterCtx } from "./expression.ts";
|
|
22
29
|
import type { JxScope } from "./types.ts";
|
|
23
30
|
|
|
24
31
|
interface RunOpts {
|
|
25
32
|
/** Named-formula-style arguments bound for $args/<name> refs in the body. */
|
|
26
|
-
args?:
|
|
33
|
+
args?: IterCtx["args"];
|
|
27
34
|
/**
|
|
28
35
|
* Dispatch target for dispatchEvent statements when there is no event to read `currentTarget`
|
|
29
36
|
* from — a parameterised body invoked through `call`, or a lifecycle hook. A thunk is read at
|
|
@@ -32,31 +39,49 @@ interface RunOpts {
|
|
|
32
39
|
target?: EventTarget | null | (() => EventTarget | null);
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
/*
|
|
43
|
+
* Statement bodies come from documents that are not schema-validated at runtime, so the branch
|
|
44
|
+
* arrays the schema requires may still be absent; these views say so and keep the fallbacks
|
|
45
|
+
* honest.
|
|
46
|
+
*/
|
|
47
|
+
interface UncheckedIfStatement extends Omit<JxIfStatement, "then"> {
|
|
48
|
+
then?: JxStatement[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface UncheckedSwitchStatement extends Omit<JxSwitchStatement, "cases"> {
|
|
52
|
+
cases?: JxSwitchStatement["cases"];
|
|
53
|
+
}
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
type ClassifiedStatement =
|
|
56
|
+
| { kind: "expression"; node: JxExpressionNode }
|
|
57
|
+
| { kind: "if"; node: UncheckedIfStatement }
|
|
58
|
+
| { kind: "switch"; node: UncheckedSwitchStatement }
|
|
59
|
+
| { kind: "dispatch"; node: JxDispatchStatement }
|
|
60
|
+
| { kind: "stopPropagation" }
|
|
61
|
+
| { kind: "preventDefault" };
|
|
62
|
+
|
|
63
|
+
function classifyStatement(statement: JxStatement): ClassifiedStatement {
|
|
44
64
|
if ("operator" in statement) {
|
|
45
|
-
return "expression";
|
|
65
|
+
return { kind: "expression", node: statement };
|
|
46
66
|
}
|
|
47
67
|
if ("if" in statement) {
|
|
48
|
-
return "if";
|
|
68
|
+
return { kind: "if", node: statement };
|
|
49
69
|
}
|
|
50
70
|
if ("$switch" in statement) {
|
|
51
|
-
return "switch";
|
|
71
|
+
return { kind: "switch", node: statement };
|
|
52
72
|
}
|
|
53
73
|
if ("stopPropagation" in statement) {
|
|
54
|
-
return "stopPropagation";
|
|
74
|
+
return { kind: "stopPropagation" };
|
|
55
75
|
}
|
|
56
76
|
if ("preventDefault" in statement) {
|
|
57
|
-
return "preventDefault";
|
|
77
|
+
return { kind: "preventDefault" };
|
|
58
78
|
}
|
|
59
|
-
return "dispatch";
|
|
79
|
+
return { kind: "dispatch", node: statement };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A value `await` would adopt: anything with a callable `then` (ECMA-262 thenable). */
|
|
83
|
+
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
84
|
+
return (isNonNullObject(value) || isFunction(value)) && "then" in value && isFunction(value.then);
|
|
60
85
|
}
|
|
61
86
|
|
|
62
87
|
/**
|
|
@@ -72,16 +97,18 @@ export async function runStatements(
|
|
|
72
97
|
): Promise<void> {
|
|
73
98
|
const iterCtx: IterCtx | undefined = opts.args ? { args: opts.args } : undefined;
|
|
74
99
|
for (const statement of statements) {
|
|
75
|
-
|
|
100
|
+
const classified = classifyStatement(statement);
|
|
101
|
+
switch (classified.kind) {
|
|
76
102
|
case "expression": {
|
|
77
|
-
const result = evaluateExpression(
|
|
78
|
-
if (
|
|
79
|
-
|
|
103
|
+
const result = evaluateExpression(classified.node, state, event, iterCtx);
|
|
104
|
+
if (isThenable(result)) {
|
|
105
|
+
// Awaited for its ordering alone: the next statement runs once this one settles.
|
|
106
|
+
void (await result);
|
|
80
107
|
}
|
|
81
108
|
break;
|
|
82
109
|
}
|
|
83
110
|
case "if": {
|
|
84
|
-
const branch =
|
|
111
|
+
const branch = classified.node;
|
|
85
112
|
const taken = evaluateOperand(branch.if, state, event, iterCtx) ? branch.then : branch.else;
|
|
86
113
|
if (Array.isArray(taken)) {
|
|
87
114
|
await runStatements(taken, state, event, opts);
|
|
@@ -89,11 +116,7 @@ export async function runStatements(
|
|
|
89
116
|
break;
|
|
90
117
|
}
|
|
91
118
|
case "switch": {
|
|
92
|
-
const branch =
|
|
93
|
-
$switch: unknown;
|
|
94
|
-
cases: Record<string, JxStatement[]>;
|
|
95
|
-
default?: JxStatement[];
|
|
96
|
-
};
|
|
119
|
+
const branch = classified.node;
|
|
97
120
|
const key = String(evaluateOperand(branch.$switch, state, event, iterCtx));
|
|
98
121
|
const cases = branch.cases ?? {};
|
|
99
122
|
const taken = Object.hasOwn(cases, key) ? cases[key] : branch.default;
|
|
@@ -103,14 +126,9 @@ export async function runStatements(
|
|
|
103
126
|
break;
|
|
104
127
|
}
|
|
105
128
|
case "dispatch": {
|
|
106
|
-
const dispatch =
|
|
107
|
-
dispatchEvent: string;
|
|
108
|
-
detail?: unknown;
|
|
109
|
-
bubbles?: boolean;
|
|
110
|
-
composed?: boolean;
|
|
111
|
-
};
|
|
129
|
+
const dispatch = classified.node;
|
|
112
130
|
// An event's own target wins; the option is the fallback for bodies run without one.
|
|
113
|
-
const fallback =
|
|
131
|
+
const fallback = isFunction(opts.target) ? opts.target() : opts.target;
|
|
114
132
|
const target = event?.currentTarget ?? fallback;
|
|
115
133
|
if (!target) {
|
|
116
134
|
break;
|
|
@@ -164,13 +182,14 @@ export function compileStatements(
|
|
|
164
182
|
const indent = opts.indent ?? "";
|
|
165
183
|
const lines: string[] = [];
|
|
166
184
|
for (const statement of statements) {
|
|
167
|
-
|
|
185
|
+
const classified = classifyStatement(statement);
|
|
186
|
+
switch (classified.kind) {
|
|
168
187
|
case "expression": {
|
|
169
|
-
lines.push(`${indent}${compileExpression(
|
|
188
|
+
lines.push(`${indent}${compileExpression(classified.node, opts)};`);
|
|
170
189
|
break;
|
|
171
190
|
}
|
|
172
191
|
case "if": {
|
|
173
|
-
const branch =
|
|
192
|
+
const branch = classified.node;
|
|
174
193
|
const test = compileOperandSource(branch.if, opts);
|
|
175
194
|
const inner = { ...opts, indent: `${indent} ` };
|
|
176
195
|
lines.push(`${indent}if (${test}) {`, compileStatements(branch.then ?? [], inner));
|
|
@@ -181,11 +200,7 @@ export function compileStatements(
|
|
|
181
200
|
break;
|
|
182
201
|
}
|
|
183
202
|
case "switch": {
|
|
184
|
-
const branch =
|
|
185
|
-
$switch: unknown;
|
|
186
|
-
cases: Record<string, JxStatement[]>;
|
|
187
|
-
default?: JxStatement[];
|
|
188
|
-
};
|
|
203
|
+
const branch = classified.node;
|
|
189
204
|
const disc = compileOperandSource(branch.$switch, opts);
|
|
190
205
|
const inner = { ...opts, indent: `${indent} ` };
|
|
191
206
|
lines.push(`${indent}switch (String(${disc})) {`);
|
|
@@ -208,12 +223,7 @@ export function compileStatements(
|
|
|
208
223
|
break;
|
|
209
224
|
}
|
|
210
225
|
case "dispatch": {
|
|
211
|
-
const dispatch =
|
|
212
|
-
dispatchEvent: string;
|
|
213
|
-
detail?: unknown;
|
|
214
|
-
bubbles?: boolean;
|
|
215
|
-
composed?: boolean;
|
|
216
|
-
};
|
|
226
|
+
const dispatch = classified.node;
|
|
217
227
|
const eventName = opts.eventParam ?? "event";
|
|
218
228
|
const target = opts.dispatchTarget ?? `(${eventName} && ${eventName}.currentTarget)`;
|
|
219
229
|
const init: string[] = [];
|
package/src/types.ts
CHANGED
|
@@ -1,17 +1,101 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
import type { JxDocument, JxElement, JxPath } from "@jxsuite/schema/types";
|
|
2
|
+
import type { JxDocument, JxDocumentValue, JxElement, JxPath } from "@jxsuite/schema/types";
|
|
3
|
+
import type { Ref } from "@vue/reactivity";
|
|
3
4
|
|
|
4
5
|
export type { JxPath } from "@jxsuite/schema/types";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* What one member of a {@link JxScope} holds: every kind of value the runtime puts into a scope or
|
|
9
|
+
* hands to a document's code.
|
|
10
|
+
*
|
|
11
|
+
* - A document's own values (`JxDocumentValue`): JSON, and the typed shapes a naked `state` entry, a
|
|
12
|
+
* `$`-local or a literal `$props` value copies in (spec.md §5.3).
|
|
13
|
+
* - `undefined`, for a member or an argument that is absent.
|
|
14
|
+
* - Arrays and records of scope values: reactive objects, host records, `$props`, and the instance an
|
|
15
|
+
* external class constructs (spec.md §12.3), every one of them read by member name.
|
|
16
|
+
* - Functions ({@link JxScopeFunction}): handlers, formulas, `$src` exports, server-function proxies
|
|
17
|
+
* and a host's own functions.
|
|
18
|
+
* - Refs: the computed and data-source entries the reactive proxy unwraps on read.
|
|
19
|
+
* - The `$map` context a mapped row renders under ({@link JxRowContext}).
|
|
20
|
+
* - DOM objects: event targets (nodes, the window, the document) and events, which `window#/`,
|
|
21
|
+
* `document#/` and `event#/` references read.
|
|
22
|
+
* - The platform objects a `$prototype` entry resolves to (spec.md §11.2): `Map`, `Set`, `FormData`,
|
|
23
|
+
* `Blob`, and the `IDBObjectStore` an `IndexedDB` entry's `getStore()` yields.
|
|
24
|
+
* - A promise of any of those: a computed entry over an `async` function settles later.
|
|
25
|
+
*
|
|
26
|
+
* Narrow it with the guards in `@jxsuite/schema/guards`, which work on the union.
|
|
10
27
|
*/
|
|
11
|
-
export type
|
|
28
|
+
export type JxScopeValue = JxSettledValue | Promise<JxSettledValue>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A scope member that is not a promise: what a {@link JxScopeValue} settles to. A promise never
|
|
32
|
+
* fulfils with another promise, which is what lets an `await` on a scope member end.
|
|
33
|
+
*/
|
|
34
|
+
export type JxSettledValue =
|
|
35
|
+
| JxDocumentValue
|
|
36
|
+
| undefined
|
|
37
|
+
| JxScopeValue[]
|
|
38
|
+
| readonly JxScopeValue[]
|
|
39
|
+
| JxScope
|
|
40
|
+
| JxScopeFunction
|
|
41
|
+
| Ref<JxScopeValue>
|
|
42
|
+
| JxRowContext
|
|
43
|
+
| EventTarget
|
|
44
|
+
| Event
|
|
45
|
+
| ReadonlyMap<JxScopeValue, JxScopeValue>
|
|
46
|
+
| ReadonlySet<JxScopeValue>
|
|
47
|
+
| FormData
|
|
48
|
+
| Blob
|
|
49
|
+
| IDBObjectStore;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The live reactive scope (spec.md §15): a prototype-chained record of state entries, `$props`, a
|
|
53
|
+
* host's members and the runtime's `$` vocabulary, each read and written by name.
|
|
54
|
+
*/
|
|
55
|
+
export interface JxScope {
|
|
56
|
+
[name: string]: JxScopeValue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A function a scope holds, as the scope sees it: a handler, a formula, a `$src` export, a
|
|
61
|
+
* server-function proxy or a host's own function. JavaScript checks neither the arguments a call
|
|
62
|
+
* passes nor what it returns, so this is every function; a call site narrows it to the signature it
|
|
63
|
+
* calls with, and a result it reads is a scope member by the contract of whatever stored the
|
|
64
|
+
* function (a computed entry, a formula, a server call).
|
|
65
|
+
*/
|
|
66
|
+
export type JxScopeFunction = (...args: never[]) => void;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The `$map` context a mapped row renders under (spec.md §10.2): the row's item, and its index in
|
|
70
|
+
* the list. Both are written in place when the row is reused.
|
|
71
|
+
*/
|
|
72
|
+
export interface JxRowContext {
|
|
73
|
+
index: number;
|
|
74
|
+
item: JxScopeValue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A value a host hands the runtime, held to {@link JxScopeValue} member by member.
|
|
79
|
+
*
|
|
80
|
+
* A host declares its scope with interfaces, and TypeScript gives an interface no implicit index
|
|
81
|
+
* signature, so a host's record is never assignable to {@link JxScope}, however its members are
|
|
82
|
+
* typed. This walks the declared type instead: a scope value stays as it is, a `bigint` or a
|
|
83
|
+
* `symbol` is refused (neither is a scope value), a function is held to what it returns, and
|
|
84
|
+
* anything else — an array, a record — is held member by member.
|
|
85
|
+
*/
|
|
86
|
+
export type JxHostValue<T> = T extends JxScopeValue
|
|
87
|
+
? T
|
|
88
|
+
: T extends bigint | symbol
|
|
89
|
+
? never
|
|
90
|
+
: T extends (...args: infer A) => infer R
|
|
91
|
+
? (...args: A) => JxHostValue<R>
|
|
92
|
+
: { [K in keyof T]: JxHostValue<T[K]> };
|
|
93
|
+
|
|
94
|
+
/** A host's scope, held to {@link JxScopeValue} member by member (see {@link JxHostValue}). */
|
|
95
|
+
export type JxHostScope<S> = { [K in keyof S]: JxHostValue<S[K]> };
|
|
12
96
|
|
|
13
97
|
/** An event handler stored in the scope: `(scope, event) => void`. */
|
|
14
|
-
export type JxEventHandler = (scope: JxScope, event: Event) =>
|
|
98
|
+
export type JxEventHandler = (scope: JxScope, event: Event) => void;
|
|
15
99
|
|
|
16
100
|
/**
|
|
17
101
|
* A host-supplied document source consulted before the network. Returning `undefined` means "not
|
|
@@ -42,14 +126,17 @@ export interface JxContext {
|
|
|
42
126
|
skipAutoRequests?: boolean;
|
|
43
127
|
}
|
|
44
128
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Options for {@link mount}: the host's half of the embedding contract (specs/embedding.md). `S` is
|
|
131
|
+
* the host's own scope type, which `mount` holds to {@link JxHostScope}.
|
|
132
|
+
*/
|
|
133
|
+
export interface JxMountOptions<S = JxScope> {
|
|
47
134
|
/**
|
|
48
135
|
* Host members merged into the document's scope before its own `state` is built: reactive
|
|
49
136
|
* records, refs, computeds and plain functions. A document `state` entry of the same name wins.
|
|
50
137
|
* Keys beginning with `$` or `#` are runtime vocabulary and are refused.
|
|
51
138
|
*/
|
|
52
|
-
scope?:
|
|
139
|
+
scope?: S;
|
|
53
140
|
base?: string;
|
|
54
141
|
media?: Record<string, string>;
|
|
55
142
|
resolver?: DocumentResolver;
|
|
@@ -71,7 +158,7 @@ export interface JxMount {
|
|
|
71
158
|
* touches.
|
|
72
159
|
*/
|
|
73
160
|
readonly elements: ReadonlySet<string>;
|
|
74
|
-
/**
|
|
161
|
+
/** Removes the root, stops every effect, then calls `onUnmount`. Idempotent. */
|
|
75
162
|
readonly dispose: () => void;
|
|
76
163
|
}
|
|
77
164
|
|
|
@@ -112,8 +199,22 @@ export interface JxRenderOptions {
|
|
|
112
199
|
onNodeMoved?: (node: HTMLElement | Text, path: JxPath) => void;
|
|
113
200
|
}
|
|
114
201
|
|
|
202
|
+
/**
|
|
203
|
+
* A method body compiled by `new Function` from a `.class.json` method definition. Only document
|
|
204
|
+
* code calls it, so its arguments and result are opaque to TypeScript.
|
|
205
|
+
*/
|
|
206
|
+
export type DynamicMethod = (...args: never[]) => void;
|
|
207
|
+
|
|
208
|
+
/** The prototype of a {@link DynamicClass}: instance methods attached by name. */
|
|
209
|
+
export type DynamicPrototype = Record<string, DynamicMethod>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* A class built at runtime from a `.class.json` definition. Its static side carries the static
|
|
213
|
+
* methods attached by name beside the prototype; an instance holds the constructor `config` values
|
|
214
|
+
* under its field names.
|
|
215
|
+
*/
|
|
115
216
|
export interface DynamicClass {
|
|
116
|
-
new (config?:
|
|
117
|
-
[
|
|
118
|
-
prototype:
|
|
217
|
+
new (config?: JxScope): JxScope;
|
|
218
|
+
[member: string]: DynamicMethod | DynamicPrototype;
|
|
219
|
+
prototype: DynamicPrototype;
|
|
119
220
|
}
|