@rohal12/spindle 0.59.14 → 0.59.16
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 +1 -0
- package/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +814 -321
- package/dist/pkg/story-variables.js +349 -152
- package/package.json +1 -1
- package/src/class-registry.ts +105 -35
- package/src/components/Passage.tsx +21 -13
- package/src/components/PassageDialog.tsx +12 -2
- package/src/components/macros/For.tsx +16 -9
- package/src/components/macros/Radiobutton.tsx +4 -1
- package/src/components/macros/Watch.tsx +24 -4
- package/src/components/macros/detached-body.tsx +17 -6
- package/src/define-macro.ts +11 -2
- package/src/execute-mutation.ts +3 -2
- package/src/markup/render.tsx +17 -1
- package/src/markup/spindle.peggy +16 -0
- package/src/runtime-errors.ts +4 -8
- package/src/saves/save-manager.ts +63 -0
- package/src/store.ts +54 -10
- package/src/story-api.ts +4 -3
- package/src/story-variables.ts +15 -3
- package/src/structural.ts +74 -38
- package/src/triggers.ts +23 -2
- package/src/utils/control-edits.ts +81 -30
- package/src/utils/listeners.ts +23 -0
- package/src/utils/object-path.ts +18 -6
- package/src/utils/stable-key.ts +73 -40
- package/src/utils/value-kinds.ts +42 -0
- package/types/index.d.ts +7 -2
- package/types/tooling.d.ts +5 -1
|
@@ -1,48 +1,99 @@
|
|
|
1
|
+
import { createContext } from 'preact';
|
|
2
|
+
import { readState } from '../execute-mutation';
|
|
3
|
+
import { diffPaths } from '../structural';
|
|
1
4
|
import { getByPath } from './object-path';
|
|
5
|
+
import type { VariableNamespaces } from '../store';
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
|
-
* Records the edits
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* Records the edits made to the items of a list from inside a {for}
|
|
9
|
+
* iteration, which {for} tells apart from a list that is replaced:
|
|
10
|
+
*
|
|
11
|
+
* - a reader editing a variable through a control (`{textbox}`,
|
|
12
|
+
* `{checkbox}`, ...): typing into a control inside an iteration changes
|
|
13
|
+
* its item, but must not remount the iteration and drop the control's
|
|
14
|
+
* focus (#369);
|
|
15
|
+
* - mutation code an iteration runs as it renders (`{set $party[@i].hp +=
|
|
16
|
+
* 1}`): remounting would run the iteration's mount-only macros, and so the
|
|
17
|
+
* write, again for every write it makes (#400).
|
|
18
|
+
*
|
|
8
19
|
* An edit is attributed to the variable path it wrote, so a replacement made
|
|
9
|
-
* elsewhere (a watcher reacting to the edit
|
|
20
|
+
* elsewhere (a watcher reacting to the edit, a button outside the loop)
|
|
21
|
+
* still remounts.
|
|
10
22
|
*/
|
|
11
23
|
let version = 0;
|
|
12
|
-
const MAX_LOG =
|
|
13
|
-
|
|
24
|
+
const MAX_LOG = 4096;
|
|
25
|
+
/** Edits in the order made: paths below the namespaces (`['variables', 'a']`). */
|
|
26
|
+
const log: { version: number; path: readonly string[] }[] = [];
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
log.push({ version: ++version,
|
|
28
|
+
function note(path: readonly string[]): void {
|
|
29
|
+
log.push({ version: ++version, path });
|
|
17
30
|
if (log.length > MAX_LOG) log.shift();
|
|
18
31
|
}
|
|
19
32
|
|
|
33
|
+
/** Note an edit a control made to the story variable at `segments`. */
|
|
34
|
+
export function noteControlEdit(segments: readonly string[]): void {
|
|
35
|
+
note(['variables', ...segments]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether mutation code runs inside a {for} iteration: {for} provides true
|
|
40
|
+
* around its iterations, and the writes of such code are noted as edits of
|
|
41
|
+
* the items they reach (see runAsItemEdit).
|
|
42
|
+
*/
|
|
43
|
+
export const ItemEditContext = createContext(false);
|
|
44
|
+
|
|
45
|
+
const NAMESPACES = ['variables', 'temporary', 'transient'] as const;
|
|
46
|
+
|
|
47
|
+
/** Run `run`, noting each variable path it changes as an edit. */
|
|
48
|
+
export function runAsItemEdit(run: () => void): void {
|
|
49
|
+
const before = readState();
|
|
50
|
+
try {
|
|
51
|
+
run();
|
|
52
|
+
} finally {
|
|
53
|
+
const after = readState();
|
|
54
|
+
for (const ns of NAMESPACES) {
|
|
55
|
+
if (before[ns] === after[ns]) continue;
|
|
56
|
+
for (const { path } of diffPaths(before[ns], after[ns], true)) {
|
|
57
|
+
note([ns, ...path]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
20
63
|
export function controlEditVersion(): number {
|
|
21
64
|
return version;
|
|
22
65
|
}
|
|
23
66
|
|
|
24
67
|
/**
|
|
25
|
-
*
|
|
26
|
-
* `list`: the path leads through the item (compared by reference
|
|
27
|
-
* `
|
|
68
|
+
* A test of whether an edit made since `since` wrote inside `item`, element
|
|
69
|
+
* `index` of `list`: the path leads through the item (compared by reference
|
|
70
|
+
* in `state`), or, for a primitive item, ends at its slot in the list.
|
|
28
71
|
*/
|
|
29
|
-
export function
|
|
72
|
+
export function editedItems(
|
|
30
73
|
since: number,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
state: VariableNamespaces,
|
|
75
|
+
): (list: readonly unknown[], item: unknown, index: number) => boolean {
|
|
76
|
+
// The objects the paths lead through, and the keys they end at
|
|
77
|
+
const through = new Set<unknown>();
|
|
78
|
+
const ends = new Map<unknown, Set<string>>();
|
|
79
|
+
for (let i = log.length - 1; i >= 0 && log[i]!.version > since; i--) {
|
|
80
|
+
const { path } = log[i]!;
|
|
81
|
+
let node: unknown = state;
|
|
82
|
+
path.forEach((seg, n) => {
|
|
83
|
+
if (n === path.length - 1 && node !== null && typeof node === 'object') {
|
|
84
|
+
const keys = ends.get(node) ?? new Set<string>();
|
|
85
|
+
keys.add(seg);
|
|
86
|
+
ends.set(node, keys);
|
|
87
|
+
}
|
|
88
|
+
node =
|
|
89
|
+
node !== null && typeof node === 'object'
|
|
90
|
+
? getByPath(node, [seg])
|
|
91
|
+
: undefined;
|
|
92
|
+
if (node !== null && typeof node === 'object') through.add(node);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return (list, item, index) =>
|
|
96
|
+
typeof item === 'object' && item !== null
|
|
97
|
+
? through.has(item)
|
|
98
|
+
: ends.get(list)?.has(String(index)) === true;
|
|
48
99
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A set of listeners to call when something happens: `subscribe` adds one and
|
|
3
|
+
* returns the function that removes it, `notify` calls each one. A listener
|
|
4
|
+
* removed or added while they are called is called or not as the set was
|
|
5
|
+
* when `notify` started.
|
|
6
|
+
*/
|
|
7
|
+
export function createListeners(): {
|
|
8
|
+
subscribe: (listener: () => void) => () => void;
|
|
9
|
+
notify: () => void;
|
|
10
|
+
} {
|
|
11
|
+
const listeners = new Set<() => void>();
|
|
12
|
+
return {
|
|
13
|
+
subscribe(listener) {
|
|
14
|
+
listeners.add(listener);
|
|
15
|
+
return () => {
|
|
16
|
+
listeners.delete(listener);
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
notify() {
|
|
20
|
+
for (const listener of [...listeners]) listener();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
package/src/utils/object-path.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { current as draftState, isDraft } from 'immer';
|
|
2
|
+
import { registeredClassName } from '../class-registry';
|
|
2
3
|
import { hasOwn } from './namespace';
|
|
3
4
|
import { atomicName } from './value-kinds';
|
|
4
5
|
|
|
@@ -74,10 +75,12 @@ export interface SetByPathOptions {
|
|
|
74
75
|
* written in place.
|
|
75
76
|
*
|
|
76
77
|
* The path goes through own properties of plain objects, class instances
|
|
77
|
-
* and arrays (by index)
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
78
|
+
* and arrays (by index), and, outside a draft, any key of an instance of a
|
|
79
|
+
* registered subclass of a built-in (#408); anything else throws a
|
|
80
|
+
* TypeError rather than writing where no clone or save would see it: an
|
|
81
|
+
* inherited property such as a method counts as missing, and Map, Set,
|
|
82
|
+
* Date and RegExp values and other keys of arrays are refused, as is a
|
|
83
|
+
* "__proto__" segment.
|
|
81
84
|
*/
|
|
82
85
|
export function setByPath(
|
|
83
86
|
root: Record<string, unknown>,
|
|
@@ -134,13 +137,21 @@ function checkSegments(segments: readonly string[]): void {
|
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Throw unless `holder` can take `key` as story state (see setByPath). An
|
|
142
|
+
* instance of a registered subclass of a built-in keeps its own fields
|
|
143
|
+
* through clones and saves, so it takes any key (#408) when written in
|
|
144
|
+
* place: a draft of one, or one a draft shares, is refused as other
|
|
145
|
+
* built-ins are, and the caller writes the whole value instead.
|
|
146
|
+
*/
|
|
138
147
|
function checkHolder(
|
|
139
148
|
holder: object,
|
|
140
149
|
key: string,
|
|
141
150
|
segments: readonly string[],
|
|
142
151
|
depth: number,
|
|
152
|
+
inPlace: boolean,
|
|
143
153
|
): void {
|
|
154
|
+
if (inPlace && registeredClassName(holder) !== undefined) return;
|
|
144
155
|
const builtin = builtinName(holder);
|
|
145
156
|
const kind =
|
|
146
157
|
builtin ?? (Array.isArray(holder) && !isArrayKey(key) ? 'an array' : '');
|
|
@@ -167,7 +178,7 @@ function walkToParent(
|
|
|
167
178
|
let current: Record<string, unknown> = root;
|
|
168
179
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
169
180
|
const seg = segments[i]!;
|
|
170
|
-
checkHolder(current, seg, segments, i);
|
|
181
|
+
checkHolder(current, seg, segments, i, !copyOnWrite);
|
|
171
182
|
// An inherited property (a method, "constructor") counts as missing
|
|
172
183
|
let next = hasOwn(current, seg) ? current[seg] : undefined;
|
|
173
184
|
if (next == null || typeof next !== 'object') {
|
|
@@ -189,6 +200,7 @@ function walkToParent(
|
|
|
189
200
|
segments[segments.length - 1]!,
|
|
190
201
|
segments,
|
|
191
202
|
segments.length - 1,
|
|
203
|
+
!copyOnWrite,
|
|
192
204
|
);
|
|
193
205
|
return current;
|
|
194
206
|
}
|
package/src/utils/stable-key.ts
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
import { registeredClassName } from '../class-registry';
|
|
2
|
+
import { extraKeys, mapEntries, setMembers } from './value-kinds';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Content-derived string key for a value, used to remount components when
|
|
5
6
|
* the value's contents change (e.g. {for} iterations, #45).
|
|
6
7
|
*
|
|
7
|
-
* JSON-compatible values (strings, finite numbers, booleans, null,
|
|
8
|
-
* plain objects of those)
|
|
9
|
-
* values get distinct, unambiguous markers so their contents
|
|
10
|
-
* too: Map and Set entries (nested at any depth), Date,
|
|
11
|
-
* undefined, NaN/±Infinity, symbols (by description),
|
|
12
|
-
* and registered class instances
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* A tree of JSON-compatible values (strings, finite numbers, booleans, null,
|
|
9
|
+
* arrays and plain objects of those) produces exactly its `JSON.stringify`
|
|
10
|
+
* output. Other values get distinct, unambiguous markers so their contents
|
|
11
|
+
* are reflected too: Map and Set entries (nested at any depth), Date,
|
|
12
|
+
* RegExp, BigInt, undefined, NaN/±Infinity, symbols (by description),
|
|
13
|
+
* functions (by name) and registered class instances, with the class name
|
|
14
|
+
* and, for a subclass of a built-in, its own fields (#407). A hole in an
|
|
15
|
+
* array reads as undefined.
|
|
16
|
+
*
|
|
17
|
+
* An object met again, through a cycle or a second reference to it, is a
|
|
18
|
+
* marker naming where it was first met rather than another copy of its
|
|
19
|
+
* contents, so the key grows with the objects a value holds, not with the
|
|
20
|
+
* paths to them (#406). The function never throws.
|
|
15
21
|
*/
|
|
16
22
|
export function stableKey(value: unknown): string {
|
|
17
23
|
try {
|
|
18
|
-
return keyOf(value,
|
|
24
|
+
return keyOf(value, new Map());
|
|
19
25
|
} catch {
|
|
20
26
|
return '<unkeyable>';
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The key of `val`. `seen` numbers the objects keyed so far, in the order
|
|
32
|
+
* they were first met.
|
|
33
|
+
*/
|
|
34
|
+
function keyOf(val: unknown, seen: Map<object, number>): string {
|
|
25
35
|
switch (typeof val) {
|
|
26
36
|
case 'string':
|
|
27
37
|
return JSON.stringify(val);
|
|
@@ -45,39 +55,62 @@ function keyOf(val: unknown, ancestors: object[]): string {
|
|
|
45
55
|
if (val === null) return 'null';
|
|
46
56
|
|
|
47
57
|
const obj = val as object;
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
58
|
+
const first = seen.get(obj);
|
|
59
|
+
if (first !== undefined) return `<ref ${first}>`;
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
const className = registeredClassName(obj);
|
|
62
|
+
const named = (key: string) =>
|
|
63
|
+
className === undefined ? key : `Class(${JSON.stringify(className)})${key}`;
|
|
64
|
+
// The own fields of a registered subclass of a built-in, which clones and
|
|
65
|
+
// saves keep with its contents
|
|
66
|
+
const withFields = (key: string) => {
|
|
67
|
+
const keys = className === undefined ? [] : extraKeys(obj);
|
|
68
|
+
return named(keys.length ? `${key}${fieldsOf(obj, keys, seen)}` : key);
|
|
69
|
+
};
|
|
53
70
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// hole from an undefined element (deepEqual does)
|
|
60
|
-
return `[${Array.from(val, (v) => keyOf(v, ancestors)).join(',')}]`;
|
|
61
|
-
}
|
|
62
|
-
if (val instanceof Map) {
|
|
63
|
-
const entries = [...val].map(
|
|
64
|
-
([k, v]) => `[${keyOf(k, ancestors)},${keyOf(v, ancestors)}]`,
|
|
65
|
-
);
|
|
66
|
-
return `Map[${entries.join(',')}]`;
|
|
67
|
-
}
|
|
68
|
-
if (val instanceof Set) {
|
|
69
|
-
return `Set[${[...val].map((v) => keyOf(v, ancestors)).join(',')}]`;
|
|
71
|
+
// Read with the built-in methods: a subclass may override them. A Date or
|
|
72
|
+
// RegExp holds no other value, so it needs no number unless it has fields.
|
|
73
|
+
if (val instanceof Date || val instanceof RegExp) {
|
|
74
|
+
if (className !== undefined && extraKeys(obj).length) {
|
|
75
|
+
seen.set(obj, seen.size);
|
|
70
76
|
}
|
|
77
|
+
return withFields(
|
|
78
|
+
val instanceof Date
|
|
79
|
+
? `Date(${Date.prototype.getTime.call(val)})`
|
|
80
|
+
: `RegExp(${RegExp.prototype.toString.call(val)}@${val.lastIndex})`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
seen.set(obj, seen.size);
|
|
85
|
+
if (Array.isArray(val)) {
|
|
86
|
+
// Array.from reads a hole as undefined (map would skip it, giving
|
|
87
|
+
// `[,]` the key of `[]`): as JSON.stringify, a key does not tell a
|
|
88
|
+
// hole from an undefined element (deepEqual does)
|
|
89
|
+
return withFields(`[${Array.from(val, (v) => keyOf(v, seen)).join(',')}]`);
|
|
90
|
+
}
|
|
91
|
+
if (val instanceof Map) {
|
|
92
|
+
const entries = Array.from(
|
|
93
|
+
mapEntries(val),
|
|
94
|
+
([k, v]) => `[${keyOf(k, seen)},${keyOf(v, seen)}]`,
|
|
95
|
+
);
|
|
96
|
+
return withFields(`Map[${entries.join(',')}]`);
|
|
97
|
+
}
|
|
98
|
+
if (val instanceof Set) {
|
|
99
|
+
const members = Array.from(setMembers(val), (v) => keyOf(v, seen));
|
|
100
|
+
return withFields(`Set[${members.join(',')}]`);
|
|
82
101
|
}
|
|
102
|
+
return named(fieldsOf(obj, Object.keys(obj), seen));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** `{"key":value,...}` for the `keys` of `obj`. */
|
|
106
|
+
function fieldsOf(
|
|
107
|
+
obj: object,
|
|
108
|
+
keys: readonly string[],
|
|
109
|
+
seen: Map<object, number>,
|
|
110
|
+
): string {
|
|
111
|
+
const record = obj as Record<string, unknown>;
|
|
112
|
+
const body = keys.map(
|
|
113
|
+
(k) => `${JSON.stringify(k)}:${keyOf(record[k], seen)}`,
|
|
114
|
+
);
|
|
115
|
+
return `{${body.join(',')}}`;
|
|
83
116
|
}
|
package/src/utils/value-kinds.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Kinds of story values that structural operations (clone, equality, merge)
|
|
2
2
|
// and property paths treat as a whole, by their content.
|
|
3
3
|
|
|
4
|
+
import { isDraft } from 'immer';
|
|
5
|
+
|
|
4
6
|
export const toStringTag = (value: object): string =>
|
|
5
7
|
Object.prototype.toString.call(value).slice(8, -1);
|
|
6
8
|
|
|
@@ -57,3 +59,43 @@ export function extraKeys(collection: object): string[] {
|
|
|
57
59
|
const keys = Object.keys(collection);
|
|
58
60
|
return Array.isArray(collection) ? keys.filter((k) => !isIndexKey(k)) : keys;
|
|
59
61
|
}
|
|
62
|
+
|
|
63
|
+
// A subclass of Map or Set may override how it is iterated, read or written
|
|
64
|
+
// (an inventory that iterates only the items on hand). What it holds is in
|
|
65
|
+
// its built-in slots, so structural operations read and write those with the
|
|
66
|
+
// built-in methods (#391, #394). An Immer draft keeps its entries in its own
|
|
67
|
+
// state instead, so it is read through its methods.
|
|
68
|
+
|
|
69
|
+
/** The entries of a Map, as the built-in iterator gives them. */
|
|
70
|
+
export function mapEntries<K, V>(map: Map<K, V>): MapIterator<[K, V]> {
|
|
71
|
+
return isDraft(map)
|
|
72
|
+
? map.entries()
|
|
73
|
+
: (Map.prototype.entries.call(map) as MapIterator<[K, V]>);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The members of a Set, as the built-in iterator gives them. */
|
|
77
|
+
export function setMembers<T>(set: Set<T>): SetIterator<T> {
|
|
78
|
+
return isDraft(set)
|
|
79
|
+
? set.values()
|
|
80
|
+
: (Set.prototype.values.call(set) as SetIterator<T>);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The number of entries of a Map or members of a Set. */
|
|
84
|
+
export function collectionSize(
|
|
85
|
+
collection: Map<unknown, unknown> | Set<unknown>,
|
|
86
|
+
) {
|
|
87
|
+
if (isDraft(collection)) return collection.size;
|
|
88
|
+
const proto = collection instanceof Map ? Map.prototype : Set.prototype;
|
|
89
|
+
return Reflect.get(proto, 'size', collection) as number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The value a Map holds for `key`. */
|
|
93
|
+
export function mapGet<K, V>(map: Map<K, V>, key: K): V | undefined {
|
|
94
|
+
return isDraft(map) ? map.get(key) : Map.prototype.get.call(map, key);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Make a Map hold `value` for `key`. */
|
|
98
|
+
export function mapSet<K, V>(map: Map<K, V>, key: K, value: V): void {
|
|
99
|
+
if (isDraft(map)) map.set(key, value);
|
|
100
|
+
else Map.prototype.set.call(map, key, value);
|
|
101
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -794,7 +794,9 @@ export interface StoryAPI {
|
|
|
794
794
|
/**
|
|
795
795
|
* Register a class so its instances keep their class through clones,
|
|
796
796
|
* history, saves and loads. A save refuses instances of classes that are
|
|
797
|
-
* not registered.
|
|
797
|
+
* not registered. A class may extend Array, Map, Set, Date, RegExp or
|
|
798
|
+
* Error; one extending another built-in whose value a save cannot hold (a
|
|
799
|
+
* typed array, URL, Promise...) throws.
|
|
798
800
|
*/
|
|
799
801
|
registerClass(name: string, ctor: new (...args: any[]) => any): void;
|
|
800
802
|
|
|
@@ -864,7 +866,10 @@ export interface StoryAPI {
|
|
|
864
866
|
/** Set a one-time transition for the next navigation only. Pass `null` to clear. */
|
|
865
867
|
setNextTransition(config: TransitionConfig | null): void;
|
|
866
868
|
|
|
867
|
-
/**
|
|
869
|
+
/**
|
|
870
|
+
* Defer initial passage rendering until `ready()` is called. Calling it
|
|
871
|
+
* again before `ready()` keeps the one deferral: a single `ready()` ends it.
|
|
872
|
+
*/
|
|
868
873
|
deferRender(): void;
|
|
869
874
|
|
|
870
875
|
/** Unblock deferred rendering (call after `deferRender()`). */
|
package/types/tooling.d.ts
CHANGED
|
@@ -111,7 +111,11 @@ export type VarType =
|
|
|
111
111
|
/** Inferred shape of a declared variable (or one of its object fields). */
|
|
112
112
|
export interface FieldSchema {
|
|
113
113
|
type: VarType;
|
|
114
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Field schemas, only present for objects. An object the default refers
|
|
116
|
+
* to more than once has one schema, so a cyclic default (`node.self =
|
|
117
|
+
* node`) gives a schema that refers to itself.
|
|
118
|
+
*/
|
|
115
119
|
fields?: Map<string, FieldSchema>;
|
|
116
120
|
}
|
|
117
121
|
|