@dannote/figma-use 0.6.0 → 0.6.2

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,32 +1,32 @@
1
1
  /**
2
2
  * React Reconciler that outputs Figma NodeChanges directly
3
- *
3
+ *
4
4
  * Renders React components directly to NodeChanges array
5
5
  * ready for multiplayer WebSocket transmission.
6
- *
6
+ *
7
7
  * ## Key implementation notes (discovered via protocol sniffing)
8
- *
8
+ *
9
9
  * ### TEXT nodes require specific fields
10
10
  * - fontName: { family, style, postscript } - ALWAYS required, even without explicit font
11
11
  * - textAlignVertical: 'TOP' - required for height calculation
12
12
  * - lineHeight: { value: 100, units: 'PERCENT' } - CRITICAL! Without this, height=0
13
13
  * - textData: { characters: '...' } - text content wrapper
14
- *
14
+ *
15
15
  * ### Auto-layout field names differ from Plugin API
16
16
  * - justifyContent → stackPrimaryAlignItems (not stackJustify)
17
17
  * - alignItems → stackCounterAlignItems (not stackCounterAlign)
18
18
  * - Valid values: 'MIN', 'CENTER', 'MAX', 'SPACE_BETWEEN', 'SPACE_EVENLY'
19
- *
19
+ *
20
20
  * ### Sizing modes for auto-layout
21
21
  * - stackPrimarySizing/stackCounterSizing = 'FIXED' when explicit size given
22
22
  * - stackPrimarySizing/stackCounterSizing = 'RESIZE_TO_FIT' for hug contents
23
23
  * - Setting RESIZE_TO_FIT via multiplayer doesn't work; use Plugin API trigger-layout
24
- *
24
+ *
25
25
  * ### Component types
26
26
  * - SYMBOL (15) = Component in Figma (historical name)
27
27
  * - INSTANCE (16) = Component instance
28
28
  * - ComponentSet = FRAME with isStateGroup=true (field 225)
29
- *
29
+ *
30
30
  * See also: component-set.tsx for ComponentSet/Instance linking issues
31
31
  */
32
32
 
@@ -36,11 +36,11 @@ import type { NodeChange, Paint } from '../multiplayer/codec.ts'
36
36
  import { parseColor } from '../color.ts'
37
37
  import { isVariable, resolveVariable, type FigmaVariable } from './vars.ts'
38
38
  import { getComponentRegistry } from './components.tsx'
39
- import {
40
- getComponentSetRegistry,
41
- generateVariantCombinations,
42
- buildVariantName,
43
- buildStateGroupPropertyValueOrders
39
+ import {
40
+ getComponentSetRegistry,
41
+ generateVariantCombinations,
42
+ buildVariantName,
43
+ buildStateGroupPropertyValueOrders
44
44
  } from './component-set.tsx'
45
45
 
46
46
  // Track rendered components: symbol -> GUID
@@ -49,7 +49,10 @@ const renderedComponents = new Map<symbol, { sessionID: number; localID: number
49
49
  // Track rendered ComponentSets: symbol -> ComponentSet GUID
50
50
  const renderedComponentSets = new Map<symbol, { sessionID: number; localID: number }>()
51
51
  // Track variant component IDs within each ComponentSet
52
- const renderedComponentSetVariants = new Map<symbol, Map<string, { sessionID: number; localID: number }>>()
52
+ const renderedComponentSetVariants = new Map<
53
+ symbol,
54
+ Map<string, { sessionID: number; localID: number }>
55
+ >()
53
56
 
54
57
  // Pending ComponentSet instances to create via Plugin API
55
58
  export interface PendingComponentSetInstance {
@@ -95,8 +98,6 @@ interface Container {
95
98
  children: Instance[]
96
99
  }
97
100
 
98
-
99
-
100
101
  function styleToNodeChange(
101
102
  type: string,
102
103
  props: Record<string, unknown>,
@@ -108,7 +109,7 @@ function styleToNodeChange(
108
109
  ): NodeChange {
109
110
  const style = (props.style || {}) as Record<string, unknown>
110
111
  const name = (props.name as string) || type
111
-
112
+
112
113
  const nodeChange: NodeChange = {
113
114
  guid: { sessionID, localID },
114
115
  phase: 'CREATED',
@@ -116,9 +117,9 @@ function styleToNodeChange(
116
117
  type: mapType(type),
117
118
  name,
118
119
  visible: true,
119
- opacity: typeof style.opacity === 'number' ? style.opacity : 1,
120
+ opacity: typeof style.opacity === 'number' ? style.opacity : 1
120
121
  }
121
-
122
+
122
123
  // Size
123
124
  const width = style.width ?? props.width
124
125
  const height = style.height ?? props.height
@@ -132,15 +133,19 @@ function styleToNodeChange(
132
133
  // Minimal size for auto-layout to expand from
133
134
  nodeChange.size = { x: 1, y: 1 }
134
135
  }
135
-
136
+
136
137
  // Position (transform)
137
138
  const x = Number(style.x ?? props.x ?? 0)
138
139
  const y = Number(style.y ?? props.y ?? 0)
139
140
  nodeChange.transform = {
140
- m00: 1, m01: 0, m02: x,
141
- m10: 0, m11: 1, m12: y,
141
+ m00: 1,
142
+ m01: 0,
143
+ m02: x,
144
+ m10: 0,
145
+ m11: 1,
146
+ m12: y
142
147
  }
143
-
148
+
144
149
  // Background color → fill (supports Figma variables)
145
150
  if (style.backgroundColor) {
146
151
  const bgColor = style.backgroundColor
@@ -148,53 +153,63 @@ function styleToNodeChange(
148
153
  const resolved = resolveVariable(bgColor)
149
154
  // Use explicit value as fallback, or black
150
155
  const fallback = bgColor.value ? parseColor(bgColor.value) : { r: 0, g: 0, b: 0, a: 1 }
151
- nodeChange.fillPaints = [{
152
- type: 'SOLID',
153
- color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
154
- opacity: 1,
155
- visible: true,
156
- colorVariableBinding: {
157
- variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
158
- }
159
- } as Paint]
156
+ nodeChange.fillPaints = [
157
+ {
158
+ type: 'SOLID',
159
+ color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
160
+ opacity: 1,
161
+ visible: true,
162
+ colorVariableBinding: {
163
+ variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
164
+ }
165
+ } as Paint
166
+ ]
160
167
  } else {
161
168
  const color = parseColor(bgColor as string)
162
- nodeChange.fillPaints = [{
163
- type: 'SOLID',
164
- color: { r: color.r, g: color.g, b: color.b, a: color.a },
165
- opacity: color.a,
166
- visible: true,
167
- }]
169
+ nodeChange.fillPaints = [
170
+ {
171
+ type: 'SOLID',
172
+ color: { r: color.r, g: color.g, b: color.b, a: color.a },
173
+ opacity: color.a,
174
+ visible: true
175
+ }
176
+ ]
168
177
  }
169
178
  }
170
-
179
+
171
180
  // Border → stroke (supports Figma variables)
172
181
  if (style.borderColor) {
173
182
  const borderColor = style.borderColor
174
183
  if (isVariable(borderColor)) {
175
184
  const resolved = resolveVariable(borderColor)
176
- const fallback = borderColor.value ? parseColor(borderColor.value) : { r: 0, g: 0, b: 0, a: 1 }
177
- nodeChange.strokePaints = [{
178
- type: 'SOLID',
179
- color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
180
- opacity: 1,
181
- visible: true,
182
- colorVariableBinding: {
183
- variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
184
- }
185
- } as Paint]
185
+ const fallback = borderColor.value
186
+ ? parseColor(borderColor.value)
187
+ : { r: 0, g: 0, b: 0, a: 1 }
188
+ nodeChange.strokePaints = [
189
+ {
190
+ type: 'SOLID',
191
+ color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
192
+ opacity: 1,
193
+ visible: true,
194
+ colorVariableBinding: {
195
+ variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
196
+ }
197
+ } as Paint
198
+ ]
186
199
  } else {
187
200
  const color = parseColor(borderColor as string)
188
- nodeChange.strokePaints = [{
189
- type: 'SOLID',
190
- color: { r: color.r, g: color.g, b: color.b, a: color.a },
191
- opacity: color.a,
192
- visible: true,
193
- }]
201
+ nodeChange.strokePaints = [
202
+ {
203
+ type: 'SOLID',
204
+ color: { r: color.r, g: color.g, b: color.b, a: color.a },
205
+ opacity: color.a,
206
+ visible: true
207
+ }
208
+ ]
194
209
  }
195
210
  nodeChange.strokeWeight = Number(style.borderWidth ?? 1)
196
211
  }
197
-
212
+
198
213
  // Corner radius
199
214
  if (style.borderRadius !== undefined) {
200
215
  nodeChange.cornerRadius = Number(style.borderRadius)
@@ -215,7 +230,7 @@ function styleToNodeChange(
215
230
  nodeChange.rectangleBottomRightCornerRadius = Number(style.borderBottomRightRadius)
216
231
  nodeChange.rectangleCornerRadiiIndependent = true
217
232
  }
218
-
233
+
219
234
  // Auto-layout
220
235
  if (style.flexDirection) {
221
236
  nodeChange.stackMode = style.flexDirection === 'row' ? 'HORIZONTAL' : 'VERTICAL'
@@ -232,24 +247,29 @@ function styleToNodeChange(
232
247
  if (style.gap !== undefined) {
233
248
  nodeChange.stackSpacing = Number(style.gap)
234
249
  }
235
-
250
+
236
251
  // Padding
237
252
  const pt = style.paddingTop ?? style.padding
238
253
  const pr = style.paddingRight ?? style.padding
239
254
  const pb = style.paddingBottom ?? style.padding
240
255
  const pl = style.paddingLeft ?? style.padding
241
-
242
- if (pt !== undefined) (nodeChange as unknown as Record<string, unknown>).stackVerticalPadding = Number(pt)
243
- if (pl !== undefined) (nodeChange as unknown as Record<string, unknown>).stackHorizontalPadding = Number(pl)
256
+
257
+ if (pt !== undefined)
258
+ (nodeChange as unknown as Record<string, unknown>).stackVerticalPadding = Number(pt)
259
+ if (pl !== undefined)
260
+ (nodeChange as unknown as Record<string, unknown>).stackHorizontalPadding = Number(pl)
244
261
  if (pr !== undefined) nodeChange.stackPaddingRight = Number(pr)
245
262
  if (pb !== undefined) nodeChange.stackPaddingBottom = Number(pb)
246
-
263
+
247
264
  // Alignment - NOTE: field names differ from Plugin API!
248
265
  // Plugin API uses primaryAxisAlignItems/counterAxisAlignItems
249
266
  // Multiplayer uses stackPrimaryAlignItems/stackCounterAlignItems
250
267
  if (style.justifyContent) {
251
268
  const validValues: Record<string, string> = {
252
- 'flex-start': 'MIN', 'center': 'CENTER', 'flex-end': 'MAX', 'space-between': 'SPACE_BETWEEN'
269
+ 'flex-start': 'MIN',
270
+ center: 'CENTER',
271
+ 'flex-end': 'MAX',
272
+ 'space-between': 'SPACE_BETWEEN'
253
273
  }
254
274
  const mapped = validValues[style.justifyContent as string]
255
275
  if (mapped) {
@@ -261,7 +281,10 @@ function styleToNodeChange(
261
281
  }
262
282
  if (style.alignItems) {
263
283
  const validValues: Record<string, string> = {
264
- 'flex-start': 'MIN', 'center': 'CENTER', 'flex-end': 'MAX', 'stretch': 'STRETCH'
284
+ 'flex-start': 'MIN',
285
+ center: 'CENTER',
286
+ 'flex-end': 'MAX',
287
+ stretch: 'STRETCH'
265
288
  }
266
289
  const mapped = validValues[style.alignItems as string]
267
290
  if (mapped) {
@@ -271,15 +294,15 @@ function styleToNodeChange(
271
294
  nodeChange.stackCounterAlignItems = 'MIN'
272
295
  }
273
296
  }
274
-
297
+
275
298
  // Text-specific
276
299
  if (type.toLowerCase() === 'text' && textContent) {
277
300
  // Text content via textData.characters
278
301
  const nc = nodeChange as unknown as Record<string, unknown>
279
302
  nc.textData = { characters: textContent }
280
303
  nc.textAutoResize = 'WIDTH_AND_HEIGHT'
281
- nc.textAlignVertical = 'TOP' // Required for text height calculation
282
-
304
+ nc.textAlignVertical = 'TOP' // Required for text height calculation
305
+
283
306
  if (style.fontSize) nc.fontSize = Number(style.fontSize)
284
307
  // CRITICAL: lineHeight MUST be { value: 100, units: 'PERCENT' } for text to have height
285
308
  // Without this, TEXT nodes render with height=0 and are invisible
@@ -291,10 +314,10 @@ function styleToNodeChange(
291
314
  nc.fontName = {
292
315
  family,
293
316
  style: fontStyle,
294
- postscript: `${family}-${fontStyle}`.replace(/\s+/g, ''),
317
+ postscript: `${family}-${fontStyle}`.replace(/\s+/g, '')
295
318
  }
296
319
  if (style.textAlign) {
297
- const map: Record<string, string> = { 'left': 'LEFT', 'center': 'CENTER', 'right': 'RIGHT' }
320
+ const map: Record<string, string> = { left: 'LEFT', center: 'CENTER', right: 'RIGHT' }
298
321
  nc.textAlignHorizontal = map[style.textAlign as string] || 'LEFT'
299
322
  }
300
323
  if (style.color) {
@@ -302,27 +325,31 @@ function styleToNodeChange(
302
325
  if (isVariable(textColor)) {
303
326
  const resolved = resolveVariable(textColor)
304
327
  const fallback = textColor.value ? parseColor(textColor.value) : { r: 0, g: 0, b: 0, a: 1 }
305
- nodeChange.fillPaints = [{
306
- type: 'SOLID',
307
- color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
308
- opacity: 1,
309
- visible: true,
310
- colorVariableBinding: {
311
- variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
312
- }
313
- } as Paint]
328
+ nodeChange.fillPaints = [
329
+ {
330
+ type: 'SOLID',
331
+ color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
332
+ opacity: 1,
333
+ visible: true,
334
+ colorVariableBinding: {
335
+ variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
336
+ }
337
+ } as Paint
338
+ ]
314
339
  } else {
315
340
  const color = parseColor(textColor as string)
316
- nodeChange.fillPaints = [{
317
- type: 'SOLID',
318
- color: { r: color.r, g: color.g, b: color.b, a: color.a },
319
- opacity: color.a,
320
- visible: true,
321
- }]
341
+ nodeChange.fillPaints = [
342
+ {
343
+ type: 'SOLID',
344
+ color: { r: color.r, g: color.g, b: color.b, a: color.a },
345
+ opacity: color.a,
346
+ visible: true
347
+ }
348
+ ]
322
349
  }
323
350
  }
324
351
  }
325
-
352
+
326
353
  // Instance - link to component
327
354
  if (type.toLowerCase() === 'instance' && props.componentId) {
328
355
  const match = String(props.componentId).match(/(\d+):(\d+)/)
@@ -331,12 +358,12 @@ function styleToNodeChange(
331
358
  nc.symbolData = {
332
359
  symbolID: {
333
360
  sessionID: parseInt(match[1], 10),
334
- localID: parseInt(match[2], 10),
361
+ localID: parseInt(match[2], 10)
335
362
  }
336
363
  }
337
364
  }
338
365
  }
339
-
366
+
340
367
  return nodeChange
341
368
  }
342
369
 
@@ -350,10 +377,10 @@ function mapType(type: string): string {
350
377
  star: 'STAR',
351
378
  polygon: 'REGULAR_POLYGON',
352
379
  vector: 'VECTOR',
353
- component: 'SYMBOL', // Figma internally calls components "symbols"
380
+ component: 'SYMBOL', // Figma internally calls components "symbols"
354
381
  instance: 'INSTANCE',
355
382
  group: 'GROUP',
356
- page: 'CANVAS',
383
+ page: 'CANVAS'
357
384
  }
358
385
  return map[type.toLowerCase()] || 'FRAME'
359
386
  }
@@ -361,8 +388,8 @@ function mapType(type: string): string {
361
388
  function mapFontWeight(weight?: string): string {
362
389
  if (!weight) return 'Regular'
363
390
  const map: Record<string, string> = {
364
- 'normal': 'Regular',
365
- 'bold': 'Bold',
391
+ normal: 'Regular',
392
+ bold: 'Bold',
366
393
  '100': 'Thin',
367
394
  '200': 'Extra Light',
368
395
  '300': 'Light',
@@ -371,7 +398,7 @@ function mapFontWeight(weight?: string): string {
371
398
  '600': 'Semi Bold',
372
399
  '700': 'Bold',
373
400
  '800': 'Extra Bold',
374
- '900': 'Black',
401
+ '900': 'Black'
375
402
  }
376
403
  return map[weight] || 'Regular'
377
404
  }
@@ -390,51 +417,53 @@ function collectNodeChanges(
390
417
  const name = instance.props.__componentName as string
391
418
  const registry = getComponentRegistry()
392
419
  const def = registry.get(sym)
393
-
420
+
394
421
  if (!def) {
395
422
  consola.error(`Component "${name}" not found in registry`)
396
423
  return
397
424
  }
398
-
425
+
399
426
  // Check if component already rendered
400
427
  let componentGUID = renderedComponents.get(sym)
401
-
428
+
402
429
  if (!componentGUID) {
403
430
  // First instance: render as Component
404
431
  const componentLocalID = container.localIDCounter++
405
432
  componentGUID = { sessionID, localID: componentLocalID }
406
433
  renderedComponents.set(sym, componentGUID)
407
-
434
+
408
435
  // Render the component's element tree
409
436
  const componentResult = renderToNodeChanges(def.element, {
410
437
  sessionID,
411
438
  parentGUID, // Will be fixed below
412
- startLocalID: container.localIDCounter,
439
+ startLocalID: container.localIDCounter
413
440
  })
414
-
441
+
415
442
  // Update counter
416
443
  container.localIDCounter = componentResult.nextLocalID
417
-
444
+
418
445
  // Change first node to be SYMBOL type and add to results
419
446
  if (componentResult.nodeChanges.length > 0) {
420
447
  const rootChange = componentResult.nodeChanges[0]
421
448
  const originalRootGUID = { ...rootChange.guid }
422
-
449
+
423
450
  // Replace root node's guid with componentGUID
424
451
  rootChange.guid = componentGUID
425
452
  rootChange.type = 'SYMBOL'
426
453
  rootChange.name = name
427
454
  rootChange.parentIndex = { guid: parentGUID, position }
428
-
455
+
429
456
  // Fix children's parentIndex to point to componentGUID instead of original root
430
457
  for (let i = 1; i < componentResult.nodeChanges.length; i++) {
431
458
  const child = componentResult.nodeChanges[i]
432
- if (child.parentIndex?.guid.localID === originalRootGUID.localID &&
433
- child.parentIndex?.guid.sessionID === originalRootGUID.sessionID) {
459
+ if (
460
+ child.parentIndex?.guid.localID === originalRootGUID.localID &&
461
+ child.parentIndex?.guid.sessionID === originalRootGUID.sessionID
462
+ ) {
434
463
  child.parentIndex.guid = componentGUID
435
464
  }
436
465
  }
437
-
466
+
438
467
  // Merge style props from instance onto component
439
468
  const style = (instance.props.style || {}) as Record<string, unknown>
440
469
  if (style.x !== undefined || style.y !== undefined) {
@@ -442,7 +471,7 @@ function collectNodeChanges(
442
471
  const y = Number(style.y ?? 0)
443
472
  rootChange.transform = { m00: 1, m01: 0, m02: x, m10: 0, m11: 1, m12: y }
444
473
  }
445
-
474
+
446
475
  result.push(...componentResult.nodeChanges)
447
476
  }
448
477
  } else {
@@ -451,7 +480,7 @@ function collectNodeChanges(
451
480
  const style = (instance.props.style || {}) as Record<string, unknown>
452
481
  const x = Number(style.x ?? 0)
453
482
  const y = Number(style.y ?? 0)
454
-
483
+
455
484
  const instanceChange: NodeChange = {
456
485
  guid: { sessionID, localID: instanceLocalID },
457
486
  phase: 'CREATED',
@@ -460,18 +489,18 @@ function collectNodeChanges(
460
489
  name,
461
490
  visible: true,
462
491
  opacity: 1,
463
- transform: { m00: 1, m01: 0, m02: x, m10: 0, m11: 1, m12: y },
492
+ transform: { m00: 1, m01: 0, m02: x, m10: 0, m11: 1, m12: y }
464
493
  }
465
-
494
+
466
495
  // Link to component
467
496
  const nc = instanceChange as unknown as Record<string, unknown>
468
497
  nc.symbolData = { symbolID: componentGUID }
469
-
498
+
470
499
  result.push(instanceChange)
471
500
  }
472
501
  return
473
502
  }
474
-
503
+
475
504
  // Handle defineComponentSet instances
476
505
  if (instance.type === '__component_set_instance__') {
477
506
  const sym = instance.props.__componentSetSymbol as symbol
@@ -479,25 +508,25 @@ function collectNodeChanges(
479
508
  const variantProps = (instance.props.__variantProps || {}) as Record<string, string>
480
509
  const csRegistry = getComponentSetRegistry()
481
510
  const csDef = csRegistry.get(sym)
482
-
511
+
483
512
  if (!csDef) {
484
513
  consola.error(`ComponentSet "${name}" not found in registry`)
485
514
  return
486
515
  }
487
-
516
+
488
517
  // Check if ComponentSet already rendered
489
518
  let componentSetGUID = renderedComponentSets.get(sym)
490
-
519
+
491
520
  if (!componentSetGUID) {
492
521
  // First instance: create ComponentSet with all variant components
493
522
  const componentSetLocalID = container.localIDCounter++
494
523
  componentSetGUID = { sessionID, localID: componentSetLocalID }
495
524
  renderedComponentSets.set(sym, componentSetGUID)
496
-
525
+
497
526
  const variants = csDef.variants
498
527
  const combinations = generateVariantCombinations(variants)
499
528
  const variantComponentIds = new Map<string, { sessionID: number; localID: number }>()
500
-
529
+
501
530
  // Create ComponentSet node (FRAME with isStateGroup)
502
531
  const setChange: NodeChange = {
503
532
  guid: componentSetGUID,
@@ -507,7 +536,7 @@ function collectNodeChanges(
507
536
  name,
508
537
  visible: true,
509
538
  opacity: 1,
510
- size: { x: 1, y: 1 }, // Will be auto-sized
539
+ size: { x: 1, y: 1 } // Will be auto-sized
511
540
  }
512
541
  const setNc = setChange as unknown as Record<string, unknown>
513
542
  setNc.isStateGroup = true
@@ -516,29 +545,29 @@ function collectNodeChanges(
516
545
  setNc.stackSpacing = 20
517
546
  setNc.stackPrimarySizing = 'RESIZE_TO_FIT'
518
547
  setNc.stackCounterSizing = 'RESIZE_TO_FIT'
519
-
548
+
520
549
  result.push(setChange)
521
-
550
+
522
551
  // Create Component for each variant combination
523
552
  combinations.forEach((combo, i) => {
524
553
  const variantName = buildVariantName(combo)
525
554
  const variantLocalID = container.localIDCounter++
526
555
  const variantGUID = { sessionID, localID: variantLocalID }
527
556
  variantComponentIds.set(variantName, variantGUID)
528
-
557
+
529
558
  // Render the variant's element
530
559
  const variantElement = csDef.render(combo)
531
560
  const variantResult = renderToNodeChanges(variantElement, {
532
561
  sessionID,
533
562
  parentGUID: componentSetGUID!,
534
- startLocalID: container.localIDCounter,
563
+ startLocalID: container.localIDCounter
535
564
  })
536
565
  container.localIDCounter = variantResult.nextLocalID
537
-
566
+
538
567
  if (variantResult.nodeChanges.length > 0) {
539
568
  const rootChange = variantResult.nodeChanges[0]
540
569
  const originalRootGUID = { ...rootChange.guid }
541
-
570
+
542
571
  rootChange.guid = variantGUID
543
572
  rootChange.type = 'SYMBOL'
544
573
  rootChange.name = variantName
@@ -546,23 +575,25 @@ function collectNodeChanges(
546
575
  guid: componentSetGUID!,
547
576
  position: String.fromCharCode(33 + i)
548
577
  }
549
-
578
+
550
579
  // Fix children's parentIndex
551
580
  for (let j = 1; j < variantResult.nodeChanges.length; j++) {
552
581
  const child = variantResult.nodeChanges[j]
553
- if (child.parentIndex?.guid.localID === originalRootGUID.localID &&
554
- child.parentIndex?.guid.sessionID === originalRootGUID.sessionID) {
582
+ if (
583
+ child.parentIndex?.guid.localID === originalRootGUID.localID &&
584
+ child.parentIndex?.guid.sessionID === originalRootGUID.sessionID
585
+ ) {
555
586
  child.parentIndex.guid = variantGUID
556
587
  }
557
588
  }
558
-
589
+
559
590
  result.push(...variantResult.nodeChanges)
560
591
  }
561
592
  })
562
-
593
+
563
594
  // Store variant IDs for creating instances
564
595
  renderedComponentSetVariants.set(sym, variantComponentIds)
565
-
596
+
566
597
  // Store pending instances to create via Plugin API (multiplayer symbolData doesn't work for ComponentSet)
567
598
  const requestedVariantName = buildVariantName({
568
599
  ...getDefaultVariants(variants),
@@ -575,7 +606,7 @@ function collectNodeChanges(
575
606
  parentGUID,
576
607
  position: String.fromCharCode(34 + combinations.length),
577
608
  x: Number(style.x ?? 0),
578
- y: Number(style.y ?? 0),
609
+ y: Number(style.y ?? 0)
579
610
  })
580
611
  } else {
581
612
  // Subsequent instance: store for Plugin API creation
@@ -590,12 +621,12 @@ function collectNodeChanges(
590
621
  parentGUID,
591
622
  position,
592
623
  x: Number(style.x ?? 0),
593
- y: Number(style.y ?? 0),
624
+ y: Number(style.y ?? 0)
594
625
  })
595
626
  }
596
627
  return
597
628
  }
598
-
629
+
599
630
  const nodeChange = styleToNodeChange(
600
631
  instance.type,
601
632
  instance.props,
@@ -606,7 +637,7 @@ function collectNodeChanges(
606
637
  instance.textContent
607
638
  )
608
639
  result.push(nodeChange)
609
-
640
+
610
641
  const thisGUID = { sessionID, localID: instance.localID }
611
642
  instance.children.forEach((child, i) => {
612
643
  const childPosition = String.fromCharCode(33 + (i % 90))
@@ -620,50 +651,48 @@ const hostConfig: any = {
620
651
  supportsPersistence: false,
621
652
  supportsHydration: false,
622
653
  isPrimaryRenderer: true,
623
-
654
+
624
655
  now: Date.now,
625
656
  scheduleTimeout: setTimeout,
626
657
  cancelTimeout: clearTimeout,
627
658
  noTimeout: -1 as const,
628
-
659
+
629
660
  getRootHostContext() {
630
661
  return {}
631
662
  },
632
-
663
+
633
664
  getChildHostContext() {
634
665
  return {}
635
666
  },
636
-
667
+
637
668
  shouldSetTextContent() {
638
669
  return false
639
670
  },
640
-
671
+
641
672
  createInstance(
642
673
  type: string,
643
674
  props: Record<string, unknown>,
644
- _rootContainer: Container,
675
+ _rootContainer: Container
645
676
  ): Instance {
646
677
  const { children: _, ...rest } = props
647
678
  return {
648
679
  type,
649
680
  props: rest,
650
681
  localID: _rootContainer.localIDCounter++,
651
- children: [],
682
+ children: []
652
683
  }
653
684
  },
654
-
655
- createTextInstance(
656
- text: string,
657
- ): Instance {
685
+
686
+ createTextInstance(text: string): Instance {
658
687
  return {
659
688
  type: '__text__',
660
689
  props: {},
661
690
  localID: -1,
662
691
  children: [],
663
- textContent: text,
692
+ textContent: text
664
693
  }
665
694
  },
666
-
695
+
667
696
  appendInitialChild(parent: Instance, child: Instance): void {
668
697
  if (child.type === '__text__') {
669
698
  parent.textContent = (parent.textContent || '') + (child.textContent || '')
@@ -671,7 +700,7 @@ const hostConfig: any = {
671
700
  parent.children.push(child)
672
701
  }
673
702
  },
674
-
703
+
675
704
  appendChild(parent: Instance, child: Instance): void {
676
705
  if (child.type === '__text__') {
677
706
  parent.textContent = (parent.textContent || '') + (child.textContent || '')
@@ -679,23 +708,23 @@ const hostConfig: any = {
679
708
  parent.children.push(child)
680
709
  }
681
710
  },
682
-
711
+
683
712
  appendChildToContainer(container: Container, child: Instance): void {
684
713
  if (child.type !== '__text__') {
685
714
  container.children.push(child)
686
715
  }
687
716
  },
688
-
717
+
689
718
  removeChild(parent: Instance, child: Instance): void {
690
719
  const index = parent.children.indexOf(child)
691
720
  if (index !== -1) parent.children.splice(index, 1)
692
721
  },
693
-
722
+
694
723
  removeChildFromContainer(container: Container, child: Instance): void {
695
724
  const index = container.children.indexOf(child)
696
725
  if (index !== -1) container.children.splice(index, 1)
697
726
  },
698
-
727
+
699
728
  insertBefore(parent: Instance, child: Instance, beforeChild: Instance): void {
700
729
  if (child.type === '__text__') return
701
730
  const index = parent.children.indexOf(beforeChild)
@@ -705,7 +734,7 @@ const hostConfig: any = {
705
734
  parent.children.push(child)
706
735
  }
707
736
  },
708
-
737
+
709
738
  insertInContainerBefore(container: Container, child: Instance, beforeChild: Instance): void {
710
739
  if (child.type === '__text__') return
711
740
  const index = container.children.indexOf(beforeChild)
@@ -715,25 +744,25 @@ const hostConfig: any = {
715
744
  container.children.push(child)
716
745
  }
717
746
  },
718
-
747
+
719
748
  prepareForCommit(): Record<string, unknown> | null {
720
749
  return null
721
750
  },
722
-
751
+
723
752
  resetAfterCommit(): void {},
724
-
753
+
725
754
  clearContainer(container: Container): void {
726
755
  container.children = []
727
756
  },
728
-
757
+
729
758
  finalizeInitialChildren() {
730
759
  return false
731
760
  },
732
-
761
+
733
762
  prepareUpdate() {
734
763
  return true
735
764
  },
736
-
765
+
737
766
  commitUpdate(
738
767
  instance: Instance,
739
768
  _updatePayload: unknown,
@@ -744,40 +773,36 @@ const hostConfig: any = {
744
773
  const { children: _, ...rest } = nextProps
745
774
  instance.props = rest
746
775
  },
747
-
748
- commitTextUpdate(
749
- textInstance: Instance,
750
- _oldText: string,
751
- newText: string
752
- ): void {
776
+
777
+ commitTextUpdate(textInstance: Instance, _oldText: string, newText: string): void {
753
778
  textInstance.textContent = newText
754
779
  },
755
-
780
+
756
781
  getPublicInstance(instance: Instance): Instance {
757
782
  return instance
758
783
  },
759
-
784
+
760
785
  preparePortalMount() {},
761
-
786
+
762
787
  getCurrentEventPriority() {
763
788
  return 16 // DefaultEventPriority
764
789
  },
765
-
790
+
766
791
  getInstanceFromNode() {
767
792
  return null
768
793
  },
769
-
794
+
770
795
  beforeActiveInstanceBlur() {},
771
-
796
+
772
797
  afterActiveInstanceBlur() {},
773
-
798
+
774
799
  prepareScopeUpdate() {},
775
-
800
+
776
801
  getInstanceFromScope() {
777
802
  return null
778
803
  },
779
-
780
- detachDeletedInstance() {},
804
+
805
+ detachDeletedInstance() {}
781
806
  }
782
807
 
783
808
  /**
@@ -790,37 +815,44 @@ export function renderToNodeChanges(
790
815
  const container: Container = {
791
816
  options,
792
817
  localIDCounter: options.startLocalID ?? 1,
793
- children: [],
818
+ children: []
794
819
  }
795
-
820
+
796
821
  const reconciler = Reconciler(hostConfig)
797
-
822
+
798
823
  const root = reconciler.createContainer(
799
824
  container,
800
- 0, // tag: LegacyRoot
801
- null, // hydrationCallbacks
802
- false, // isStrictMode
803
- null, // concurrentUpdatesByDefaultOverride
804
- '', // identifierPrefix
805
- () => {}, // onUncaughtError
806
- () => {}, // onCaughtError
807
- () => {}, // onRecoverableError
808
- () => {}, // onDefaultTransitionIndicator
809
- null // transitionCallbacks
825
+ 0, // tag: LegacyRoot
826
+ null, // hydrationCallbacks
827
+ false, // isStrictMode
828
+ null, // concurrentUpdatesByDefaultOverride
829
+ '', // identifierPrefix
830
+ () => {}, // onUncaughtError
831
+ () => {}, // onCaughtError
832
+ () => {}, // onRecoverableError
833
+ () => {}, // onDefaultTransitionIndicator
834
+ null // transitionCallbacks
810
835
  )
811
-
836
+
812
837
  reconciler.updateContainer(element, root, null, () => {})
813
838
  reconciler.flushSync(() => {})
814
-
839
+
815
840
  const nodeChanges: NodeChange[] = []
816
841
  container.children.forEach((child, i) => {
817
842
  const position = String.fromCharCode(33 + (i % 90))
818
- collectNodeChanges(child, options.sessionID, options.parentGUID, position, nodeChanges, container)
843
+ collectNodeChanges(
844
+ child,
845
+ options.sessionID,
846
+ options.parentGUID,
847
+ position,
848
+ nodeChanges,
849
+ container
850
+ )
819
851
  })
820
-
852
+
821
853
  return {
822
854
  nodeChanges,
823
- nextLocalID: container.localIDCounter,
855
+ nextLocalID: container.localIDCounter
824
856
  }
825
857
  }
826
858