@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.
- package/CHANGELOG.md +34 -1
- package/README.md +34 -3
- package/SKILL.md +72 -0
- package/bin/figma-use.js +1 -4
- package/dist/cli/index.js +22348 -20440
- package/dist/proxy/index.js +25 -25
- package/package.json +7 -3
- package/packages/cli/src/render/component-set.tsx +30 -21
- package/packages/cli/src/render/components.tsx +40 -16
- package/packages/cli/src/render/index.ts +14 -14
- package/packages/cli/src/render/reconciler.ts +222 -190
- package/packages/cli/src/render/vars.ts +23 -22
- package/packages/plugin/dist/main.js +168 -55
- package/packages/plugin/dist/ui.html +23 -18
- package/packages/plugin/dist/ui.js +44 -43
|
@@ -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<
|
|
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,
|
|
141
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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)
|
|
243
|
-
|
|
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',
|
|
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',
|
|
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'
|
|
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> = {
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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',
|
|
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
|
-
|
|
365
|
-
|
|
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 (
|
|
433
|
-
|
|
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 }
|
|
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 (
|
|
554
|
-
|
|
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,
|
|
801
|
-
null,
|
|
802
|
-
false,
|
|
803
|
-
null,
|
|
804
|
-
'',
|
|
805
|
-
() => {},
|
|
806
|
-
() => {},
|
|
807
|
-
() => {},
|
|
808
|
-
() => {},
|
|
809
|
-
null
|
|
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(
|
|
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
|
|