@zseven-w/pen-renderer 0.6.0 → 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
- : (tNode as unknown as Record<string, unknown>).text as string ?? ''
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,135 +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
207
+ const absX = (resolved.x ?? 0) + offsetX;
208
+ const absY = (resolved.y ?? 0) + offsetY;
175
209
 
176
210
  // Compute authoritative dimensions once via getNodeWidth/getNodeHeight.
177
211
  // Used for: RenderNode absW/absH, child available space, and clip rect.
178
212
  // This replaces the prior split where absW/absH used sizeToNumber (raw
179
213
  // parse + 100 fallback) while child layout used getNodeWidth/getNodeHeight,
180
214
  // causing divergence when nodes lacked numeric dimensions.
181
- const nodeW = getNodeWidth(resolved, parentAvailW)
182
- const nodeH = getNodeHeight(resolved, parentAvailH, parentAvailW)
183
- const absW = nodeW > 0 ? nodeW : ('width' in resolved ? sizeToNumber(resolved.width, 100) : 100)
184
- const absH = nodeH > 0 ? nodeH : ('height' in resolved ? sizeToNumber(resolved.height, 100) : 100)
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;
185
220
 
186
221
  result.push({
187
222
  node: { ...resolved, x: absX, y: absY } as PenNode,
188
- absX, absY, absW, absH,
223
+ absX,
224
+ absY,
225
+ absW,
226
+ absH,
189
227
  clipRect: clipCtx,
190
- })
228
+ });
191
229
 
192
230
  // Recurse into children
193
- const children = 'children' in node ? node.children : undefined
231
+ const children = 'children' in node ? node.children : undefined;
194
232
  if (children && children.length > 0) {
195
- const pad = resolvePadding('padding' in resolved ? (resolved as PenNode & ContainerProps).padding : undefined)
196
- const childAvailW = Math.max(0, nodeW - pad.left - pad.right)
197
- 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);
198
238
 
199
- const layout = ('layout' in node ? (node as ContainerProps).layout : undefined) || inferLayout(node)
200
- const positioned = layout && layout !== 'none'
201
- ? computeLayoutPositions(resolved, children)
202
- : 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;
203
243
 
204
244
  // Clipping — only clip for root frames (artboard behavior).
205
- let childClip = clipCtx
206
- const isRootFrame = node.type === 'frame' && depth === 0
245
+ let childClip = clipCtx;
246
+ const isRootFrame = node.type === 'frame' && depth === 0;
207
247
  if (isRootFrame) {
208
- const crRaw = 'cornerRadius' in node ? cornerRadiusVal(node.cornerRadius) : 0
209
- const cr = Math.min(crRaw, nodeH / 2)
210
- 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 };
211
251
  }
212
252
 
213
- 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
+ );
214
262
 
215
263
  // Propagate parent flip to children
216
- const parentFlipX = node.flipX === true
217
- const parentFlipY = node.flipY === true
264
+ const parentFlipX = node.flipX === true;
265
+ const parentFlipY = node.flipY === true;
218
266
  if (parentFlipX || parentFlipY) {
219
- const pcx = absX + nodeW / 2
220
- const pcy = absY + nodeH / 2
267
+ const pcx = absX + nodeW / 2;
268
+ const pcy = absY + nodeH / 2;
221
269
  for (const crn of childRNs) {
222
- const updates: Record<string, unknown> = {}
270
+ const updates: Record<string, unknown> = {};
223
271
  if (parentFlipX) {
224
- const ccx = crn.absX + crn.absW / 2
225
- crn.absX = 2 * pcx - ccx - crn.absW / 2
226
- const childFlip = crn.node.flipX === true
227
- 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;
228
276
  }
229
277
  if (parentFlipY) {
230
- const ccy = crn.absY + crn.absH / 2
231
- crn.absY = 2 * pcy - ccy - crn.absH / 2
232
- const childFlip = crn.node.flipY === true
233
- 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;
234
282
  }
235
- 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;
236
284
  }
237
285
  }
238
286
 
239
287
  // Propagate parent rotation to children
240
- const parentRot = node.rotation ?? 0
288
+ const parentRot = node.rotation ?? 0;
241
289
  if (parentRot !== 0) {
242
- const cx = absX + nodeW / 2
243
- const cy = absY + nodeH / 2
244
- const rad = parentRot * Math.PI / 180
245
- const cosA = Math.cos(rad)
246
- 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);
247
295
 
248
296
  for (const crn of childRNs) {
249
- const ccx = crn.absX + crn.absW / 2
250
- const ccy = crn.absY + crn.absH / 2
251
- const dx = ccx - cx
252
- const dy = ccy - cy
253
- const newCx = cx + dx * cosA - dy * sinA
254
- const newCy = cy + dx * sinA + dy * cosA
255
- crn.absX = newCx - crn.absW / 2
256
- crn.absY = newCy - crn.absH / 2
257
- const childRot = crn.node.rotation ?? 0
258
- 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;
259
312
  }
260
313
  }
261
314
 
262
- result.push(...childRNs)
315
+ result.push(...childRNs);
263
316
  }
264
317
  }
265
318
 
266
- return result
319
+ return result;
267
320
  }
268
321
 
269
322
  // ---------------------------------------------------------------------------
@@ -277,46 +330,56 @@ export function resolveRefs(
277
330
  findInTree?: (nodes: PenNode[], id: string) => PenNode | null,
278
331
  visited = new Set<string>(),
279
332
  ): PenNode[] {
280
- const finder = findInTree ?? ((ns: PenNode[], id: string) => findNodeInTree(ns, id) ?? null)
333
+ const finder = findInTree ?? ((ns: PenNode[], id: string) => findNodeInTree(ns, id) ?? null);
281
334
  return nodes.flatMap((node) => {
282
335
  if (node.type !== 'ref') {
283
336
  if ('children' in node && node.children) {
284
- 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
+ ];
285
340
  }
286
- return [node]
341
+ return [node];
287
342
  }
288
- if (visited.has(node.ref)) return []
289
- const component = finder(rootNodes, node.ref)
290
- if (!component) return []
291
- visited.add(node.ref)
292
- 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 };
293
348
  for (const [key, val] of Object.entries(node)) {
294
- if (key === 'type' || key === 'ref' || key === 'descendants' || key === 'children') continue
295
- if (val !== undefined) resolved[key] = val
349
+ if (key === 'type' || key === 'ref' || key === 'descendants' || key === 'children') continue;
350
+ if (val !== undefined) resolved[key] = val;
296
351
  }
297
- resolved.type = component.type
298
- if (!resolved.name) resolved.name = component.name
299
- delete resolved.reusable
300
- 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;
301
356
  if ('children' in component && component.children) {
302
- const refNode = node as RefNode
303
- ;(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
+ );
304
363
  }
305
- visited.delete(node.ref)
306
- return [resolvedNode]
307
- })
364
+ visited.delete(node.ref);
365
+ return [resolvedNode];
366
+ });
308
367
  }
309
368
 
310
- 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[] {
311
374
  return children.map((child) => {
312
- const virtualId = `${refId}__${child.id}`
313
- const ov = overrides?.[child.id] ?? {}
314
- 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;
315
378
  if ('children' in mapped && mapped.children) {
316
- (mapped as PenNode & ContainerProps).children = remapIds(mapped.children, refId, overrides)
379
+ (mapped as PenNode & ContainerProps).children = remapIds(mapped.children, refId, overrides);
317
380
  }
318
- return mapped
319
- })
381
+ return mapped;
382
+ });
320
383
  }
321
384
 
322
385
  // ---------------------------------------------------------------------------
@@ -326,10 +389,10 @@ export function remapIds(children: PenNode[], refId: string, overrides?: Record<
326
389
  export function collectReusableIds(nodes: PenNode[], result: Set<string>) {
327
390
  for (const node of nodes) {
328
391
  if (node.type === 'frame' && node.reusable === true) {
329
- result.add(node.id)
392
+ result.add(node.id);
330
393
  }
331
394
  if ('children' in node && node.children) {
332
- collectReusableIds(node.children, result)
395
+ collectReusableIds(node.children, result);
333
396
  }
334
397
  }
335
398
  }
@@ -337,10 +400,10 @@ export function collectReusableIds(nodes: PenNode[], result: Set<string>) {
337
400
  export function collectInstanceIds(nodes: PenNode[], result: Set<string>) {
338
401
  for (const node of nodes) {
339
402
  if (node.type === 'ref') {
340
- result.add(node.id)
403
+ result.add(node.id);
341
404
  }
342
405
  if ('children' in node && node.children) {
343
- collectInstanceIds(node.children, result)
406
+ collectInstanceIds(node.children, result);
344
407
  }
345
408
  }
346
409
  }