@rohal12/spindle 0.52.7 → 0.52.8
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 +26 -0
- package/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +710 -703
- package/dist/pkg/macro-registry.json +18 -0
- package/package.json +1 -1
- package/src/action-registry.ts +9 -0
- package/src/class-registry.ts +1 -5
- package/src/components/Passage.tsx +35 -39
- package/src/components/PassageDialog.tsx +2 -2
- package/src/components/StoryInterface.tsx +2 -2
- package/src/components/macros/Checkbox.tsx +3 -8
- package/src/components/macros/Computed.tsx +1 -1
- package/src/components/macros/Cycle.tsx +3 -6
- package/src/components/macros/ExprDisplay.tsx +12 -29
- package/src/components/macros/If.tsx +35 -41
- package/src/components/macros/Include.tsx +2 -1
- package/src/components/macros/Listbox.tsx +5 -7
- package/src/components/macros/Print.tsx +3 -5
- package/src/components/macros/Radiobutton.tsx +3 -5
- package/src/components/macros/Repeat.tsx +2 -1
- package/src/components/macros/Switch.tsx +6 -13
- package/src/components/macros/Timed.tsx +14 -18
- package/src/components/macros/Type.tsx +2 -1
- package/src/components/macros/VarDisplay.tsx +12 -32
- package/src/components/macros/Widget.tsx +49 -20
- package/src/components/macros/branches.ts +18 -0
- package/src/components/macros/display.ts +19 -0
- package/src/components/macros/input-macro.ts +33 -11
- package/src/components/macros/macro-args.ts +7 -0
- package/src/components/macros/option-utils.ts +10 -7
- package/src/define-macro.ts +4 -6
- package/src/expression.ts +58 -40
- package/src/hooks/use-action.ts +7 -15
- package/src/index.tsx +5 -15
- package/src/interpolation.ts +34 -41
- package/src/markup/ast.ts +4 -3
- package/src/markup/render.tsx +4 -12
- package/src/registry.ts +13 -12
- package/src/saves/save-manager.ts +18 -13
- package/src/saves/storage.ts +19 -20
- package/src/story-api.ts +30 -73
- package/src/structural.ts +1 -18
- package/src/types-drift-check.ts +37 -50
- package/src/utils/macro-names.ts +40 -0
- package/src/utils/namespace.ts +17 -0
- package/src/widgets/widget-registry.ts +5 -4
package/src/types-drift-check.ts
CHANGED
|
@@ -1,62 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compile-time check: the hand-written types
|
|
3
|
-
* with the source
|
|
4
|
-
* the published types have drifted from the implementation.
|
|
2
|
+
* Compile-time check: the hand-written published types (types/*.d.ts) must
|
|
3
|
+
* stay in sync with the source they describe. If this file fails to
|
|
4
|
+
* compile, the published types have drifted from the implementation.
|
|
5
5
|
*
|
|
6
6
|
* Run: npx tsc --noEmit
|
|
7
7
|
*/
|
|
8
8
|
import type { StoryAPI as SourceAPI } from './story-api';
|
|
9
|
-
import type { StoryAPI as PublishedAPI } from '../types/index';
|
|
10
|
-
|
|
11
|
-
// Both directions — if either fails, the types have drifted.
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
13
|
-
const _sourceToPublished: PublishedAPI = {} as SourceAPI;
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
-
const _publishedToSource: SourceAPI = {} as PublishedAPI;
|
|
16
|
-
|
|
17
|
-
// Tooling entry point (`@rohal12/spindle/tooling`): types/tooling.d.ts must
|
|
18
|
-
// match the parser that dist/pkg/tooling.js re-exports.
|
|
19
9
|
import type { parseStoryVariables as SourceParse } from './story-variables';
|
|
20
|
-
import type { parseStoryVariables as PublishedParse } from '../types/tooling';
|
|
21
|
-
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
|
-
const _parseSourceToPublished: typeof PublishedParse = {} as typeof SourceParse;
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
25
|
-
const _parsePublishedToSource: typeof SourceParse = {} as typeof PublishedParse;
|
|
26
|
-
|
|
27
|
-
// Custom macro API: the published MacroContext/MacroDefinition must match the
|
|
28
|
-
// ones defineMacro() actually passes and accepts.
|
|
29
10
|
import type {
|
|
30
11
|
MacroContext as SourceMacroContext,
|
|
31
12
|
MacroDefinition as SourceMacroDefinition,
|
|
32
13
|
} from './define-macro';
|
|
33
14
|
import type { MacroProps as SourceMacroProps } from './registry';
|
|
34
15
|
import type { ASTNode as SourceASTNode } from './markup/ast';
|
|
16
|
+
import type { bootStory as SourceBootStory } from './headless';
|
|
35
17
|
import type {
|
|
18
|
+
StoryAPI as PublishedAPI,
|
|
36
19
|
MacroContext as PublishedMacroContext,
|
|
37
20
|
MacroDefinition as PublishedMacroDefinition,
|
|
38
21
|
MacroProps as PublishedMacroProps,
|
|
39
22
|
ASTNode as PublishedASTNode,
|
|
40
23
|
} from '../types/index';
|
|
24
|
+
import type { parseStoryVariables as PublishedParse } from '../types/tooling';
|
|
25
|
+
import type { bootStory as PublishedBootStory } from '../types/headless';
|
|
41
26
|
|
|
27
|
+
/** What the source declares, by published name. */
|
|
28
|
+
interface Source {
|
|
29
|
+
StoryAPI: SourceAPI;
|
|
30
|
+
// Tooling entry point (`@rohal12/spindle/tooling`): the parser that
|
|
31
|
+
// dist/pkg/tooling.js re-exports.
|
|
32
|
+
parseStoryVariables: typeof SourceParse;
|
|
33
|
+
// Custom macro API: the MacroContext/MacroDefinition defineMacro()
|
|
34
|
+
// actually passes and accepts.
|
|
35
|
+
MacroContext: SourceMacroContext;
|
|
36
|
+
MacroDefinition: SourceMacroDefinition;
|
|
37
|
+
MacroProps: SourceMacroProps;
|
|
38
|
+
ASTNode: SourceASTNode;
|
|
39
|
+
// Headless entry point (`@rohal12/spindle/headless`).
|
|
40
|
+
bootStory: typeof SourceBootStory;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** What types/index.d.ts, tooling.d.ts and headless.d.ts publish. */
|
|
44
|
+
interface Published {
|
|
45
|
+
StoryAPI: PublishedAPI;
|
|
46
|
+
parseStoryVariables: typeof PublishedParse;
|
|
47
|
+
MacroContext: PublishedMacroContext;
|
|
48
|
+
MacroDefinition: PublishedMacroDefinition;
|
|
49
|
+
MacroProps: PublishedMacroProps;
|
|
50
|
+
ASTNode: PublishedASTNode;
|
|
51
|
+
bootStory: typeof PublishedBootStory;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Both directions — if either fails, the types have drifted.
|
|
42
55
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
43
|
-
const
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
-
const _ctxPublishedToSource: SourceMacroContext = {} as PublishedMacroContext;
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
47
|
-
const _defSourceToPublished: PublishedMacroDefinition =
|
|
48
|
-
{} as SourceMacroDefinition;
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
50
|
-
const _defPublishedToSource: SourceMacroDefinition =
|
|
51
|
-
{} as PublishedMacroDefinition;
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
53
|
-
const _propsSourceToPublished: PublishedMacroProps = {} as SourceMacroProps;
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
55
|
-
const _propsPublishedToSource: SourceMacroProps = {} as PublishedMacroProps;
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
57
|
-
const _astSourceToPublished: PublishedASTNode = {} as SourceASTNode;
|
|
56
|
+
const _sourceToPublished: Published = {} as Source;
|
|
58
57
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
59
|
-
const
|
|
58
|
+
const _publishedToSource: Source = {} as Published;
|
|
60
59
|
|
|
61
60
|
// A typical custom macro written against the published types must type-check,
|
|
62
61
|
// and misuse of the hooks must not (no `any` leaking through).
|
|
@@ -91,15 +90,3 @@ const _exampleMacro: PublishedMacroDefinition = {
|
|
|
91
90
|
);
|
|
92
91
|
},
|
|
93
92
|
};
|
|
94
|
-
|
|
95
|
-
// Headless entry point (`@rohal12/spindle/headless`): types/headless.d.ts must
|
|
96
|
-
// match src/headless.ts.
|
|
97
|
-
import type { bootStory as SourceBootStory } from './headless';
|
|
98
|
-
import type { bootStory as PublishedBootStory } from '../types/headless';
|
|
99
|
-
|
|
100
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
101
|
-
const _bootSourceToPublished: typeof PublishedBootStory =
|
|
102
|
-
{} as typeof SourceBootStory;
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
104
|
-
const _bootPublishedToSource: typeof SourceBootStory =
|
|
105
|
-
{} as typeof PublishedBootStory;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Macro and widget names are case-insensitive: `{StatusBar}` invokes the
|
|
3
|
+
* widget defined as `statusbar`. A NameMap keys its entries by the lowercased
|
|
4
|
+
* name.
|
|
5
|
+
*/
|
|
6
|
+
export class NameMap<V> extends Map<string, V> {
|
|
7
|
+
override get(name: string): V | undefined {
|
|
8
|
+
return super.get(name.toLowerCase());
|
|
9
|
+
}
|
|
10
|
+
override set(name: string, value: V): this {
|
|
11
|
+
return super.set(name.toLowerCase(), value);
|
|
12
|
+
}
|
|
13
|
+
override has(name: string): boolean {
|
|
14
|
+
return super.has(name.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
override delete(name: string): boolean {
|
|
17
|
+
return super.delete(name.toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A set of macro or widget names (see NameMap). */
|
|
22
|
+
export class NameSet {
|
|
23
|
+
private readonly names = new NameMap<true>();
|
|
24
|
+
|
|
25
|
+
constructor(names: Iterable<string> = []) {
|
|
26
|
+
for (const name of names) this.add(name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
add(name: string): void {
|
|
30
|
+
this.names.set(name, true);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
has(name: string): boolean {
|
|
34
|
+
return this.names.has(name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
delete(name: string): void {
|
|
38
|
+
this.names.delete(name);
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/utils/namespace.ts
CHANGED
|
@@ -48,6 +48,23 @@ export const ownValue = (
|
|
|
48
48
|
key: string,
|
|
49
49
|
): unknown => (ns && hasOwn(ns, key) ? (ns as Namespace)[key] : undefined);
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Set an own enumerable property. A "__proto__" key is defined as an own
|
|
53
|
+
* property (as JSON.parse does) instead of replacing the prototype.
|
|
54
|
+
*/
|
|
55
|
+
export function setOwn(target: object, key: string, value: unknown): void {
|
|
56
|
+
if (key === '__proto__') {
|
|
57
|
+
Object.defineProperty(target, key, {
|
|
58
|
+
value,
|
|
59
|
+
enumerable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
configurable: true,
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
(target as Record<string, unknown>)[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
/**
|
|
52
69
|
* A record with no prototype holding the own entries of `sources`, or only
|
|
53
70
|
* those whose value passes `keep`.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ASTNode } from '../markup/ast';
|
|
2
2
|
import { checkVariableName } from '../utils/namespace';
|
|
3
|
+
import { NameMap } from '../utils/macro-names';
|
|
3
4
|
|
|
4
5
|
interface WidgetEntry {
|
|
5
6
|
body: ASTNode[];
|
|
@@ -7,7 +8,7 @@ interface WidgetEntry {
|
|
|
7
8
|
isBlock: boolean;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
const widgets = new
|
|
11
|
+
const widgets = new NameMap<WidgetEntry>();
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Register a widget. Its `@` parameters become locals of its body, so one
|
|
@@ -24,7 +25,7 @@ export function registerWidget(
|
|
|
24
25
|
for (const param of filteredParams) {
|
|
25
26
|
if (param.startsWith('@')) checkVariableName(param.slice(1), param);
|
|
26
27
|
}
|
|
27
|
-
widgets.set(name
|
|
28
|
+
widgets.set(name, {
|
|
28
29
|
body: bodyAST,
|
|
29
30
|
params: filteredParams,
|
|
30
31
|
isBlock,
|
|
@@ -32,11 +33,11 @@ export function registerWidget(
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function getWidget(name: string): WidgetEntry | undefined {
|
|
35
|
-
return widgets.get(name
|
|
36
|
+
return widgets.get(name);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function isBlockWidget(name: string): boolean {
|
|
39
|
-
return widgets.get(name
|
|
40
|
+
return widgets.get(name)?.isBlock ?? false;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function clearWidgets(): void {
|