@rohal12/spindle 0.45.0 → 0.46.0
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/package.json +1 -1
- package/src/define-macro.ts +1 -1
- package/src/types-drift-check.ts +68 -0
- package/types/index.d.ts +113 -25
package/package.json
CHANGED
package/src/define-macro.ts
CHANGED
|
@@ -88,7 +88,7 @@ export interface MacroDefinition {
|
|
|
88
88
|
storeVar?: boolean;
|
|
89
89
|
description?: string;
|
|
90
90
|
parameters?: ParameterDef[];
|
|
91
|
-
render: (props: MacroProps, ctx: MacroContext) =>
|
|
91
|
+
render: (props: MacroProps, ctx: MacroContext) => ComponentChildren;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const sharedHooks = {
|
package/src/types-drift-check.ts
CHANGED
|
@@ -23,3 +23,71 @@ import type { parseStoryVariables as PublishedParse } from '../types/tooling';
|
|
|
23
23
|
const _parseSourceToPublished: typeof PublishedParse = {} as typeof SourceParse;
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
25
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
|
+
import type {
|
|
30
|
+
MacroContext as SourceMacroContext,
|
|
31
|
+
MacroDefinition as SourceMacroDefinition,
|
|
32
|
+
} from './define-macro';
|
|
33
|
+
import type { MacroProps as SourceMacroProps } from './registry';
|
|
34
|
+
import type { ASTNode as SourceASTNode } from './markup/ast';
|
|
35
|
+
import type {
|
|
36
|
+
MacroContext as PublishedMacroContext,
|
|
37
|
+
MacroDefinition as PublishedMacroDefinition,
|
|
38
|
+
MacroProps as PublishedMacroProps,
|
|
39
|
+
ASTNode as PublishedASTNode,
|
|
40
|
+
} from '../types/index';
|
|
41
|
+
|
|
42
|
+
// 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;
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
59
|
+
const _astPublishedToSource: SourceASTNode = {} as PublishedASTNode;
|
|
60
|
+
|
|
61
|
+
// A typical custom macro written against the published types must type-check,
|
|
62
|
+
// and misuse of the hooks must not (no `any` leaking through).
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
64
|
+
const _exampleMacro: PublishedMacroDefinition = {
|
|
65
|
+
name: 'counter',
|
|
66
|
+
block: true,
|
|
67
|
+
render(props, ctx) {
|
|
68
|
+
const [count, setCount] = ctx.hooks.useState(0);
|
|
69
|
+
const countIsNumber: number = count;
|
|
70
|
+
setCount((n) => n + 1);
|
|
71
|
+
// @ts-expect-error -- state setter is typed by the initial value
|
|
72
|
+
setCount('one');
|
|
73
|
+
const ref = ctx.hooks.useRef<HTMLSpanElement>(null);
|
|
74
|
+
ctx.hooks.useEffect(() => {
|
|
75
|
+
ref.current?.focus();
|
|
76
|
+
return () => undefined;
|
|
77
|
+
}, [count]);
|
|
78
|
+
// @ts-expect-error -- deps must be an array
|
|
79
|
+
ctx.hooks.useEffect(() => undefined, 'count');
|
|
80
|
+
const doubled: number = ctx.hooks.useMemo(() => count * 2, [count]);
|
|
81
|
+
const body = ctx.renderNodes(props.children ?? [], { nobr: true });
|
|
82
|
+
const label: string = ctx.collectText(props.children ?? []);
|
|
83
|
+
return ctx.wrap(
|
|
84
|
+
ctx.h(
|
|
85
|
+
'span',
|
|
86
|
+
{ ref, class: ctx.cls, title: label },
|
|
87
|
+
body,
|
|
88
|
+
countIsNumber,
|
|
89
|
+
doubled,
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import type { ComponentChildren, VNode, h } from 'preact';
|
|
2
|
+
import type {
|
|
3
|
+
useState,
|
|
4
|
+
useRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
useContext,
|
|
10
|
+
} from 'preact/hooks';
|
|
11
|
+
|
|
1
12
|
// Format metadata (used by twee-ts)
|
|
2
13
|
export declare const name: string;
|
|
3
14
|
export declare const version: string;
|
|
@@ -242,6 +253,68 @@ export interface MacroMetadata {
|
|
|
242
253
|
parameters?: ParameterDef[];
|
|
243
254
|
}
|
|
244
255
|
|
|
256
|
+
/** Plain text in a parsed passage. */
|
|
257
|
+
export interface TextNode {
|
|
258
|
+
type: 'text';
|
|
259
|
+
value: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** A variable display such as `{$hp}`, `{_tmp}`, `{@local}` or `{%transient}`. */
|
|
263
|
+
export interface VariableNode {
|
|
264
|
+
type: 'variable';
|
|
265
|
+
name: string;
|
|
266
|
+
scope: 'variable' | 'temporary' | 'local' | 'transient';
|
|
267
|
+
className?: string;
|
|
268
|
+
id?: string;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** An expression display such as `{= $hp * 2}`. */
|
|
272
|
+
export interface ExpressionNode {
|
|
273
|
+
type: 'expression';
|
|
274
|
+
expression: string;
|
|
275
|
+
className?: string;
|
|
276
|
+
id?: string;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** One branch of a block macro (`{elseif}`, `{else}`, `{case}`, ...). */
|
|
280
|
+
export interface Branch {
|
|
281
|
+
rawArgs: string;
|
|
282
|
+
className?: string;
|
|
283
|
+
id?: string;
|
|
284
|
+
children: ASTNode[];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** A macro invocation, with its body and branches for block macros. */
|
|
288
|
+
export interface MacroNode {
|
|
289
|
+
type: 'macro';
|
|
290
|
+
name: string;
|
|
291
|
+
rawArgs: string;
|
|
292
|
+
children: ASTNode[];
|
|
293
|
+
branches?: Branch[];
|
|
294
|
+
className?: string;
|
|
295
|
+
id?: string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** An HTML element written in passage markup. */
|
|
299
|
+
export interface HtmlNode {
|
|
300
|
+
type: 'html';
|
|
301
|
+
tag: string;
|
|
302
|
+
attributes: Record<string, string>;
|
|
303
|
+
children: ASTNode[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* A node of a parsed passage. Macros receive their body as `props.children`
|
|
308
|
+
* and render it with `ctx.renderNodes()`.
|
|
309
|
+
* @see {@link ../../src/markup/ast.ts} for the implementation.
|
|
310
|
+
*/
|
|
311
|
+
export type ASTNode =
|
|
312
|
+
| TextNode
|
|
313
|
+
| VariableNode
|
|
314
|
+
| ExpressionNode
|
|
315
|
+
| MacroNode
|
|
316
|
+
| HtmlNode;
|
|
317
|
+
|
|
245
318
|
/**
|
|
246
319
|
* Props passed to a macro's render function.
|
|
247
320
|
* @see {@link ../../src/registry.ts} for the implementation.
|
|
@@ -250,13 +323,8 @@ export interface MacroProps {
|
|
|
250
323
|
rawArgs: string;
|
|
251
324
|
className?: string;
|
|
252
325
|
id?: string;
|
|
253
|
-
children?:
|
|
254
|
-
branches?:
|
|
255
|
-
rawArgs: string;
|
|
256
|
-
className?: string;
|
|
257
|
-
id?: string;
|
|
258
|
-
children: any[];
|
|
259
|
-
}>;
|
|
326
|
+
children?: ASTNode[];
|
|
327
|
+
branches?: Branch[];
|
|
260
328
|
}
|
|
261
329
|
|
|
262
330
|
/**
|
|
@@ -278,8 +346,9 @@ export interface UseActionOptions {
|
|
|
278
346
|
|
|
279
347
|
/**
|
|
280
348
|
* Context object passed to a macro's render function alongside props.
|
|
281
|
-
*
|
|
282
|
-
*
|
|
349
|
+
*
|
|
350
|
+
* Rendering helpers and hooks are Preact's own (`preact` is a dependency of
|
|
351
|
+
* this package), so `ctx.hooks.useState<T>()`, `ctx.h()` etc. are fully typed.
|
|
283
352
|
* @see {@link ../../src/define-macro.ts} for the implementation.
|
|
284
353
|
*/
|
|
285
354
|
export interface MacroContext {
|
|
@@ -301,26 +370,38 @@ export interface MacroContext {
|
|
|
301
370
|
setValue?: (value: unknown) => void;
|
|
302
371
|
getValue?: () => unknown;
|
|
303
372
|
evaluate?: (expr: string) => unknown;
|
|
304
|
-
|
|
373
|
+
/** Concatenate the text nodes of an AST (e.g. a macro body holding a passage name). */
|
|
374
|
+
collectText: (nodes: ASTNode[]) => string;
|
|
375
|
+
/** Source location of the current passage, for error messages. */
|
|
305
376
|
sourceLocation: () => string;
|
|
306
377
|
parseVarArgs: (rawArgs: string) => { varName: string; placeholder: string };
|
|
307
|
-
|
|
308
|
-
|
|
378
|
+
/** Collect the labels of `{option}` sub-macros in a macro body. */
|
|
379
|
+
extractOptions: (children: ASTNode[]) => string[];
|
|
380
|
+
/** Wrap content in a `<span>` carrying the macro's class/id, or a fragment if it has neither. */
|
|
381
|
+
wrap: (content: ComponentChildren) => VNode<any>;
|
|
309
382
|
useAction: (opts: UseActionOptions) => string;
|
|
310
|
-
h
|
|
383
|
+
/** Preact's `h` (createElement). */
|
|
384
|
+
h: typeof h;
|
|
385
|
+
/** Render AST nodes as block content (markdown, `<p>` wrapping unless nobr). */
|
|
311
386
|
renderNodes: (
|
|
312
|
-
nodes:
|
|
313
|
-
options?: {
|
|
314
|
-
|
|
315
|
-
|
|
387
|
+
nodes: ASTNode[],
|
|
388
|
+
options?: {
|
|
389
|
+
nobr?: boolean;
|
|
390
|
+
locals?: Record<string, unknown>;
|
|
391
|
+
inline?: boolean;
|
|
392
|
+
},
|
|
393
|
+
) => ComponentChildren;
|
|
394
|
+
/** Render AST nodes as inline content (no markdown block processing). */
|
|
395
|
+
renderInlineNodes: (nodes: ASTNode[]) => ComponentChildren;
|
|
396
|
+
/** Preact hooks, shared with Spindle's own Preact instance. */
|
|
316
397
|
hooks: {
|
|
317
|
-
useState:
|
|
318
|
-
useRef:
|
|
319
|
-
useEffect:
|
|
320
|
-
useLayoutEffect:
|
|
321
|
-
useCallback:
|
|
322
|
-
useMemo:
|
|
323
|
-
useContext:
|
|
398
|
+
useState: typeof useState;
|
|
399
|
+
useRef: typeof useRef;
|
|
400
|
+
useEffect: typeof useEffect;
|
|
401
|
+
useLayoutEffect: typeof useLayoutEffect;
|
|
402
|
+
useCallback: typeof useCallback;
|
|
403
|
+
useMemo: typeof useMemo;
|
|
404
|
+
useContext: typeof useContext;
|
|
324
405
|
};
|
|
325
406
|
}
|
|
326
407
|
|
|
@@ -330,14 +411,21 @@ export interface MacroContext {
|
|
|
330
411
|
*/
|
|
331
412
|
export interface MacroDefinition {
|
|
332
413
|
name: string;
|
|
414
|
+
/** Sub-macro names (e.g. `['option']`); a non-empty list makes the macro a block macro. */
|
|
333
415
|
subMacros?: string[];
|
|
416
|
+
/** Accept a `{name}...{/name}` body. Inferred from `subMacros` when omitted. */
|
|
334
417
|
block?: boolean;
|
|
418
|
+
/** Resolve `{$var}` interpolation in the macro's class/id (`ctx.resolve`). */
|
|
335
419
|
interpolate?: boolean;
|
|
420
|
+
/** Provide `ctx.merged` and `ctx.evaluate` (variables, temporaries, locals, transients). */
|
|
336
421
|
merged?: boolean;
|
|
422
|
+
/** Bind the first argument as a story variable (`ctx.varName`, `ctx.value`, `ctx.setValue`). */
|
|
337
423
|
storeVar?: boolean;
|
|
424
|
+
/** Tooling hint: one-line description shown by editors. */
|
|
338
425
|
description?: string;
|
|
426
|
+
/** Tooling hint: positional parameters. */
|
|
339
427
|
parameters?: ParameterDef[];
|
|
340
|
-
render: (props: MacroProps, ctx: MacroContext) =>
|
|
428
|
+
render: (props: MacroProps, ctx: MacroContext) => ComponentChildren;
|
|
341
429
|
}
|
|
342
430
|
|
|
343
431
|
/**
|