@rohal12/spindle 0.59.11 → 0.59.13
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 +7293 -7141
- package/dist/pkg/story-variables.js +2 -1
- package/package.json +1 -1
- package/src/components/macros/Computed.tsx +26 -12
- package/src/components/macros/For.tsx +37 -1
- package/src/components/macros/Type.tsx +8 -4
- package/src/components/macros/Widget.tsx +1 -19
- package/src/components/macros/WidgetInvocation.tsx +3 -10
- package/src/define-macro.ts +2 -0
- package/src/execute-mutation.ts +26 -0
- package/src/hooks/use-modal-focus.ts +66 -4
- package/src/index.tsx +9 -22
- package/src/markup/render.tsx +43 -4
- package/src/saves/save-manager.ts +9 -0
- package/src/story-api.ts +2 -1
- package/src/story-init.ts +4 -0
- package/src/story-variables.ts +12 -2
- package/src/utils/control-edits.ts +48 -0
- package/src/widgets/register-widget-def.ts +44 -0
- package/types/tooling.d.ts +7 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getByPath } from './object-path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Records the edits a reader made through a control bound to a story
|
|
5
|
+
* variable (`{textbox}`, `{checkbox}`, ...). {for} tells these apart from a
|
|
6
|
+
* list that is replaced: typing into a control inside an iteration changes
|
|
7
|
+
* its item, but must not remount the iteration and drop the control's focus.
|
|
8
|
+
* An edit is attributed to the variable path it wrote, so a replacement made
|
|
9
|
+
* elsewhere (a watcher reacting to the edit) still remounts.
|
|
10
|
+
*/
|
|
11
|
+
let version = 0;
|
|
12
|
+
const MAX_LOG = 256;
|
|
13
|
+
const log: { version: number; segments: readonly string[] }[] = [];
|
|
14
|
+
|
|
15
|
+
export function noteControlEdit(segments: readonly string[]): void {
|
|
16
|
+
log.push({ version: ++version, segments });
|
|
17
|
+
if (log.length > MAX_LOG) log.shift();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function controlEditVersion(): number {
|
|
21
|
+
return version;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether an edit since `since` wrote inside `item`, element `index` of
|
|
26
|
+
* `list`: the path leads through the item (compared by reference in
|
|
27
|
+
* `variables`), or, for a primitive item, ends at its slot in the list.
|
|
28
|
+
*/
|
|
29
|
+
export function editedItem(
|
|
30
|
+
since: number,
|
|
31
|
+
variables: object,
|
|
32
|
+
list: readonly unknown[],
|
|
33
|
+
item: unknown,
|
|
34
|
+
index: number,
|
|
35
|
+
): boolean {
|
|
36
|
+
return log.some(({ version: v, segments }) => {
|
|
37
|
+
if (v <= since) return false;
|
|
38
|
+
if (typeof item === 'object' && item !== null) {
|
|
39
|
+
return segments.some(
|
|
40
|
+
(_, n) => getByPath(variables, segments.slice(0, n + 1)) === item,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return (
|
|
44
|
+
segments[segments.length - 1] === String(index) &&
|
|
45
|
+
getByPath(variables, segments.slice(0, -1)) === list
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { registerWidget } from './widget-registry';
|
|
2
|
+
import { astContainsChildren } from './ast-scanner';
|
|
3
|
+
import { registerBlockMacro, type ASTNode } from '../markup/ast';
|
|
4
|
+
import { errorMessage } from '../utils/error-message';
|
|
5
|
+
import { parseWidgetDef, type WidgetDef } from './widget-def';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register the widget a definition declares, with its body. Widgets whose
|
|
9
|
+
* body renders {@children} take a closing tag, so they are registered as
|
|
10
|
+
* block macros too: passages parsed later nest their content. A parameter
|
|
11
|
+
* that no namespace can hold throws (see registerWidget), registering
|
|
12
|
+
* nothing.
|
|
13
|
+
*/
|
|
14
|
+
export function registerWidgetDef(
|
|
15
|
+
{ name, params }: WidgetDef,
|
|
16
|
+
body: ASTNode[],
|
|
17
|
+
): void {
|
|
18
|
+
const isBlock = astContainsChildren(body);
|
|
19
|
+
registerWidget(name, body, params, isBlock);
|
|
20
|
+
if (isBlock) registerBlockMacro(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Register the widgets `nodes` (a passage's markup) define at its top level,
|
|
25
|
+
* as the {widget} macro does when it renders. A widget that fails to register
|
|
26
|
+
* is reported; the others still register.
|
|
27
|
+
*/
|
|
28
|
+
export function registerWidgetDefinitions(
|
|
29
|
+
nodes: ASTNode[],
|
|
30
|
+
passageName: string,
|
|
31
|
+
): void {
|
|
32
|
+
for (const node of nodes) {
|
|
33
|
+
if (node.type === 'macro' && node.name === 'widget' && node.rawArgs) {
|
|
34
|
+
const def = parseWidgetDef(node.rawArgs);
|
|
35
|
+
try {
|
|
36
|
+
registerWidgetDef(def, node.children as ASTNode[]);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(
|
|
39
|
+
`spindle: widget "${def.name}" in passage "${passageName}" was not registered: ${errorMessage(err)}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/types/tooling.d.ts
CHANGED
|
@@ -100,7 +100,13 @@ export declare function defineMacro(config: MacroDefinition): void;
|
|
|
100
100
|
export declare function getMacroRegistry(): MacroMetadata[];
|
|
101
101
|
|
|
102
102
|
/** Variable type inferred from a StoryVariables/StoryTransients default value. */
|
|
103
|
-
export type VarType =
|
|
103
|
+
export type VarType =
|
|
104
|
+
| 'number'
|
|
105
|
+
| 'string'
|
|
106
|
+
| 'boolean'
|
|
107
|
+
| 'array'
|
|
108
|
+
| 'object'
|
|
109
|
+
| 'null';
|
|
104
110
|
|
|
105
111
|
/** Inferred shape of a declared variable (or one of its object fields). */
|
|
106
112
|
export interface FieldSchema {
|