@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.
Files changed (46) hide show
  1. package/README.md +26 -0
  2. package/dist/pkg/format.js +1 -1
  3. package/dist/pkg/headless.js +710 -703
  4. package/dist/pkg/macro-registry.json +18 -0
  5. package/package.json +1 -1
  6. package/src/action-registry.ts +9 -0
  7. package/src/class-registry.ts +1 -5
  8. package/src/components/Passage.tsx +35 -39
  9. package/src/components/PassageDialog.tsx +2 -2
  10. package/src/components/StoryInterface.tsx +2 -2
  11. package/src/components/macros/Checkbox.tsx +3 -8
  12. package/src/components/macros/Computed.tsx +1 -1
  13. package/src/components/macros/Cycle.tsx +3 -6
  14. package/src/components/macros/ExprDisplay.tsx +12 -29
  15. package/src/components/macros/If.tsx +35 -41
  16. package/src/components/macros/Include.tsx +2 -1
  17. package/src/components/macros/Listbox.tsx +5 -7
  18. package/src/components/macros/Print.tsx +3 -5
  19. package/src/components/macros/Radiobutton.tsx +3 -5
  20. package/src/components/macros/Repeat.tsx +2 -1
  21. package/src/components/macros/Switch.tsx +6 -13
  22. package/src/components/macros/Timed.tsx +14 -18
  23. package/src/components/macros/Type.tsx +2 -1
  24. package/src/components/macros/VarDisplay.tsx +12 -32
  25. package/src/components/macros/Widget.tsx +49 -20
  26. package/src/components/macros/branches.ts +18 -0
  27. package/src/components/macros/display.ts +19 -0
  28. package/src/components/macros/input-macro.ts +33 -11
  29. package/src/components/macros/macro-args.ts +7 -0
  30. package/src/components/macros/option-utils.ts +10 -7
  31. package/src/define-macro.ts +4 -6
  32. package/src/expression.ts +58 -40
  33. package/src/hooks/use-action.ts +7 -15
  34. package/src/index.tsx +5 -15
  35. package/src/interpolation.ts +34 -41
  36. package/src/markup/ast.ts +4 -3
  37. package/src/markup/render.tsx +4 -12
  38. package/src/registry.ts +13 -12
  39. package/src/saves/save-manager.ts +18 -13
  40. package/src/saves/storage.ts +19 -20
  41. package/src/story-api.ts +30 -73
  42. package/src/structural.ts +1 -18
  43. package/src/types-drift-check.ts +37 -50
  44. package/src/utils/macro-names.ts +40 -0
  45. package/src/utils/namespace.ts +17 -0
  46. package/src/widgets/widget-registry.ts +5 -4
@@ -1,62 +1,61 @@
1
1
  /**
2
- * Compile-time check: the hand-written types/index.d.ts must stay in sync
3
- * with the source StoryAPI interface. If this file fails to compile,
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 _ctxSourceToPublished: PublishedMacroContext = {} as SourceMacroContext;
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 _astPublishedToSource: SourceASTNode = {} as PublishedASTNode;
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
+ }
@@ -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 Map<string, WidgetEntry>();
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.toLowerCase(), {
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.toLowerCase());
36
+ return widgets.get(name);
36
37
  }
37
38
 
38
39
  export function isBlockWidget(name: string): boolean {
39
- return widgets.get(name.toLowerCase())?.isBlock ?? false;
40
+ return widgets.get(name)?.isBlock ?? false;
40
41
  }
41
42
 
42
43
  export function clearWidgets(): void {