@zseven-w/pen-renderer 0.5.2 → 0.7.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.
@@ -1,4 +1,4 @@
1
- import type { PenNode, ContainerProps, RefNode } from '@zseven-w/pen-types'
1
+ import type { PenNode, ContainerProps, RefNode } from '@zseven-w/pen-types';
2
2
  import {
3
3
  resolvePadding,
4
4
  isNodeVisible,
@@ -10,21 +10,21 @@ import {
10
10
  defaultLineHeight,
11
11
  findNodeInTree,
12
12
  cssFontFamily,
13
- } from '@zseven-w/pen-core'
14
- import { wrapLine } from './paint-utils.js'
15
- import type { RenderNode } from './types.js'
13
+ } from '@zseven-w/pen-core';
14
+ import { wrapLine } from './paint-utils.js';
15
+ import type { RenderNode } from './types.js';
16
16
 
17
17
  // ---------------------------------------------------------------------------
18
18
  // Pre-measure text widths using Canvas 2D (browser fonts)
19
19
  // ---------------------------------------------------------------------------
20
20
 
21
- let _measureCtx: CanvasRenderingContext2D | null = null
21
+ let _measureCtx: CanvasRenderingContext2D | null = null;
22
22
  function getMeasureCtx(): CanvasRenderingContext2D {
23
23
  if (!_measureCtx) {
24
- const c = document.createElement('canvas')
25
- _measureCtx = c.getContext('2d')!
24
+ const c = document.createElement('canvas');
25
+ _measureCtx = c.getContext('2d')!;
26
26
  }
27
- return _measureCtx
27
+ return _measureCtx;
28
28
  }
29
29
 
30
30
  /**
@@ -40,65 +40,84 @@ function getMeasureCtx(): CanvasRenderingContext2D {
40
40
  */
41
41
  export function premeasureTextHeights(nodes: PenNode[]): PenNode[] {
42
42
  return nodes.map((node) => {
43
- let result = node
43
+ let result = node;
44
44
 
45
45
  if (node.type === 'text') {
46
- const tNode = node as PenNode & { width?: number | string; height?: number | string; fontSize?: number; fontWeight?: string; fontFamily?: string; lineHeight?: number; textAlign?: string; textGrowth?: string; content?: string | { text?: string }[] }
47
- const hasFixedWidth = typeof tNode.width === 'number' && tNode.width > 0
48
- const isContainerHeight = typeof tNode.height === 'string'
49
- && (tNode.height === 'fill_container' || tNode.height === 'fit_content')
50
- const textGrowth = tNode.textGrowth
51
- const content = typeof tNode.content === 'string'
52
- ? tNode.content
53
- : Array.isArray(tNode.content)
54
- ? tNode.content.map((s) => s.text ?? '').join('')
55
- : ''
56
-
57
- const textAlign = tNode.textAlign
58
- const isFixedWidthText = textGrowth === 'fixed-width' || textGrowth === 'fixed-width-height'
59
- || (textGrowth !== 'auto' && textAlign != null && textAlign !== 'left')
46
+ const tNode = node as PenNode & {
47
+ width?: number | string;
48
+ height?: number | string;
49
+ fontSize?: number;
50
+ fontWeight?: string;
51
+ fontFamily?: string;
52
+ lineHeight?: number;
53
+ textAlign?: string;
54
+ textGrowth?: string;
55
+ content?: string | { text?: string }[];
56
+ };
57
+ const hasFixedWidth = typeof tNode.width === 'number' && tNode.width > 0;
58
+ const isContainerHeight =
59
+ typeof tNode.height === 'string' &&
60
+ (tNode.height === 'fill_container' || tNode.height === 'fit_content');
61
+ const textGrowth = tNode.textGrowth;
62
+ const content =
63
+ typeof tNode.content === 'string'
64
+ ? tNode.content
65
+ : Array.isArray(tNode.content)
66
+ ? tNode.content.map((s) => s.text ?? '').join('')
67
+ : (((tNode as unknown as Record<string, unknown>).text as string) ?? '');
68
+
69
+ const textAlign = tNode.textAlign;
70
+ const isFixedWidthText =
71
+ textGrowth === 'fixed-width' ||
72
+ textGrowth === 'fixed-width-height' ||
73
+ (textGrowth !== 'auto' && textAlign != null && textAlign !== 'left');
60
74
  if (content && hasFixedWidth && isFixedWidthText && !isContainerHeight) {
61
- const fontSize = tNode.fontSize ?? 16
62
- const fontWeight = tNode.fontWeight ?? '400'
63
- const fontFamily = tNode.fontFamily ?? 'Inter, -apple-system, "Noto Sans SC", "PingFang SC", system-ui, sans-serif'
64
- const ctx = getMeasureCtx()
65
- ctx.font = `${fontWeight} ${fontSize}px ${cssFontFamily(fontFamily)}`
66
-
67
- const wrapWidth = (tNode.width as number) + fontSize * 0.2
68
- const rawLines = content.split('\n')
69
- const wrappedLines: string[] = []
75
+ const fontSize = tNode.fontSize ?? 16;
76
+ const fontWeight = tNode.fontWeight ?? '400';
77
+ const fontFamily =
78
+ tNode.fontFamily ??
79
+ 'Inter, -apple-system, "Noto Sans SC", "PingFang SC", system-ui, sans-serif';
80
+ const ctx = getMeasureCtx();
81
+ ctx.font = `${fontWeight} ${fontSize}px ${cssFontFamily(fontFamily)}`;
82
+
83
+ const wrapWidth = (tNode.width as number) + fontSize * 0.2;
84
+ const rawLines = content.split('\n');
85
+ const wrappedLines: string[] = [];
70
86
  for (const raw of rawLines) {
71
- if (!raw) { wrappedLines.push(''); continue }
72
- wrapLine(ctx, raw, wrapWidth, wrappedLines)
87
+ if (!raw) {
88
+ wrappedLines.push('');
89
+ continue;
90
+ }
91
+ wrapLine(ctx, raw, wrapWidth, wrappedLines);
73
92
  }
74
- const lineHeightMul = tNode.lineHeight ?? defaultLineHeight(fontSize)
75
- const lineHeight = lineHeightMul * fontSize
76
- const glyphH = fontSize * 1.13
93
+ const lineHeightMul = tNode.lineHeight ?? defaultLineHeight(fontSize);
94
+ const lineHeight = lineHeightMul * fontSize;
95
+ const glyphH = fontSize * 1.13;
77
96
  const measuredHeight = Math.ceil(
78
97
  wrappedLines.length <= 1
79
98
  ? glyphH + 2
80
99
  : (wrappedLines.length - 1) * lineHeight + glyphH + 2,
81
- )
82
- const currentHeight = typeof tNode.height === 'number' ? tNode.height : 0
83
- const explicitLineCount = rawLines.length
84
- const needsHeight = currentHeight <= 0 || wrappedLines.length > explicitLineCount
100
+ );
101
+ const currentHeight = typeof tNode.height === 'number' ? tNode.height : 0;
102
+ const explicitLineCount = rawLines.length;
103
+ const needsHeight = currentHeight <= 0 || wrappedLines.length > explicitLineCount;
85
104
  if (needsHeight && measuredHeight > currentHeight) {
86
- result = { ...node, height: measuredHeight } as unknown as PenNode
105
+ result = { ...node, height: measuredHeight } as unknown as PenNode;
87
106
  }
88
107
  }
89
108
  }
90
109
 
91
110
  // Recurse into children
92
111
  if ('children' in result && result.children) {
93
- const children = result.children
94
- const measured = premeasureTextHeights(children)
112
+ const children = result.children;
113
+ const measured = premeasureTextHeights(children);
95
114
  if (measured !== children) {
96
- result = { ...result, children: measured } as unknown as PenNode
115
+ result = { ...result, children: measured } as unknown as PenNode;
97
116
  }
98
117
  }
99
118
 
100
- return result
101
- })
119
+ return result;
120
+ });
102
121
  }
103
122
 
104
123
  // ---------------------------------------------------------------------------
@@ -106,24 +125,28 @@ export function premeasureTextHeights(nodes: PenNode[]): PenNode[] {
106
125
  // ---------------------------------------------------------------------------
107
126
 
108
127
  interface ClipInfo {
109
- x: number; y: number; w: number; h: number; rx: number
128
+ x: number;
129
+ y: number;
130
+ w: number;
131
+ h: number;
132
+ rx: number;
110
133
  }
111
134
 
112
135
  function sizeToNumber(val: number | string | undefined, fallback: number): number {
113
- if (typeof val === 'number') return val
136
+ if (typeof val === 'number') return val;
114
137
  if (typeof val === 'string') {
115
- const m = val.match(/\((\d+(?:\.\d+)?)\)/)
116
- if (m) return parseFloat(m[1])
117
- const n = parseFloat(val)
118
- if (!isNaN(n)) return n
138
+ const m = val.match(/\((\d+(?:\.\d+)?)\)/);
139
+ if (m) return parseFloat(m[1]);
140
+ const n = parseFloat(val);
141
+ if (!isNaN(n)) return n;
119
142
  }
120
- return fallback
143
+ return fallback;
121
144
  }
122
145
 
123
146
  function cornerRadiusVal(cr: number | [number, number, number, number] | undefined): number {
124
- if (cr === undefined) return 0
125
- if (typeof cr === 'number') return cr
126
- return cr[0]
147
+ if (cr === undefined) return 0;
148
+ if (typeof cr === 'number') return cr;
149
+ return cr[0];
127
150
  }
128
151
 
129
152
  export function flattenToRenderNodes(
@@ -135,129 +158,165 @@ export function flattenToRenderNodes(
135
158
  clipCtx?: ClipInfo,
136
159
  depth = 0,
137
160
  ): RenderNode[] {
138
- const result: RenderNode[] = []
161
+ const result: RenderNode[] = [];
139
162
 
140
163
  // Reverse order: children[0] = top layer = rendered last (frontmost)
141
164
  for (let i = nodes.length - 1; i >= 0; i--) {
142
- const node = nodes[i]
143
- if (!isNodeVisible(node)) continue
165
+ const node = nodes[i];
166
+ if (!isNodeVisible(node)) continue;
144
167
 
145
168
  // Resolve fill_container / fit_content
146
- let resolved = node
169
+ let resolved = node;
147
170
  if (parentAvailW !== undefined || parentAvailH !== undefined) {
148
- let changed = false
149
- const r: Record<string, unknown> = { ...node }
171
+ let changed = false;
172
+ const r: Record<string, unknown> = { ...node };
150
173
  if ('width' in node && typeof node.width !== 'number') {
151
- const s = parseSizing(node.width)
152
- if (s === 'fill' && parentAvailW) { r.width = parentAvailW; changed = true }
153
- else if (s === 'fit') { r.width = getNodeWidth(node, parentAvailW); changed = true }
174
+ const s = parseSizing(node.width);
175
+ if (s === 'fill' && parentAvailW) {
176
+ r.width = parentAvailW;
177
+ changed = true;
178
+ } else if (s === 'fit') {
179
+ r.width = getNodeWidth(node, parentAvailW);
180
+ changed = true;
181
+ }
154
182
  }
155
183
  if ('height' in node && typeof node.height !== 'number') {
156
- const s = parseSizing(node.height)
157
- if (s === 'fill' && parentAvailH) { r.height = parentAvailH; changed = true }
158
- else if (s === 'fit') { r.height = getNodeHeight(node, parentAvailH, parentAvailW); changed = true }
184
+ const s = parseSizing(node.height);
185
+ if (s === 'fill' && parentAvailH) {
186
+ r.height = parentAvailH;
187
+ changed = true;
188
+ } else if (s === 'fit') {
189
+ r.height = getNodeHeight(node, parentAvailH, parentAvailW);
190
+ changed = true;
191
+ }
159
192
  }
160
- if (changed) resolved = r as unknown as PenNode
193
+ if (changed) resolved = r as unknown as PenNode;
161
194
  }
162
195
 
163
196
  // Compute height for frames without explicit numeric height
164
197
  if (
165
- node.type === 'frame'
166
- && 'children' in node && node.children?.length
167
- && (!('height' in resolved) || typeof resolved.height !== 'number')
198
+ node.type === 'frame' &&
199
+ 'children' in node &&
200
+ node.children?.length &&
201
+ (!('height' in resolved) || typeof resolved.height !== 'number')
168
202
  ) {
169
- const computedH = getNodeHeight(resolved, parentAvailH, parentAvailW)
170
- if (computedH > 0) resolved = { ...resolved, height: computedH } as unknown as PenNode
203
+ const computedH = getNodeHeight(resolved, parentAvailH, parentAvailW);
204
+ if (computedH > 0) resolved = { ...resolved, height: computedH } as unknown as PenNode;
171
205
  }
172
206
 
173
- const absX = (resolved.x ?? 0) + offsetX
174
- const absY = (resolved.y ?? 0) + offsetY
175
- const absW = 'width' in resolved ? sizeToNumber(resolved.width, 100) : 100
176
- const absH = 'height' in resolved ? sizeToNumber(resolved.height, 100) : 100
207
+ const absX = (resolved.x ?? 0) + offsetX;
208
+ const absY = (resolved.y ?? 0) + offsetY;
209
+
210
+ // Compute authoritative dimensions once via getNodeWidth/getNodeHeight.
211
+ // Used for: RenderNode absW/absH, child available space, and clip rect.
212
+ // This replaces the prior split where absW/absH used sizeToNumber (raw
213
+ // parse + 100 fallback) while child layout used getNodeWidth/getNodeHeight,
214
+ // causing divergence when nodes lacked numeric dimensions.
215
+ const nodeW = getNodeWidth(resolved, parentAvailW);
216
+ const nodeH = getNodeHeight(resolved, parentAvailH, parentAvailW);
217
+ const absW = nodeW > 0 ? nodeW : 'width' in resolved ? sizeToNumber(resolved.width, 100) : 100;
218
+ const absH =
219
+ nodeH > 0 ? nodeH : 'height' in resolved ? sizeToNumber(resolved.height, 100) : 100;
177
220
 
178
221
  result.push({
179
222
  node: { ...resolved, x: absX, y: absY } as PenNode,
180
- absX, absY, absW, absH,
223
+ absX,
224
+ absY,
225
+ absW,
226
+ absH,
181
227
  clipRect: clipCtx,
182
- })
228
+ });
183
229
 
184
230
  // Recurse into children
185
- const children = 'children' in node ? node.children : undefined
231
+ const children = 'children' in node ? node.children : undefined;
186
232
  if (children && children.length > 0) {
187
- const nodeW = getNodeWidth(resolved, parentAvailW)
188
- const nodeH = getNodeHeight(resolved, parentAvailH, parentAvailW)
189
- const pad = resolvePadding('padding' in resolved ? (resolved as PenNode & ContainerProps).padding : undefined)
190
- const childAvailW = Math.max(0, nodeW - pad.left - pad.right)
191
- const childAvailH = Math.max(0, nodeH - pad.top - pad.bottom)
233
+ const pad = resolvePadding(
234
+ 'padding' in resolved ? (resolved as PenNode & ContainerProps).padding : undefined,
235
+ );
236
+ const childAvailW = Math.max(0, nodeW - pad.left - pad.right);
237
+ const childAvailH = Math.max(0, nodeH - pad.top - pad.bottom);
192
238
 
193
- const layout = ('layout' in node ? (node as ContainerProps).layout : undefined) || inferLayout(node)
194
- const positioned = layout && layout !== 'none'
195
- ? computeLayoutPositions(resolved, children)
196
- : children
239
+ const layout =
240
+ ('layout' in node ? (node as ContainerProps).layout : undefined) || inferLayout(node);
241
+ const positioned =
242
+ layout && layout !== 'none' ? computeLayoutPositions(resolved, children) : children;
197
243
 
198
244
  // Clipping — only clip for root frames (artboard behavior).
199
- let childClip = clipCtx
200
- const isRootFrame = node.type === 'frame' && depth === 0
245
+ let childClip = clipCtx;
246
+ const isRootFrame = node.type === 'frame' && depth === 0;
201
247
  if (isRootFrame) {
202
- const crRaw = 'cornerRadius' in node ? cornerRadiusVal(node.cornerRadius) : 0
203
- const cr = Math.min(crRaw, nodeH / 2)
204
- childClip = { x: absX, y: absY, w: nodeW, h: nodeH, rx: cr }
248
+ const crRaw = 'cornerRadius' in node ? cornerRadiusVal(node.cornerRadius) : 0;
249
+ const cr = Math.min(crRaw, nodeH / 2);
250
+ childClip = { x: absX, y: absY, w: nodeW, h: nodeH, rx: cr };
205
251
  }
206
252
 
207
- const childRNs = flattenToRenderNodes(positioned, absX, absY, childAvailW, childAvailH, childClip, depth + 1)
253
+ const childRNs = flattenToRenderNodes(
254
+ positioned,
255
+ absX,
256
+ absY,
257
+ childAvailW,
258
+ childAvailH,
259
+ childClip,
260
+ depth + 1,
261
+ );
208
262
 
209
263
  // Propagate parent flip to children
210
- const parentFlipX = node.flipX === true
211
- const parentFlipY = node.flipY === true
264
+ const parentFlipX = node.flipX === true;
265
+ const parentFlipY = node.flipY === true;
212
266
  if (parentFlipX || parentFlipY) {
213
- const pcx = absX + nodeW / 2
214
- const pcy = absY + nodeH / 2
267
+ const pcx = absX + nodeW / 2;
268
+ const pcy = absY + nodeH / 2;
215
269
  for (const crn of childRNs) {
216
- const updates: Record<string, unknown> = {}
270
+ const updates: Record<string, unknown> = {};
217
271
  if (parentFlipX) {
218
- const ccx = crn.absX + crn.absW / 2
219
- crn.absX = 2 * pcx - ccx - crn.absW / 2
220
- const childFlip = crn.node.flipX === true
221
- updates.flipX = !childFlip || undefined
272
+ const ccx = crn.absX + crn.absW / 2;
273
+ crn.absX = 2 * pcx - ccx - crn.absW / 2;
274
+ const childFlip = crn.node.flipX === true;
275
+ updates.flipX = !childFlip || undefined;
222
276
  }
223
277
  if (parentFlipY) {
224
- const ccy = crn.absY + crn.absH / 2
225
- crn.absY = 2 * pcy - ccy - crn.absH / 2
226
- const childFlip = crn.node.flipY === true
227
- updates.flipY = !childFlip || undefined
278
+ const ccy = crn.absY + crn.absH / 2;
279
+ crn.absY = 2 * pcy - ccy - crn.absH / 2;
280
+ const childFlip = crn.node.flipY === true;
281
+ updates.flipY = !childFlip || undefined;
228
282
  }
229
- crn.node = { ...crn.node, x: crn.absX, y: crn.absY, ...updates } as PenNode
283
+ crn.node = { ...crn.node, x: crn.absX, y: crn.absY, ...updates } as PenNode;
230
284
  }
231
285
  }
232
286
 
233
287
  // Propagate parent rotation to children
234
- const parentRot = node.rotation ?? 0
288
+ const parentRot = node.rotation ?? 0;
235
289
  if (parentRot !== 0) {
236
- const cx = absX + nodeW / 2
237
- const cy = absY + nodeH / 2
238
- const rad = parentRot * Math.PI / 180
239
- const cosA = Math.cos(rad)
240
- const sinA = Math.sin(rad)
290
+ const cx = absX + nodeW / 2;
291
+ const cy = absY + nodeH / 2;
292
+ const rad = (parentRot * Math.PI) / 180;
293
+ const cosA = Math.cos(rad);
294
+ const sinA = Math.sin(rad);
241
295
 
242
296
  for (const crn of childRNs) {
243
- const ccx = crn.absX + crn.absW / 2
244
- const ccy = crn.absY + crn.absH / 2
245
- const dx = ccx - cx
246
- const dy = ccy - cy
247
- const newCx = cx + dx * cosA - dy * sinA
248
- const newCy = cy + dx * sinA + dy * cosA
249
- crn.absX = newCx - crn.absW / 2
250
- crn.absY = newCy - crn.absH / 2
251
- const childRot = crn.node.rotation ?? 0
252
- crn.node = { ...crn.node, x: crn.absX, y: crn.absY, rotation: childRot + parentRot } as PenNode
297
+ const ccx = crn.absX + crn.absW / 2;
298
+ const ccy = crn.absY + crn.absH / 2;
299
+ const dx = ccx - cx;
300
+ const dy = ccy - cy;
301
+ const newCx = cx + dx * cosA - dy * sinA;
302
+ const newCy = cy + dx * sinA + dy * cosA;
303
+ crn.absX = newCx - crn.absW / 2;
304
+ crn.absY = newCy - crn.absH / 2;
305
+ const childRot = crn.node.rotation ?? 0;
306
+ crn.node = {
307
+ ...crn.node,
308
+ x: crn.absX,
309
+ y: crn.absY,
310
+ rotation: childRot + parentRot,
311
+ } as PenNode;
253
312
  }
254
313
  }
255
314
 
256
- result.push(...childRNs)
315
+ result.push(...childRNs);
257
316
  }
258
317
  }
259
318
 
260
- return result
319
+ return result;
261
320
  }
262
321
 
263
322
  // ---------------------------------------------------------------------------
@@ -271,46 +330,56 @@ export function resolveRefs(
271
330
  findInTree?: (nodes: PenNode[], id: string) => PenNode | null,
272
331
  visited = new Set<string>(),
273
332
  ): PenNode[] {
274
- const finder = findInTree ?? ((ns: PenNode[], id: string) => findNodeInTree(ns, id) ?? null)
333
+ const finder = findInTree ?? ((ns: PenNode[], id: string) => findNodeInTree(ns, id) ?? null);
275
334
  return nodes.flatMap((node) => {
276
335
  if (node.type !== 'ref') {
277
336
  if ('children' in node && node.children) {
278
- return [{ ...node, children: resolveRefs(node.children, rootNodes, finder, visited) } as PenNode]
337
+ return [
338
+ { ...node, children: resolveRefs(node.children, rootNodes, finder, visited) } as PenNode,
339
+ ];
279
340
  }
280
- return [node]
341
+ return [node];
281
342
  }
282
- if (visited.has(node.ref)) return []
283
- const component = finder(rootNodes, node.ref)
284
- if (!component) return []
285
- visited.add(node.ref)
286
- const resolved: Record<string, unknown> = { ...component }
343
+ if (visited.has(node.ref)) return [];
344
+ const component = finder(rootNodes, node.ref);
345
+ if (!component) return [];
346
+ visited.add(node.ref);
347
+ const resolved: Record<string, unknown> = { ...component };
287
348
  for (const [key, val] of Object.entries(node)) {
288
- if (key === 'type' || key === 'ref' || key === 'descendants' || key === 'children') continue
289
- if (val !== undefined) resolved[key] = val
349
+ if (key === 'type' || key === 'ref' || key === 'descendants' || key === 'children') continue;
350
+ if (val !== undefined) resolved[key] = val;
290
351
  }
291
- resolved.type = component.type
292
- if (!resolved.name) resolved.name = component.name
293
- delete resolved.reusable
294
- const resolvedNode = resolved as unknown as PenNode
352
+ resolved.type = component.type;
353
+ if (!resolved.name) resolved.name = component.name;
354
+ delete resolved.reusable;
355
+ const resolvedNode = resolved as unknown as PenNode;
295
356
  if ('children' in component && component.children) {
296
- const refNode = node as RefNode
297
- ;(resolvedNode as PenNode & ContainerProps).children = remapIds(component.children, node.id, refNode.descendants)
357
+ const refNode = node as RefNode;
358
+ (resolvedNode as PenNode & ContainerProps).children = remapIds(
359
+ component.children,
360
+ node.id,
361
+ refNode.descendants,
362
+ );
298
363
  }
299
- visited.delete(node.ref)
300
- return [resolvedNode]
301
- })
364
+ visited.delete(node.ref);
365
+ return [resolvedNode];
366
+ });
302
367
  }
303
368
 
304
- export function remapIds(children: PenNode[], refId: string, overrides?: Record<string, Partial<PenNode>>): PenNode[] {
369
+ export function remapIds(
370
+ children: PenNode[],
371
+ refId: string,
372
+ overrides?: Record<string, Partial<PenNode>>,
373
+ ): PenNode[] {
305
374
  return children.map((child) => {
306
- const virtualId = `${refId}__${child.id}`
307
- const ov = overrides?.[child.id] ?? {}
308
- const mapped = { ...child, ...ov, id: virtualId } as PenNode
375
+ const virtualId = `${refId}__${child.id}`;
376
+ const ov = overrides?.[child.id] ?? {};
377
+ const mapped = { ...child, ...ov, id: virtualId } as PenNode;
309
378
  if ('children' in mapped && mapped.children) {
310
- (mapped as PenNode & ContainerProps).children = remapIds(mapped.children, refId, overrides)
379
+ (mapped as PenNode & ContainerProps).children = remapIds(mapped.children, refId, overrides);
311
380
  }
312
- return mapped
313
- })
381
+ return mapped;
382
+ });
314
383
  }
315
384
 
316
385
  // ---------------------------------------------------------------------------
@@ -320,10 +389,10 @@ export function remapIds(children: PenNode[], refId: string, overrides?: Record<
320
389
  export function collectReusableIds(nodes: PenNode[], result: Set<string>) {
321
390
  for (const node of nodes) {
322
391
  if (node.type === 'frame' && node.reusable === true) {
323
- result.add(node.id)
392
+ result.add(node.id);
324
393
  }
325
394
  if ('children' in node && node.children) {
326
- collectReusableIds(node.children, result)
395
+ collectReusableIds(node.children, result);
327
396
  }
328
397
  }
329
398
  }
@@ -331,10 +400,10 @@ export function collectReusableIds(nodes: PenNode[], result: Set<string>) {
331
400
  export function collectInstanceIds(nodes: PenNode[], result: Set<string>) {
332
401
  for (const node of nodes) {
333
402
  if (node.type === 'ref') {
334
- result.add(node.id)
403
+ result.add(node.id);
335
404
  }
336
405
  if ('children' in node && node.children) {
337
- collectInstanceIds(node.children, result)
406
+ collectInstanceIds(node.children, result);
338
407
  }
339
408
  }
340
409
  }