@rohal12/spindle 0.28.4 → 0.29.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/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
LocalsValuesContext,
|
|
10
10
|
LocalsUpdateContext,
|
|
11
11
|
NobrContext,
|
|
12
|
+
WidgetChildrenContext,
|
|
12
13
|
renderNodes,
|
|
13
14
|
} from '../../markup/render';
|
|
14
15
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
@@ -19,6 +20,7 @@ interface WidgetInvocationProps {
|
|
|
19
20
|
body: ASTNode[];
|
|
20
21
|
params: string[];
|
|
21
22
|
rawArgs?: string;
|
|
23
|
+
invocationChildren?: ASTNode[];
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -110,13 +112,20 @@ export function WidgetInvocation({
|
|
|
110
112
|
body,
|
|
111
113
|
params,
|
|
112
114
|
rawArgs,
|
|
115
|
+
invocationChildren,
|
|
113
116
|
}: WidgetInvocationProps) {
|
|
114
117
|
const parentValues = useContext(LocalsValuesContext);
|
|
115
118
|
const nobr = useContext(NobrContext);
|
|
116
119
|
const [mergedVars, mergedTemps, mergedLocals] = useMergedLocals();
|
|
117
120
|
|
|
121
|
+
const childrenValue = invocationChildren?.length ? invocationChildren : null;
|
|
122
|
+
|
|
118
123
|
if (params.length === 0 || !rawArgs) {
|
|
119
|
-
return
|
|
124
|
+
return (
|
|
125
|
+
<WidgetChildrenContext.Provider value={childrenValue}>
|
|
126
|
+
{renderNodes(body, { nobr, locals: parentValues })}
|
|
127
|
+
</WidgetChildrenContext.Provider>
|
|
128
|
+
);
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
const argExprs = splitArgs(rawArgs);
|
|
@@ -137,10 +146,12 @@ export function WidgetInvocation({
|
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
return (
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
<WidgetChildrenContext.Provider value={childrenValue}>
|
|
150
|
+
<WidgetBody
|
|
151
|
+
body={body}
|
|
152
|
+
parentValues={parentValues}
|
|
153
|
+
ownKeys={ownKeys}
|
|
154
|
+
/>
|
|
155
|
+
</WidgetChildrenContext.Provider>
|
|
145
156
|
);
|
|
146
157
|
}
|
package/src/index.tsx
CHANGED
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
extractDefaults,
|
|
14
14
|
} from './story-variables';
|
|
15
15
|
import { tokenize } from './markup/tokenizer';
|
|
16
|
-
import { buildAST } from './markup/ast';
|
|
16
|
+
import { buildAST, registerBlockMacro } from './markup/ast';
|
|
17
17
|
import { registerWidget } from './widgets/widget-registry';
|
|
18
|
+
import { astContainsChildren } from './widgets/ast-scanner';
|
|
18
19
|
import type { ASTNode } from './markup/ast';
|
|
19
20
|
import './macros/register-builtins';
|
|
20
21
|
import builtinCSS from './styles.css?inline';
|
|
@@ -122,7 +123,12 @@ function boot() {
|
|
|
122
123
|
(t) =>
|
|
123
124
|
t.startsWith('$') || t.startsWith('_') || t.startsWith('@'),
|
|
124
125
|
);
|
|
125
|
-
|
|
126
|
+
const children = node.children as ASTNode[];
|
|
127
|
+
const isBlock = astContainsChildren(children);
|
|
128
|
+
registerWidget(widgetName, children, params, isBlock);
|
|
129
|
+
if (isBlock) {
|
|
130
|
+
registerBlockMacro(widgetName);
|
|
131
|
+
}
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
134
|
}
|
package/src/markup/render.tsx
CHANGED
|
@@ -23,6 +23,7 @@ const defaultUpdater: LocalsUpdater = {
|
|
|
23
23
|
export const LocalsValuesContext = createContext<Record<string, unknown>>({});
|
|
24
24
|
export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
|
|
25
25
|
export const NobrContext = createContext(false);
|
|
26
|
+
export const WidgetChildrenContext = createContext<ASTNode[] | null>(null);
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Convert an HTML string (from micromark) to Preact VNodes,
|
|
@@ -97,6 +98,14 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
|
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
function ChildrenSlot() {
|
|
102
|
+
const childrenAST = useContext(WidgetChildrenContext);
|
|
103
|
+
const nobr = useContext(NobrContext);
|
|
104
|
+
const locals = useContext(LocalsValuesContext);
|
|
105
|
+
if (!childrenAST || childrenAST.length === 0) return null;
|
|
106
|
+
return <>{renderNodes(childrenAST, { nobr, locals })}</>;
|
|
107
|
+
}
|
|
108
|
+
|
|
100
109
|
function renderMacro(node: MacroNode, key: number) {
|
|
101
110
|
if (isSubMacro(node.name)) return null;
|
|
102
111
|
|
|
@@ -108,6 +117,7 @@ function renderMacro(node: MacroNode, key: number) {
|
|
|
108
117
|
body={widget.body}
|
|
109
118
|
params={widget.params}
|
|
110
119
|
rawArgs={node.rawArgs}
|
|
120
|
+
invocationChildren={node.children}
|
|
111
121
|
/>
|
|
112
122
|
);
|
|
113
123
|
}
|
|
@@ -148,6 +158,9 @@ function renderSingleNode(
|
|
|
148
158
|
return node.value;
|
|
149
159
|
|
|
150
160
|
case 'variable':
|
|
161
|
+
if (node.scope === 'local' && node.name === 'children') {
|
|
162
|
+
return <ChildrenSlot key={key} />;
|
|
163
|
+
}
|
|
151
164
|
return (
|
|
152
165
|
<VarDisplay
|
|
153
166
|
key={key}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ASTNode } from '../markup/ast';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively scan an AST node array for a VariableNode
|
|
5
|
+
* with scope 'local' and name 'children' (@children).
|
|
6
|
+
*/
|
|
7
|
+
export function astContainsChildren(nodes: ASTNode[]): boolean {
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
if (
|
|
10
|
+
node.type === 'variable' &&
|
|
11
|
+
node.scope === 'local' &&
|
|
12
|
+
node.name === 'children'
|
|
13
|
+
) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (node.type === 'html' && astContainsChildren(node.children)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (node.type === 'macro') {
|
|
20
|
+
if (astContainsChildren(node.children)) return true;
|
|
21
|
+
if (node.branches) {
|
|
22
|
+
for (const branch of node.branches) {
|
|
23
|
+
if (astContainsChildren(branch.children)) return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { ASTNode } from '../markup/ast';
|
|
|
3
3
|
interface WidgetEntry {
|
|
4
4
|
body: ASTNode[];
|
|
5
5
|
params: string[];
|
|
6
|
+
isBlock: boolean;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
const widgets = new Map<string, WidgetEntry>();
|
|
@@ -11,14 +12,24 @@ export function registerWidget(
|
|
|
11
12
|
name: string,
|
|
12
13
|
bodyAST: ASTNode[],
|
|
13
14
|
params: string[],
|
|
15
|
+
isBlock = false,
|
|
14
16
|
): void {
|
|
15
|
-
|
|
17
|
+
const filteredParams = params.filter((p) => p !== '@children');
|
|
18
|
+
widgets.set(name.toLowerCase(), {
|
|
19
|
+
body: bodyAST,
|
|
20
|
+
params: filteredParams,
|
|
21
|
+
isBlock,
|
|
22
|
+
});
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
export function getWidget(name: string): WidgetEntry | undefined {
|
|
19
26
|
return widgets.get(name.toLowerCase());
|
|
20
27
|
}
|
|
21
28
|
|
|
29
|
+
export function isBlockWidget(name: string): boolean {
|
|
30
|
+
return widgets.get(name.toLowerCase())?.isBlock ?? false;
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
export function clearWidgets(): void {
|
|
23
34
|
widgets.clear();
|
|
24
35
|
}
|