@rohal12/spindle 0.51.3 → 0.51.4
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/pkg/format.js +1 -1
- package/dist/pkg/headless.js +1294 -737
- package/dist/pkg/story-variables.js +22 -12
- package/package.json +1 -1
- package/src/class-registry.ts +76 -0
- package/src/components/macros/Button.tsx +4 -1
- package/src/components/macros/Checkbox.tsx +5 -2
- package/src/components/macros/Dialog.tsx +4 -1
- package/src/components/macros/For.tsx +5 -2
- package/src/components/macros/Include.tsx +9 -19
- package/src/components/macros/MacroLink.tsx +2 -1
- package/src/components/macros/Meter.tsx +18 -40
- package/src/components/macros/Radiobutton.tsx +22 -0
- package/src/components/macros/SaveManager.tsx +14 -6
- package/src/components/macros/Watch.tsx +88 -40
- package/src/components/macros/WidgetInvocation.tsx +14 -94
- package/src/components/macros/arg-utils.ts +120 -0
- package/src/components/macros/option-utils.ts +9 -5
- package/src/define-macro.ts +13 -34
- package/src/execute-mutation.ts +294 -26
- package/src/expression.ts +22 -237
- package/src/js-lexer.ts +326 -0
- package/src/markup/render.tsx +139 -81
- package/src/markup/tokenizer.ts +23 -9
- package/src/saves/save-manager.ts +37 -3
- package/src/saves/storage.ts +8 -0
- package/src/saves/types.ts +18 -5
- package/src/store.ts +70 -25
- package/src/story-api.ts +51 -50
- package/src/triggers.ts +5 -2
- package/src/utils/object-path.ts +109 -0
- package/src/utils/stable-key.ts +77 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getClassName } from '../class-registry';
|
|
2
|
+
|
|
3
|
+
type Constructor = new (...args: any[]) => any;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Content-derived string key for a value, used to remount components when
|
|
7
|
+
* the value's contents change (e.g. {for} iterations, #45).
|
|
8
|
+
*
|
|
9
|
+
* JSON-compatible values (strings, finite numbers, booleans, null, arrays and
|
|
10
|
+
* plain objects of those) produce exactly their `JSON.stringify` output. Other
|
|
11
|
+
* values get distinct, unambiguous markers so their contents are reflected
|
|
12
|
+
* too: Map and Set entries (nested at any depth), Date, RegExp, BigInt,
|
|
13
|
+
* undefined, NaN/±Infinity, symbols, functions and registered class
|
|
14
|
+
* instances. Cyclic references become a back-reference marker instead of
|
|
15
|
+
* throwing, and the function never throws.
|
|
16
|
+
*/
|
|
17
|
+
export function stableKey(value: unknown): string {
|
|
18
|
+
try {
|
|
19
|
+
return keyOf(value, []);
|
|
20
|
+
} catch {
|
|
21
|
+
return '<unkeyable>';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function keyOf(val: unknown, ancestors: object[]): string {
|
|
26
|
+
switch (typeof val) {
|
|
27
|
+
case 'string':
|
|
28
|
+
return JSON.stringify(val);
|
|
29
|
+
case 'number':
|
|
30
|
+
return Number.isFinite(val) ? JSON.stringify(val) : String(val);
|
|
31
|
+
case 'boolean':
|
|
32
|
+
return String(val);
|
|
33
|
+
case 'bigint':
|
|
34
|
+
return `${val}n`;
|
|
35
|
+
case 'undefined':
|
|
36
|
+
return 'undefined';
|
|
37
|
+
case 'symbol':
|
|
38
|
+
return val.toString();
|
|
39
|
+
case 'function':
|
|
40
|
+
return `<function ${JSON.stringify(val.name)}>`;
|
|
41
|
+
}
|
|
42
|
+
if (val === null) return 'null';
|
|
43
|
+
|
|
44
|
+
const obj = val as object;
|
|
45
|
+
const depth = ancestors.indexOf(obj);
|
|
46
|
+
if (depth !== -1) return `<cycle ${ancestors.length - depth}>`;
|
|
47
|
+
|
|
48
|
+
if (val instanceof Date) return `Date(${val.getTime()})`;
|
|
49
|
+
if (val instanceof RegExp) return `RegExp(${String(val)})`;
|
|
50
|
+
|
|
51
|
+
ancestors.push(obj);
|
|
52
|
+
try {
|
|
53
|
+
if (Array.isArray(val)) {
|
|
54
|
+
return `[${val.map((v) => keyOf(v, ancestors)).join(',')}]`;
|
|
55
|
+
}
|
|
56
|
+
if (val instanceof Map) {
|
|
57
|
+
const entries = [...val].map(
|
|
58
|
+
([k, v]) => `[${keyOf(k, ancestors)},${keyOf(v, ancestors)}]`,
|
|
59
|
+
);
|
|
60
|
+
return `Map[${entries.join(',')}]`;
|
|
61
|
+
}
|
|
62
|
+
if (val instanceof Set) {
|
|
63
|
+
return `Set[${[...val].map((v) => keyOf(v, ancestors)).join(',')}]`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const record = obj as Record<string, unknown>;
|
|
67
|
+
const body = Object.keys(record)
|
|
68
|
+
.map((k) => `${JSON.stringify(k)}:${keyOf(record[k], ancestors)}`)
|
|
69
|
+
.join(',');
|
|
70
|
+
const className = getClassName(obj.constructor as Constructor);
|
|
71
|
+
return className === undefined
|
|
72
|
+
? `{${body}}`
|
|
73
|
+
: `Class(${JSON.stringify(className)}){${body}}`;
|
|
74
|
+
} finally {
|
|
75
|
+
ancestors.pop();
|
|
76
|
+
}
|
|
77
|
+
}
|