@llc3233149/core 0.9.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.
Files changed (457) hide show
  1. package/dist/canvaskit.d.ts +9 -0
  2. package/dist/canvaskit.d.ts.map +1 -0
  3. package/dist/canvaskit.js +55 -0
  4. package/dist/canvaskit.js.map +1 -0
  5. package/dist/clipboard.d.ts +32 -0
  6. package/dist/clipboard.d.ts.map +1 -0
  7. package/dist/clipboard.js +345 -0
  8. package/dist/clipboard.js.map +1 -0
  9. package/dist/color.d.ts +28 -0
  10. package/dist/color.d.ts.map +1 -0
  11. package/dist/color.js +67 -0
  12. package/dist/color.js.map +1 -0
  13. package/dist/constants.d.ts +159 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +224 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/copy.d.ts +20 -0
  18. package/dist/copy.d.ts.map +1 -0
  19. package/dist/copy.js +64 -0
  20. package/dist/copy.js.map +1 -0
  21. package/dist/fig-export-worker.d.ts +2 -0
  22. package/dist/fig-export-worker.d.ts.map +1 -0
  23. package/dist/fig-export-worker.js +17 -0
  24. package/dist/fig-export-worker.js.map +1 -0
  25. package/dist/fig-export.d.ts +13 -0
  26. package/dist/fig-export.d.ts.map +1 -0
  27. package/dist/fig-export.js +243 -0
  28. package/dist/fig-export.js.map +1 -0
  29. package/dist/figma-api.d.ts +268 -0
  30. package/dist/figma-api.d.ts.map +1 -0
  31. package/dist/figma-api.js +1074 -0
  32. package/dist/figma-api.js.map +1 -0
  33. package/dist/figma-rest-import.d.ts +122 -0
  34. package/dist/figma-rest-import.d.ts.map +1 -0
  35. package/dist/figma-rest-import.js +319 -0
  36. package/dist/figma-rest-import.js.map +1 -0
  37. package/dist/fonts.d.ts +24 -0
  38. package/dist/fonts.d.ts.map +1 -0
  39. package/dist/fonts.js +356 -0
  40. package/dist/fonts.js.map +1 -0
  41. package/dist/geometry.d.ts +14 -0
  42. package/dist/geometry.d.ts.map +1 -0
  43. package/dist/geometry.js +47 -0
  44. package/dist/geometry.js.map +1 -0
  45. package/dist/index.d.ts +38 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +32 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/kiwi/codec.d.ts +292 -0
  50. package/dist/kiwi/codec.d.ts.map +1 -0
  51. package/dist/kiwi/codec.js +340 -0
  52. package/dist/kiwi/codec.js.map +1 -0
  53. package/dist/kiwi/fig-file.d.ts +4 -0
  54. package/dist/kiwi/fig-file.d.ts.map +1 -0
  55. package/dist/kiwi/fig-file.js +236 -0
  56. package/dist/kiwi/fig-file.js.map +1 -0
  57. package/dist/kiwi/fig-import.d.ts +4 -0
  58. package/dist/kiwi/fig-import.d.ts.map +1 -0
  59. package/dist/kiwi/fig-import.js +248 -0
  60. package/dist/kiwi/fig-import.js.map +1 -0
  61. package/dist/kiwi/fig-parse-worker.d.ts +7 -0
  62. package/dist/kiwi/fig-parse-worker.d.ts.map +1 -0
  63. package/dist/kiwi/fig-parse-worker.js +184 -0
  64. package/dist/kiwi/fig-parse-worker.js.map +1 -0
  65. package/dist/kiwi/instance-overrides.d.ts +66 -0
  66. package/dist/kiwi/instance-overrides.d.ts.map +1 -0
  67. package/dist/kiwi/instance-overrides.js +644 -0
  68. package/dist/kiwi/instance-overrides.js.map +1 -0
  69. package/dist/kiwi/kiwi-convert.d.ts +19 -0
  70. package/dist/kiwi/kiwi-convert.d.ts.map +1 -0
  71. package/dist/kiwi/kiwi-convert.js +678 -0
  72. package/dist/kiwi/kiwi-convert.js.map +1 -0
  73. package/dist/kiwi/kiwi-schema/bb.d.ts +25 -0
  74. package/dist/kiwi/kiwi-schema/bb.d.ts.map +1 -0
  75. package/dist/kiwi/kiwi-schema/bb.js +225 -0
  76. package/dist/kiwi/kiwi-schema/bb.js.map +1 -0
  77. package/dist/kiwi/kiwi-schema/binary.d.ts +5 -0
  78. package/dist/kiwi/kiwi-schema/binary.d.ts.map +1 -0
  79. package/dist/kiwi/kiwi-schema/binary.js +86 -0
  80. package/dist/kiwi/kiwi-schema/binary.js.map +1 -0
  81. package/dist/kiwi/kiwi-schema/index.d.ts +6 -0
  82. package/dist/kiwi/kiwi-schema/index.d.ts.map +1 -0
  83. package/dist/kiwi/kiwi-schema/index.js +5 -0
  84. package/dist/kiwi/kiwi-schema/index.js.map +1 -0
  85. package/dist/kiwi/kiwi-schema/js.d.ts +4 -0
  86. package/dist/kiwi/kiwi-schema/js.d.ts.map +1 -0
  87. package/dist/kiwi/kiwi-schema/js.js +282 -0
  88. package/dist/kiwi/kiwi-schema/js.js.map +1 -0
  89. package/dist/kiwi/kiwi-schema/parser.d.ts +5 -0
  90. package/dist/kiwi/kiwi-schema/parser.d.ts.map +1 -0
  91. package/dist/kiwi/kiwi-schema/parser.js +228 -0
  92. package/dist/kiwi/kiwi-schema/parser.js.map +1 -0
  93. package/dist/kiwi/kiwi-schema/schema.d.ts +22 -0
  94. package/dist/kiwi/kiwi-schema/schema.d.ts.map +1 -0
  95. package/dist/kiwi/kiwi-schema/schema.js +2 -0
  96. package/dist/kiwi/kiwi-schema/schema.js.map +1 -0
  97. package/dist/kiwi/kiwi-schema/util.d.ts +3 -0
  98. package/dist/kiwi/kiwi-schema/util.d.ts.map +1 -0
  99. package/dist/kiwi/kiwi-schema/util.js +10 -0
  100. package/dist/kiwi/kiwi-schema/util.js.map +1 -0
  101. package/dist/kiwi/protocol.d.ts +172 -0
  102. package/dist/kiwi/protocol.d.ts.map +1 -0
  103. package/dist/kiwi/protocol.js +214 -0
  104. package/dist/kiwi/protocol.js.map +1 -0
  105. package/dist/kiwi/schema.d.ts +3 -0
  106. package/dist/kiwi/schema.d.ts.map +1 -0
  107. package/dist/kiwi/schema.js +2285 -0
  108. package/dist/kiwi/schema.js.map +1 -0
  109. package/dist/kiwi-serialize.d.ts +17 -0
  110. package/dist/kiwi-serialize.d.ts.map +1 -0
  111. package/dist/kiwi-serialize.js +419 -0
  112. package/dist/kiwi-serialize.js.map +1 -0
  113. package/dist/layout.d.ts +9 -0
  114. package/dist/layout.d.ts.map +1 -0
  115. package/dist/layout.js +533 -0
  116. package/dist/layout.js.map +1 -0
  117. package/dist/profiler/draw-call-counter.d.ts +9 -0
  118. package/dist/profiler/draw-call-counter.d.ts.map +1 -0
  119. package/dist/profiler/draw-call-counter.js +37 -0
  120. package/dist/profiler/draw-call-counter.js.map +1 -0
  121. package/dist/profiler/frame-capture.d.ts +34 -0
  122. package/dist/profiler/frame-capture.d.ts.map +1 -0
  123. package/dist/profiler/frame-capture.js +90 -0
  124. package/dist/profiler/frame-capture.js.map +1 -0
  125. package/dist/profiler/frame-stats.d.ts +33 -0
  126. package/dist/profiler/frame-stats.d.ts.map +1 -0
  127. package/dist/profiler/frame-stats.js +103 -0
  128. package/dist/profiler/frame-stats.js.map +1 -0
  129. package/dist/profiler/gpu-timer.d.ts +15 -0
  130. package/dist/profiler/gpu-timer.d.ts.map +1 -0
  131. package/dist/profiler/gpu-timer.js +77 -0
  132. package/dist/profiler/gpu-timer.js.map +1 -0
  133. package/dist/profiler/hud-renderer.d.ts +22 -0
  134. package/dist/profiler/hud-renderer.d.ts.map +1 -0
  135. package/dist/profiler/hud-renderer.js +200 -0
  136. package/dist/profiler/hud-renderer.js.map +1 -0
  137. package/dist/profiler/index.d.ts +9 -0
  138. package/dist/profiler/index.d.ts.map +1 -0
  139. package/dist/profiler/index.js +8 -0
  140. package/dist/profiler/index.js.map +1 -0
  141. package/dist/profiler/phase-timer.d.ts +9 -0
  142. package/dist/profiler/phase-timer.d.ts.map +1 -0
  143. package/dist/profiler/phase-timer.js +51 -0
  144. package/dist/profiler/phase-timer.js.map +1 -0
  145. package/dist/profiler/render-profiler.d.ts +41 -0
  146. package/dist/profiler/render-profiler.d.ts.map +1 -0
  147. package/dist/profiler/render-profiler.js +142 -0
  148. package/dist/profiler/render-profiler.js.map +1 -0
  149. package/dist/render/components.d.ts +25 -0
  150. package/dist/render/components.d.ts.map +1 -0
  151. package/dist/render/components.js +55 -0
  152. package/dist/render/components.js.map +1 -0
  153. package/dist/render/emoji-icons.d.ts +5 -0
  154. package/dist/render/emoji-icons.d.ts.map +1 -0
  155. package/dist/render/emoji-icons.js +55 -0
  156. package/dist/render/emoji-icons.js.map +1 -0
  157. package/dist/render/export-code.d.ts +9 -0
  158. package/dist/render/export-code.d.ts.map +1 -0
  159. package/dist/render/export-code.js +335 -0
  160. package/dist/render/export-code.js.map +1 -0
  161. package/dist/render/export-jsx.d.ts +5 -0
  162. package/dist/render/export-jsx.d.ts.map +1 -0
  163. package/dist/render/export-jsx.js +621 -0
  164. package/dist/render/export-jsx.js.map +1 -0
  165. package/dist/render/index.d.ts +8 -0
  166. package/dist/render/index.d.ts.map +1 -0
  167. package/dist/render/index.js +8 -0
  168. package/dist/render/index.js.map +1 -0
  169. package/dist/render/jsx-runtime.d.ts +31 -0
  170. package/dist/render/jsx-runtime.d.ts.map +1 -0
  171. package/dist/render/jsx-runtime.js +13 -0
  172. package/dist/render/jsx-runtime.js.map +1 -0
  173. package/dist/render/mini-react.d.ts +14 -0
  174. package/dist/render/mini-react.d.ts.map +1 -0
  175. package/dist/render/mini-react.js +16 -0
  176. package/dist/render/mini-react.js.map +1 -0
  177. package/dist/render/render-jsx.d.ts +24 -0
  178. package/dist/render/render-jsx.d.ts.map +1 -0
  179. package/dist/render/render-jsx.js +51 -0
  180. package/dist/render/render-jsx.js.map +1 -0
  181. package/dist/render/renderer.d.ts +16 -0
  182. package/dist/render/renderer.d.ts.map +1 -0
  183. package/dist/render/renderer.js +437 -0
  184. package/dist/render/renderer.js.map +1 -0
  185. package/dist/render/tailwind.d.ts +20 -0
  186. package/dist/render/tailwind.d.ts.map +1 -0
  187. package/dist/render/tailwind.js +115 -0
  188. package/dist/render/tailwind.js.map +1 -0
  189. package/dist/render/tree.d.ts +72 -0
  190. package/dist/render/tree.d.ts.map +1 -0
  191. package/dist/render/tree.js +63 -0
  192. package/dist/render/tree.js.map +1 -0
  193. package/dist/render-image.d.ts +19 -0
  194. package/dist/render-image.d.ts.map +1 -0
  195. package/dist/render-image.js +84 -0
  196. package/dist/render-image.js.map +1 -0
  197. package/dist/renderer/effects.d.ts +9 -0
  198. package/dist/renderer/effects.d.ts.map +1 -0
  199. package/dist/renderer/effects.js +44 -0
  200. package/dist/renderer/effects.js.map +1 -0
  201. package/dist/renderer/fills.d.ts +9 -0
  202. package/dist/renderer/fills.d.ts.map +1 -0
  203. package/dist/renderer/fills.js +189 -0
  204. package/dist/renderer/fills.js.map +1 -0
  205. package/dist/renderer/index.d.ts +2 -0
  206. package/dist/renderer/index.d.ts.map +1 -0
  207. package/dist/renderer/index.js +2 -0
  208. package/dist/renderer/index.js.map +1 -0
  209. package/dist/renderer/label-cache.d.ts +43 -0
  210. package/dist/renderer/label-cache.d.ts.map +1 -0
  211. package/dist/renderer/label-cache.js +85 -0
  212. package/dist/renderer/label-cache.js.map +1 -0
  213. package/dist/renderer/labels.d.ts +6 -0
  214. package/dist/renderer/labels.d.ts.map +1 -0
  215. package/dist/renderer/labels.js +126 -0
  216. package/dist/renderer/labels.js.map +1 -0
  217. package/dist/renderer/lru-cache.d.ts +20 -0
  218. package/dist/renderer/lru-cache.d.ts.map +1 -0
  219. package/dist/renderer/lru-cache.js +65 -0
  220. package/dist/renderer/lru-cache.js.map +1 -0
  221. package/dist/renderer/overlays.d.ts +25 -0
  222. package/dist/renderer/overlays.d.ts.map +1 -0
  223. package/dist/renderer/overlays.js +783 -0
  224. package/dist/renderer/overlays.js.map +1 -0
  225. package/dist/renderer/renderer.d.ts +229 -0
  226. package/dist/renderer/renderer.d.ts.map +1 -0
  227. package/dist/renderer/renderer.js +909 -0
  228. package/dist/renderer/renderer.js.map +1 -0
  229. package/dist/renderer/rulers.d.ts +8 -0
  230. package/dist/renderer/rulers.d.ts.map +1 -0
  231. package/dist/renderer/rulers.js +147 -0
  232. package/dist/renderer/rulers.js.map +1 -0
  233. package/dist/renderer/scene.d.ts +11 -0
  234. package/dist/renderer/scene.d.ts.map +1 -0
  235. package/dist/renderer/scene.js +392 -0
  236. package/dist/renderer/scene.js.map +1 -0
  237. package/dist/renderer/shapes.d.ts +13 -0
  238. package/dist/renderer/shapes.d.ts.map +1 -0
  239. package/dist/renderer/shapes.js +160 -0
  240. package/dist/renderer/shapes.js.map +1 -0
  241. package/dist/renderer/strokes.d.ts +9 -0
  242. package/dist/renderer/strokes.d.ts.map +1 -0
  243. package/dist/renderer/strokes.js +180 -0
  244. package/dist/renderer/strokes.js.map +1 -0
  245. package/dist/rpc/commands.d.ts +202 -0
  246. package/dist/rpc/commands.d.ts.map +1 -0
  247. package/dist/rpc/commands.js +429 -0
  248. package/dist/rpc/commands.js.map +1 -0
  249. package/dist/rpc/index.d.ts +3 -0
  250. package/dist/rpc/index.d.ts.map +1 -0
  251. package/dist/rpc/index.js +2 -0
  252. package/dist/rpc/index.js.map +1 -0
  253. package/dist/scene-graph.d.ts +318 -0
  254. package/dist/scene-graph.d.ts.map +1 -0
  255. package/dist/scene-graph.js +803 -0
  256. package/dist/scene-graph.js.map +1 -0
  257. package/dist/snap.d.ts +16 -0
  258. package/dist/snap.d.ts.map +1 -0
  259. package/dist/snap.js +97 -0
  260. package/dist/snap.js.map +1 -0
  261. package/dist/style-runs.d.ts +20 -0
  262. package/dist/style-runs.d.ts.map +1 -0
  263. package/dist/style-runs.js +168 -0
  264. package/dist/style-runs.js.map +1 -0
  265. package/dist/svg-export/defs.d.ts +19 -0
  266. package/dist/svg-export/defs.d.ts.map +1 -0
  267. package/dist/svg-export/defs.js +191 -0
  268. package/dist/svg-export/defs.js.map +1 -0
  269. package/dist/svg-export/index.d.ts +8 -0
  270. package/dist/svg-export/index.d.ts.map +1 -0
  271. package/dist/svg-export/index.js +318 -0
  272. package/dist/svg-export/index.js.map +1 -0
  273. package/dist/svg-export/paths.d.ts +9 -0
  274. package/dist/svg-export/paths.d.ts.map +1 -0
  275. package/dist/svg-export/paths.js +189 -0
  276. package/dist/svg-export/paths.js.map +1 -0
  277. package/dist/svg-node.d.ts +8 -0
  278. package/dist/svg-node.d.ts.map +1 -0
  279. package/dist/svg-node.js +48 -0
  280. package/dist/svg-node.js.map +1 -0
  281. package/dist/text-editor.d.ts +57 -0
  282. package/dist/text-editor.d.ts.map +1 -0
  283. package/dist/text-editor.js +337 -0
  284. package/dist/text-editor.js.map +1 -0
  285. package/dist/tools/ai-adapter.d.ts +22 -0
  286. package/dist/tools/ai-adapter.d.ts.map +1 -0
  287. package/dist/tools/ai-adapter.js +121 -0
  288. package/dist/tools/ai-adapter.js.map +1 -0
  289. package/dist/tools/analyze.d.ts +22 -0
  290. package/dist/tools/analyze.d.ts.map +1 -0
  291. package/dist/tools/analyze.js +677 -0
  292. package/dist/tools/analyze.js.map +1 -0
  293. package/dist/tools/create.d.ts +8 -0
  294. package/dist/tools/create.d.ts.map +1 -0
  295. package/dist/tools/create.js +204 -0
  296. package/dist/tools/create.js.map +1 -0
  297. package/dist/tools/describe.d.ts +9 -0
  298. package/dist/tools/describe.d.ts.map +1 -0
  299. package/dist/tools/describe.js +295 -0
  300. package/dist/tools/describe.js.map +1 -0
  301. package/dist/tools/export-code.d.ts +2 -0
  302. package/dist/tools/export-code.d.ts.map +1 -0
  303. package/dist/tools/export-code.js +34 -0
  304. package/dist/tools/export-code.js.map +1 -0
  305. package/dist/tools/export-tailwind-config.d.ts +27 -0
  306. package/dist/tools/export-tailwind-config.d.ts.map +1 -0
  307. package/dist/tools/export-tailwind-config.js +215 -0
  308. package/dist/tools/export-tailwind-config.js.map +1 -0
  309. package/dist/tools/icons.d.ts +5 -0
  310. package/dist/tools/icons.d.ts.map +1 -0
  311. package/dist/tools/icons.js +192 -0
  312. package/dist/tools/icons.js.map +1 -0
  313. package/dist/tools/index.d.ts +7 -0
  314. package/dist/tools/index.d.ts.map +1 -0
  315. package/dist/tools/index.js +6 -0
  316. package/dist/tools/index.js.map +1 -0
  317. package/dist/tools/modify.d.ts +22 -0
  318. package/dist/tools/modify.d.ts.map +1 -0
  319. package/dist/tools/modify.js +653 -0
  320. package/dist/tools/modify.js.map +1 -0
  321. package/dist/tools/patterns.d.ts +2 -0
  322. package/dist/tools/patterns.d.ts.map +1 -0
  323. package/dist/tools/patterns.js +173 -0
  324. package/dist/tools/patterns.js.map +1 -0
  325. package/dist/tools/read.d.ts +17 -0
  326. package/dist/tools/read.d.ts.map +1 -0
  327. package/dist/tools/read.js +379 -0
  328. package/dist/tools/read.js.map +1 -0
  329. package/dist/tools/registry.d.ts +3 -0
  330. package/dist/tools/registry.d.ts.map +1 -0
  331. package/dist/tools/registry.js +124 -0
  332. package/dist/tools/registry.js.map +1 -0
  333. package/dist/tools/schema.d.ts +46 -0
  334. package/dist/tools/schema.d.ts.map +1 -0
  335. package/dist/tools/schema.js +17 -0
  336. package/dist/tools/schema.js.map +1 -0
  337. package/dist/tools/structure.d.ts +18 -0
  338. package/dist/tools/structure.d.ts.map +1 -0
  339. package/dist/tools/structure.js +345 -0
  340. package/dist/tools/structure.js.map +1 -0
  341. package/dist/tools/variables.d.ts +12 -0
  342. package/dist/tools/variables.d.ts.map +1 -0
  343. package/dist/tools/variables.js +190 -0
  344. package/dist/tools/variables.js.map +1 -0
  345. package/dist/tools/vector.d.ts +15 -0
  346. package/dist/tools/vector.d.ts.map +1 -0
  347. package/dist/tools/vector.js +312 -0
  348. package/dist/tools/vector.js.map +1 -0
  349. package/dist/types.d.ts +29 -0
  350. package/dist/types.d.ts.map +1 -0
  351. package/dist/types.js +2 -0
  352. package/dist/types.js.map +1 -0
  353. package/dist/undo.d.ts +33 -0
  354. package/dist/undo.d.ts.map +1 -0
  355. package/dist/undo.js +98 -0
  356. package/dist/undo.js.map +1 -0
  357. package/dist/vector.d.ts +14 -0
  358. package/dist/vector.d.ts.map +1 -0
  359. package/dist/vector.js +334 -0
  360. package/dist/vector.js.map +1 -0
  361. package/dist/xpath.d.ts +8 -0
  362. package/dist/xpath.d.ts.map +1 -0
  363. package/dist/xpath.js +199 -0
  364. package/dist/xpath.js.map +1 -0
  365. package/package.json +54 -0
  366. package/src/canvaskit.ts +66 -0
  367. package/src/clipboard.ts +444 -0
  368. package/src/color.ts +83 -0
  369. package/src/constants.ts +277 -0
  370. package/src/copy.ts +75 -0
  371. package/src/fig-export-worker.ts +28 -0
  372. package/src/fig-export.ts +321 -0
  373. package/src/figma-api.ts +1297 -0
  374. package/src/figma-rest-import.ts +443 -0
  375. package/src/fonts.ts +369 -0
  376. package/src/geometry.ts +73 -0
  377. package/src/global.d.ts +20 -0
  378. package/src/index.ts +247 -0
  379. package/src/kiwi/codec.ts +614 -0
  380. package/src/kiwi/fig-file.ts +248 -0
  381. package/src/kiwi/fig-import.ts +300 -0
  382. package/src/kiwi/fig-parse-worker.ts +213 -0
  383. package/src/kiwi/instance-overrides.ts +682 -0
  384. package/src/kiwi/kiwi-convert.ts +771 -0
  385. package/src/kiwi/kiwi-schema/bb.ts +244 -0
  386. package/src/kiwi/kiwi-schema/binary.ts +102 -0
  387. package/src/kiwi/kiwi-schema/index.ts +5 -0
  388. package/src/kiwi/kiwi-schema/js.ts +339 -0
  389. package/src/kiwi/kiwi-schema/parser.ts +287 -0
  390. package/src/kiwi/kiwi-schema/schema.ts +24 -0
  391. package/src/kiwi/kiwi-schema/util.ts +10 -0
  392. package/src/kiwi/protocol.ts +237 -0
  393. package/src/kiwi/schema.ts +2286 -0
  394. package/src/kiwi-serialize.ts +467 -0
  395. package/src/layout.ts +608 -0
  396. package/src/profiler/draw-call-counter.ts +43 -0
  397. package/src/profiler/frame-capture.ts +139 -0
  398. package/src/profiler/frame-stats.ts +122 -0
  399. package/src/profiler/gpu-timer.ts +93 -0
  400. package/src/profiler/hud-renderer.ts +242 -0
  401. package/src/profiler/index.ts +8 -0
  402. package/src/profiler/phase-timer.ts +62 -0
  403. package/src/profiler/render-profiler.ts +163 -0
  404. package/src/render/components.ts +69 -0
  405. package/src/render/emoji-icons.ts +60 -0
  406. package/src/render/export-code.ts +366 -0
  407. package/src/render/export-jsx.ts +633 -0
  408. package/src/render/index.ts +38 -0
  409. package/src/render/jsx-runtime.ts +36 -0
  410. package/src/render/mini-react.ts +30 -0
  411. package/src/render/render-jsx.ts +74 -0
  412. package/src/render/renderer.ts +508 -0
  413. package/src/render/tailwind.ts +114 -0
  414. package/src/render/tree.ts +137 -0
  415. package/src/render-image.ts +139 -0
  416. package/src/renderer/effects.ts +64 -0
  417. package/src/renderer/fills.ts +248 -0
  418. package/src/renderer/index.ts +1 -0
  419. package/src/renderer/label-cache.ts +114 -0
  420. package/src/renderer/labels.ts +168 -0
  421. package/src/renderer/lru-cache.ts +71 -0
  422. package/src/renderer/overlays.ts +1089 -0
  423. package/src/renderer/renderer.ts +1226 -0
  424. package/src/renderer/rulers.ts +230 -0
  425. package/src/renderer/scene.ts +554 -0
  426. package/src/renderer/shapes.ts +193 -0
  427. package/src/renderer/strokes.ts +221 -0
  428. package/src/rpc/commands.ts +667 -0
  429. package/src/rpc/index.ts +25 -0
  430. package/src/scene-graph.ts +1254 -0
  431. package/src/snap.ts +122 -0
  432. package/src/style-runs.ts +234 -0
  433. package/src/svg-export/defs.ts +255 -0
  434. package/src/svg-export/index.ts +427 -0
  435. package/src/svg-export/paths.ts +212 -0
  436. package/src/svg-node.ts +62 -0
  437. package/src/text-editor.ts +349 -0
  438. package/src/tools/ai-adapter.ts +144 -0
  439. package/src/tools/analyze.ts +786 -0
  440. package/src/tools/create.ts +206 -0
  441. package/src/tools/describe.ts +280 -0
  442. package/src/tools/export-code.ts +41 -0
  443. package/src/tools/export-tailwind-config.ts +266 -0
  444. package/src/tools/icons.ts +213 -0
  445. package/src/tools/index.ts +6 -0
  446. package/src/tools/modify.ts +651 -0
  447. package/src/tools/patterns.ts +189 -0
  448. package/src/tools/read.ts +401 -0
  449. package/src/tools/registry.ts +155 -0
  450. package/src/tools/schema.ts +65 -0
  451. package/src/tools/structure.ts +348 -0
  452. package/src/tools/variables.ts +193 -0
  453. package/src/tools/vector.ts +311 -0
  454. package/src/types.ts +32 -0
  455. package/src/undo.ts +126 -0
  456. package/src/vector.ts +413 -0
  457. package/src/xpath.ts +259 -0
@@ -0,0 +1,1254 @@
1
+ import { createNanoEvents } from 'nanoevents'
2
+
3
+ import { BLACK, DEFAULT_FONT_FAMILY, DEFAULT_STROKE_MITER_LIMIT } from './constants'
4
+ import { copyEffects, copyFills, copyStrokes, copyStyleRuns } from './copy'
5
+
6
+ export type { GUID, Color } from './types'
7
+ import type { Matrix, Vector, Color, Rect } from './types'
8
+ import type { Emitter } from 'nanoevents'
9
+
10
+ export interface SceneGraphEvents {
11
+ 'node:created': (node: SceneNode) => void
12
+ 'node:updated': (id: string, changes: Partial<SceneNode>) => void
13
+ 'node:deleted': (id: string) => void
14
+ 'node:reparented': (nodeId: string, oldParentId: string | null, newParentId: string) => void
15
+ 'node:reordered': (nodeId: string, parentId: string, index: number) => void
16
+ }
17
+
18
+ export type HandleMirroring = 'NONE' | 'ANGLE' | 'ANGLE_AND_LENGTH'
19
+ export type WindingRule = 'NONZERO' | 'EVENODD'
20
+
21
+ export interface VectorVertex {
22
+ x: number
23
+ y: number
24
+ strokeCap?: string
25
+ strokeJoin?: string
26
+ cornerRadius?: number
27
+ handleMirroring?: HandleMirroring
28
+ }
29
+
30
+ export interface VectorSegment {
31
+ start: number
32
+ end: number
33
+ tangentStart: Vector
34
+ tangentEnd: Vector
35
+ }
36
+
37
+ export interface VectorRegion {
38
+ windingRule: WindingRule
39
+ loops: number[][]
40
+ }
41
+
42
+ export interface VectorNetwork {
43
+ vertices: VectorVertex[]
44
+ segments: VectorSegment[]
45
+ regions: VectorRegion[]
46
+ }
47
+
48
+ export interface GeometryPath {
49
+ windingRule: WindingRule
50
+ commandsBlob: Uint8Array
51
+ }
52
+
53
+ export type NodeType =
54
+ | 'CANVAS'
55
+ | 'FRAME'
56
+ | 'RECTANGLE'
57
+ | 'ROUNDED_RECTANGLE'
58
+ | 'ELLIPSE'
59
+ | 'TEXT'
60
+ | 'LINE'
61
+ | 'STAR'
62
+ | 'POLYGON'
63
+ | 'VECTOR'
64
+ | 'GROUP'
65
+ | 'SECTION'
66
+ | 'COMPONENT'
67
+ | 'COMPONENT_SET'
68
+ | 'INSTANCE'
69
+ | 'CONNECTOR'
70
+ | 'SHAPE_WITH_TEXT'
71
+
72
+
73
+ export type FillType =
74
+ | 'SOLID'
75
+ | 'GRADIENT_LINEAR'
76
+ | 'GRADIENT_RADIAL'
77
+ | 'GRADIENT_ANGULAR'
78
+ | 'GRADIENT_DIAMOND'
79
+ | 'IMAGE'
80
+ export type BlendMode =
81
+ | 'NORMAL'
82
+ | 'DARKEN'
83
+ | 'MULTIPLY'
84
+ | 'COLOR_BURN'
85
+ | 'LIGHTEN'
86
+ | 'SCREEN'
87
+ | 'COLOR_DODGE'
88
+ | 'OVERLAY'
89
+ | 'SOFT_LIGHT'
90
+ | 'HARD_LIGHT'
91
+ | 'DIFFERENCE'
92
+ | 'EXCLUSION'
93
+ | 'HUE'
94
+ | 'SATURATION'
95
+ | 'COLOR'
96
+ | 'LUMINOSITY'
97
+ | 'PASS_THROUGH'
98
+ export type ImageScaleMode = 'FILL' | 'FIT' | 'CROP' | 'TILE'
99
+
100
+ export interface GradientStop {
101
+ color: Color
102
+ position: number
103
+ }
104
+
105
+ export type GradientTransform = Matrix
106
+
107
+ export interface Fill {
108
+ type: FillType
109
+ color: Color
110
+ opacity: number
111
+ visible: boolean
112
+ blendMode?: BlendMode
113
+ gradientStops?: GradientStop[]
114
+ gradientTransform?: GradientTransform
115
+ imageHash?: string
116
+ imageScaleMode?: ImageScaleMode
117
+ imageTransform?: GradientTransform
118
+ }
119
+
120
+ export type StrokeCap = 'NONE' | 'ROUND' | 'SQUARE' | 'ARROW_LINES' | 'ARROW_EQUILATERAL'
121
+ export type StrokeJoin = 'MITER' | 'BEVEL' | 'ROUND'
122
+ export type MaskType = 'ALPHA' | 'VECTOR' | 'LUMINANCE'
123
+
124
+ export interface Stroke {
125
+ color: Color
126
+ weight: number
127
+ opacity: number
128
+ visible: boolean
129
+ align: 'INSIDE' | 'CENTER' | 'OUTSIDE'
130
+ cap?: StrokeCap
131
+ join?: StrokeJoin
132
+ dashPattern?: number[]
133
+ }
134
+
135
+ export interface Effect {
136
+ type: 'DROP_SHADOW' | 'INNER_SHADOW' | 'LAYER_BLUR' | 'BACKGROUND_BLUR' | 'FOREGROUND_BLUR'
137
+ color: Color
138
+ offset: Vector
139
+ radius: number
140
+ spread: number
141
+ visible: boolean
142
+ blendMode?: BlendMode
143
+ }
144
+
145
+ export type ConstraintType = 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
146
+ export type TextAutoResize = 'NONE' | 'HEIGHT' | 'WIDTH_AND_HEIGHT' | 'TRUNCATE'
147
+ export type TextAlignVertical = 'TOP' | 'CENTER' | 'BOTTOM'
148
+ export type TextCase = 'ORIGINAL' | 'UPPER' | 'LOWER' | 'TITLE'
149
+ export type TextDecoration = 'NONE' | 'UNDERLINE' | 'STRIKETHROUGH'
150
+
151
+ export interface CharacterStyleOverride {
152
+ fontWeight?: number
153
+ italic?: boolean
154
+ textDecoration?: TextDecoration
155
+ fontSize?: number
156
+ fontFamily?: string
157
+ letterSpacing?: number
158
+ lineHeight?: number | null
159
+ }
160
+
161
+ export interface StyleRun {
162
+ start: number
163
+ length: number
164
+ style: CharacterStyleOverride
165
+ }
166
+
167
+ export interface ArcData {
168
+ startingAngle: number
169
+ endingAngle: number
170
+ innerRadius: number
171
+ }
172
+
173
+ export type LayoutMode = 'NONE' | 'HORIZONTAL' | 'VERTICAL' | 'GRID'
174
+ export type LayoutSizing = 'FIXED' | 'HUG' | 'FILL'
175
+
176
+ export type GridTrackSizing = 'FIXED' | 'FR' | 'AUTO'
177
+
178
+ export interface GridTrack {
179
+ sizing: GridTrackSizing
180
+ value: number
181
+ }
182
+
183
+ export interface GridPosition {
184
+ column: number
185
+ row: number
186
+ columnSpan: number
187
+ rowSpan: number
188
+ }
189
+ export type LayoutAlign = 'MIN' | 'CENTER' | 'MAX' | 'SPACE_BETWEEN'
190
+ export type LayoutCounterAlign = 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'BASELINE'
191
+ export type LayoutAlignSelf = 'AUTO' | 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'BASELINE'
192
+ export type LayoutWrap = 'NO_WRAP' | 'WRAP'
193
+
194
+ export interface SceneNode {
195
+ id: string
196
+ type: NodeType
197
+ name: string
198
+ parentId: string | null
199
+ childIds: string[]
200
+
201
+ x: number
202
+ y: number
203
+ width: number
204
+ height: number
205
+ rotation: number
206
+
207
+ fills: Fill[]
208
+ strokes: Stroke[]
209
+ effects: Effect[]
210
+ opacity: number
211
+
212
+ cornerRadius: number
213
+ topLeftRadius: number
214
+ topRightRadius: number
215
+ bottomRightRadius: number
216
+ bottomLeftRadius: number
217
+ independentCorners: boolean
218
+ cornerSmoothing: number
219
+
220
+ visible: boolean
221
+ locked: boolean
222
+ clipsContent: boolean
223
+
224
+ blendMode: BlendMode
225
+
226
+ text: string
227
+ fontSize: number
228
+ fontFamily: string
229
+ fontWeight: number
230
+ italic: boolean
231
+ textAlignHorizontal: 'LEFT' | 'CENTER' | 'RIGHT' | 'JUSTIFIED'
232
+ textAlignVertical: TextAlignVertical
233
+ textAutoResize: TextAutoResize
234
+ textCase: TextCase
235
+ textDecoration: TextDecoration
236
+ lineHeight: number | null
237
+ letterSpacing: number
238
+ maxLines: number | null
239
+ styleRuns: StyleRun[]
240
+
241
+ horizontalConstraint: ConstraintType
242
+ verticalConstraint: ConstraintType
243
+
244
+ layoutMode: LayoutMode
245
+ layoutWrap: LayoutWrap
246
+ primaryAxisAlign: LayoutAlign
247
+ counterAxisAlign: LayoutCounterAlign
248
+ primaryAxisSizing: LayoutSizing
249
+ counterAxisSizing: LayoutSizing
250
+ itemSpacing: number
251
+ counterAxisSpacing: number
252
+ paddingTop: number
253
+ paddingRight: number
254
+ paddingBottom: number
255
+ paddingLeft: number
256
+
257
+ layoutPositioning: 'AUTO' | 'ABSOLUTE'
258
+ layoutGrow: number
259
+ layoutAlignSelf: LayoutAlignSelf
260
+
261
+ vectorNetwork: VectorNetwork | null
262
+ fillGeometry: GeometryPath[]
263
+ strokeGeometry: GeometryPath[]
264
+
265
+ arcData: ArcData | null
266
+
267
+ strokeCap: StrokeCap
268
+ strokeJoin: StrokeJoin
269
+ dashPattern: number[]
270
+
271
+ borderTopWeight: number
272
+ borderRightWeight: number
273
+ borderBottomWeight: number
274
+ borderLeftWeight: number
275
+ independentStrokeWeights: boolean
276
+
277
+ strokeMiterLimit: number
278
+
279
+ minWidth: number | null
280
+ maxWidth: number | null
281
+ minHeight: number | null
282
+ maxHeight: number | null
283
+
284
+ isMask: boolean
285
+ maskType: MaskType
286
+
287
+ gridTemplateColumns: GridTrack[]
288
+ gridTemplateRows: GridTrack[]
289
+ gridColumnGap: number
290
+ gridRowGap: number
291
+ gridPosition: GridPosition | null
292
+
293
+ counterAxisAlignContent: 'AUTO' | 'SPACE_BETWEEN'
294
+ itemReverseZIndex: boolean
295
+ strokesIncludedInLayout: boolean
296
+
297
+ expanded: boolean
298
+ textTruncation: 'DISABLED' | 'ENDING'
299
+ autoRename: boolean
300
+
301
+ pointCount: number
302
+ starInnerRadius: number
303
+
304
+ componentId: string | null
305
+ overrides: Record<string, unknown>
306
+
307
+ boundVariables: Record<string, string>
308
+
309
+ internalOnly: boolean
310
+
311
+ flipX: boolean
312
+ flipY: boolean
313
+
314
+ textPicture: Uint8Array | null
315
+ }
316
+
317
+ export type VariableType = 'COLOR' | 'FLOAT' | 'STRING' | 'BOOLEAN'
318
+ export type VariableValue = Color | number | string | boolean | { aliasId: string }
319
+
320
+ export interface Variable {
321
+ id: string
322
+ name: string
323
+ type: VariableType
324
+ collectionId: string
325
+ valuesByMode: Record<string, VariableValue>
326
+ description: string
327
+ hiddenFromPublishing: boolean
328
+ }
329
+
330
+ export interface VariableCollectionMode {
331
+ modeId: string
332
+ name: string
333
+ }
334
+
335
+ export interface VariableCollection {
336
+ id: string
337
+ name: string
338
+ modes: VariableCollectionMode[]
339
+ defaultModeId: string
340
+ variableIds: string[]
341
+ }
342
+
343
+ let nextLocalID = 1
344
+
345
+ export function generateId(): string {
346
+ return `0:${nextLocalID++}`
347
+ }
348
+
349
+ function createDefaultNode(type: NodeType, overrides: Partial<SceneNode> = {}, id?: string): SceneNode {
350
+ return {
351
+ id: id ?? generateId(),
352
+ type,
353
+ name: type.charAt(0) + type.slice(1).toLowerCase(),
354
+ parentId: null,
355
+ childIds: [],
356
+ x: 0,
357
+ y: 0,
358
+ width: 100,
359
+ height: 100,
360
+ rotation: 0,
361
+ fills: [],
362
+ strokes: [],
363
+ effects: [],
364
+ opacity: 1,
365
+ cornerRadius: 0,
366
+ topLeftRadius: 0,
367
+ topRightRadius: 0,
368
+ bottomRightRadius: 0,
369
+ bottomLeftRadius: 0,
370
+ independentCorners: false,
371
+ cornerSmoothing: 0,
372
+ visible: true,
373
+ locked: false,
374
+ clipsContent: false,
375
+ text: '',
376
+ fontSize: 14,
377
+ fontFamily: DEFAULT_FONT_FAMILY,
378
+ fontWeight: 400,
379
+ italic: false,
380
+ textAlignHorizontal: 'LEFT',
381
+ lineHeight: null,
382
+ letterSpacing: 0,
383
+ layoutMode: 'NONE',
384
+ layoutWrap: 'NO_WRAP',
385
+ primaryAxisAlign: 'MIN',
386
+ counterAxisAlign: 'MIN',
387
+ primaryAxisSizing: 'FIXED',
388
+ counterAxisSizing: 'FIXED',
389
+ itemSpacing: 0,
390
+ counterAxisSpacing: 0,
391
+ paddingTop: 0,
392
+ paddingRight: 0,
393
+ paddingBottom: 0,
394
+ paddingLeft: 0,
395
+ blendMode: 'PASS_THROUGH',
396
+ layoutPositioning: 'AUTO',
397
+ layoutGrow: 0,
398
+ layoutAlignSelf: 'AUTO',
399
+ vectorNetwork: null,
400
+ fillGeometry: [],
401
+ strokeGeometry: [],
402
+ arcData: null,
403
+ textAlignVertical: 'TOP',
404
+ textAutoResize: 'NONE',
405
+ textCase: 'ORIGINAL',
406
+ textDecoration: 'NONE',
407
+ maxLines: null,
408
+ styleRuns: [],
409
+ horizontalConstraint: 'MIN',
410
+ verticalConstraint: 'MIN',
411
+ strokeCap: 'NONE',
412
+ strokeJoin: 'MITER',
413
+ dashPattern: [],
414
+ borderTopWeight: 0,
415
+ borderRightWeight: 0,
416
+ borderBottomWeight: 0,
417
+ borderLeftWeight: 0,
418
+ independentStrokeWeights: false,
419
+ strokeMiterLimit: DEFAULT_STROKE_MITER_LIMIT,
420
+ minWidth: null,
421
+ maxWidth: null,
422
+ minHeight: null,
423
+ maxHeight: null,
424
+ isMask: false,
425
+ maskType: 'ALPHA',
426
+ gridTemplateColumns: [],
427
+ gridTemplateRows: [],
428
+ gridColumnGap: 0,
429
+ gridRowGap: 0,
430
+ gridPosition: null,
431
+ counterAxisAlignContent: 'AUTO',
432
+ itemReverseZIndex: false,
433
+ strokesIncludedInLayout: false,
434
+ expanded: true,
435
+ textTruncation: 'DISABLED',
436
+ autoRename: true,
437
+ pointCount: 5,
438
+ starInnerRadius: 0.38,
439
+ componentId: null,
440
+ overrides: {},
441
+ boundVariables: {},
442
+ internalOnly: false,
443
+ flipX: false,
444
+ flipY: false,
445
+ textPicture: null,
446
+ ...overrides
447
+ }
448
+ }
449
+
450
+ const CONTAINER_TYPES = new Set<NodeType>([
451
+ 'CANVAS',
452
+ 'FRAME',
453
+ 'GROUP',
454
+ 'SECTION',
455
+ 'COMPONENT',
456
+ 'COMPONENT_SET',
457
+ 'INSTANCE'
458
+ ])
459
+
460
+ export class SceneGraph {
461
+ nodes = new Map<string, SceneNode>()
462
+ images = new Map<string, Uint8Array>()
463
+ variables = new Map<string, Variable>()
464
+ variableCollections = new Map<string, VariableCollection>()
465
+ activeMode = new Map<string, string>()
466
+ rootId: string
467
+ readonly emitter: Emitter<SceneGraphEvents> = createNanoEvents()
468
+ private absPosCache = new Map<string, Vector>()
469
+ private instanceIndex = new Map<string, Set<string>>()
470
+ private nextLocalID = 1
471
+
472
+ constructor() {
473
+ const root = createDefaultNode('FRAME', {
474
+ name: 'Document',
475
+ width: 0,
476
+ height: 0
477
+ }, this.generateId())
478
+ this.rootId = root.id
479
+ this.nodes.set(root.id, root)
480
+
481
+ this.addPage('Page 1')
482
+ }
483
+
484
+ generateId(): string {
485
+ return `0:${this.nextLocalID++}`
486
+ }
487
+
488
+ addPage(name: string): SceneNode {
489
+ return this.createNode('CANVAS', this.rootId, { name, width: 0, height: 0 })
490
+ }
491
+
492
+ getPages(includeInternal = false): SceneNode[] {
493
+ return this.getChildren(this.rootId).filter(
494
+ (n) => n.type === 'CANVAS' && (includeInternal || !n.internalOnly)
495
+ )
496
+ }
497
+
498
+ getAllNodes(): Iterable<SceneNode> {
499
+ return this.nodes.values()
500
+ }
501
+
502
+ getNode(id: string): SceneNode | undefined {
503
+ return this.nodes.get(id)
504
+ }
505
+
506
+ // --- Variables ---
507
+
508
+ addVariable(variable: Variable): void {
509
+ this.variables.set(variable.id, variable)
510
+ const collection = this.variableCollections.get(variable.collectionId)
511
+ if (collection && !collection.variableIds.includes(variable.id)) {
512
+ collection.variableIds.push(variable.id)
513
+ }
514
+ }
515
+
516
+ removeVariable(id: string): void {
517
+ const variable = this.variables.get(id)
518
+ if (!variable) return
519
+ this.variables.delete(id)
520
+ const collection = this.variableCollections.get(variable.collectionId)
521
+ if (collection) {
522
+ collection.variableIds = collection.variableIds.filter((vid) => vid !== id)
523
+ }
524
+ for (const node of this.nodes.values()) {
525
+ for (const [field, varId] of Object.entries(node.boundVariables)) {
526
+ if (varId === id) delete node.boundVariables[field]
527
+ }
528
+ }
529
+ }
530
+
531
+ addCollection(collection: VariableCollection): void {
532
+ this.variableCollections.set(collection.id, collection)
533
+ if (!this.activeMode.has(collection.id)) {
534
+ this.activeMode.set(collection.id, collection.defaultModeId)
535
+ }
536
+ }
537
+
538
+ createVariable(
539
+ name: string,
540
+ type: VariableType,
541
+ collectionId: string,
542
+ value?: VariableValue
543
+ ): Variable {
544
+ const collection = this.variableCollections.get(collectionId)
545
+ if (!collection) throw new Error(`Collection "${collectionId}" not found`)
546
+ const id = this.generateId()
547
+ let defaultValue: VariableValue
548
+ if (value !== undefined) {
549
+ defaultValue = value
550
+ } else if (type === 'COLOR') {
551
+ defaultValue = { ...BLACK }
552
+ } else if (type === 'FLOAT') {
553
+ defaultValue = 0
554
+ } else if (type === 'BOOLEAN') {
555
+ defaultValue = false
556
+ } else {
557
+ defaultValue = ''
558
+ }
559
+ const valuesByMode: Record<string, VariableValue> = {}
560
+ for (const mode of collection.modes) {
561
+ valuesByMode[mode.modeId] = structuredClone(defaultValue)
562
+ }
563
+ const variable: Variable = {
564
+ id,
565
+ name,
566
+ type,
567
+ collectionId,
568
+ valuesByMode,
569
+ description: '',
570
+ hiddenFromPublishing: false
571
+ }
572
+ this.addVariable(variable)
573
+ return variable
574
+ }
575
+
576
+ createCollection(name: string): VariableCollection {
577
+ const id = this.generateId()
578
+ const modeId = this.generateId()
579
+ const collection: VariableCollection = {
580
+ id,
581
+ name,
582
+ modes: [{ modeId, name: 'Mode 1' }],
583
+ defaultModeId: modeId,
584
+ variableIds: []
585
+ }
586
+ this.addCollection(collection)
587
+ return collection
588
+ }
589
+
590
+ removeCollection(id: string): void {
591
+ const collection = this.variableCollections.get(id)
592
+ if (collection) {
593
+ for (const varId of Array.from(collection.variableIds)) {
594
+ this.removeVariable(varId)
595
+ }
596
+ }
597
+ this.variableCollections.delete(id)
598
+ this.activeMode.delete(id)
599
+ }
600
+
601
+ getActiveModeId(collectionId: string): string {
602
+ const mode = this.activeMode.get(collectionId)
603
+ if (mode) return mode
604
+ const collection = this.variableCollections.get(collectionId)
605
+ return collection?.defaultModeId ?? ''
606
+ }
607
+
608
+ setActiveMode(collectionId: string, modeId: string): void {
609
+ this.activeMode.set(collectionId, modeId)
610
+ }
611
+
612
+ resolveVariable(
613
+ variableId: string,
614
+ modeId?: string,
615
+ visited?: Set<string>
616
+ ): VariableValue | undefined {
617
+ if (visited?.has(variableId)) return undefined
618
+ const variable = this.variables.get(variableId)
619
+ if (!variable) return undefined
620
+ const resolvedModeId = modeId ?? this.getActiveModeId(variable.collectionId)
621
+ const value = variable.valuesByMode[resolvedModeId]
622
+ if (typeof value === 'object' && 'aliasId' in value) {
623
+ const seen = visited ?? new Set<string>()
624
+ seen.add(variableId)
625
+ return this.resolveVariable(value.aliasId, undefined, seen)
626
+ }
627
+ return value
628
+ }
629
+
630
+ resolveColorVariable(variableId: string): Color | undefined {
631
+ const value = this.resolveVariable(variableId)
632
+ if (value && typeof value === 'object' && 'r' in value) return value
633
+ return undefined
634
+ }
635
+
636
+ resolveNumberVariable(variableId: string): number | undefined {
637
+ const value = this.resolveVariable(variableId)
638
+ return typeof value === 'number' ? value : undefined
639
+ }
640
+
641
+ getVariablesForCollection(collectionId: string): Variable[] {
642
+ const collection = this.variableCollections.get(collectionId)
643
+ if (!collection) return []
644
+ return collection.variableIds
645
+ .map((id) => this.variables.get(id))
646
+ .filter((v): v is Variable => v !== undefined)
647
+ }
648
+
649
+ getVariablesByType(type: VariableType): Variable[] {
650
+ return [...this.variables.values()].filter((v) => v.type === type)
651
+ }
652
+
653
+ bindVariable(nodeId: string, field: string, variableId: string): void {
654
+ const node = this.nodes.get(nodeId)
655
+ if (node) node.boundVariables[field] = variableId
656
+ }
657
+
658
+ unbindVariable(nodeId: string, field: string): void {
659
+ const node = this.nodes.get(nodeId)
660
+ if (node) delete node.boundVariables[field]
661
+ }
662
+
663
+ getChildren(id: string): SceneNode[] {
664
+ const node = this.nodes.get(id)
665
+ if (!node) return []
666
+ return node.childIds
667
+ .map((cid) => this.nodes.get(cid))
668
+ .filter((n): n is SceneNode => n !== undefined)
669
+ }
670
+
671
+ isContainer(id: string): boolean {
672
+ const node = this.nodes.get(id)
673
+ return node ? CONTAINER_TYPES.has(node.type) : false
674
+ }
675
+
676
+ isDescendant(childId: string, ancestorId: string): boolean {
677
+ let current = this.nodes.get(childId)
678
+ while (current) {
679
+ if (current.id === ancestorId) return true
680
+ current = current.parentId ? this.nodes.get(current.parentId) : undefined
681
+ }
682
+ return false
683
+ }
684
+
685
+ clearAbsPosCache(): void {
686
+ this.absPosCache.clear()
687
+ }
688
+
689
+ getAbsolutePosition(id: string): Vector {
690
+ const cached = this.absPosCache.get(id)
691
+ if (cached) return cached
692
+
693
+ let ax = 0
694
+ let ay = 0
695
+ let current = this.nodes.get(id)
696
+ while (current && current.id !== this.rootId && current.type !== 'CANVAS') {
697
+ ax += current.x
698
+ ay += current.y
699
+ current = current.parentId ? this.nodes.get(current.parentId) : undefined
700
+ }
701
+ const result = { x: ax, y: ay }
702
+ this.absPosCache.set(id, result)
703
+ return result
704
+ }
705
+
706
+ getAbsoluteBounds(id: string): Rect {
707
+ const pos = this.getAbsolutePosition(id)
708
+ const node = this.nodes.get(id)
709
+ return {
710
+ x: pos.x,
711
+ y: pos.y,
712
+ width: node?.width ?? 0,
713
+ height: node?.height ?? 0
714
+ }
715
+ }
716
+
717
+ createNode(type: NodeType, parentId: string, overrides: Partial<SceneNode> & { characters?: string } = {}): SceneNode {
718
+ // Figma API compat: map `characters` → `text`
719
+ if ('characters' in overrides && overrides.characters != null && !('text' in overrides)) {
720
+ (overrides as Partial<SceneNode>).text = overrides.characters
721
+ }
722
+ const node = createDefaultNode(type, overrides as Partial<SceneNode>, this.generateId())
723
+ node.parentId = parentId
724
+ this.nodes.set(node.id, node)
725
+
726
+ const parent = this.nodes.get(parentId)
727
+ if (parent) {
728
+ parent.childIds.push(node.id)
729
+ }
730
+
731
+ if (node.type === 'INSTANCE' && node.componentId) {
732
+ let set = this.instanceIndex.get(node.componentId)
733
+ if (!set) {
734
+ set = new Set()
735
+ this.instanceIndex.set(node.componentId, set)
736
+ }
737
+ set.add(node.id)
738
+ }
739
+
740
+ this.emitter.emit('node:created', node)
741
+ return node
742
+ }
743
+
744
+ private static readonly POS_AFFECTING_PROPS = new Set<string>([
745
+ 'x', 'y', 'width', 'height', 'rotation', 'parentId',
746
+ 'layoutMode', 'layoutAlign', 'layoutGrow',
747
+ 'primaryAxisSizingMode', 'counterAxisSizingMode',
748
+ ])
749
+
750
+ updateNode(id: string, changes: Partial<SceneNode>): void {
751
+ const node = this.nodes.get(id)
752
+ if (!node) return
753
+ for (const key of Object.keys(changes)) {
754
+ if (SceneGraph.POS_AFFECTING_PROPS.has(key)) {
755
+ this.absPosCache.clear()
756
+ break
757
+ }
758
+ }
759
+ if (node.type === 'INSTANCE' && 'componentId' in changes && changes.componentId !== node.componentId) {
760
+ if (node.componentId) this.instanceIndex.get(node.componentId)?.delete(id)
761
+ if (changes.componentId) {
762
+ let set = this.instanceIndex.get(changes.componentId)
763
+ if (!set) {
764
+ set = new Set()
765
+ this.instanceIndex.set(changes.componentId, set)
766
+ }
767
+ set.add(id)
768
+ }
769
+ }
770
+ Object.assign(node, changes)
771
+ this.emitter.emit('node:updated', id, changes)
772
+ }
773
+
774
+ reparentNode(nodeId: string, newParentId: string): void {
775
+ const node = this.nodes.get(nodeId)
776
+ if (!node || nodeId === this.rootId) return
777
+ if (this.isDescendant(newParentId, nodeId)) return
778
+
779
+ const oldParent = node.parentId ? this.nodes.get(node.parentId) : undefined
780
+ const newParent = this.nodes.get(newParentId)
781
+ if (!newParent) return
782
+ if (node.parentId === newParentId) return
783
+
784
+ const oldParentId = node.parentId
785
+ this.absPosCache.clear()
786
+
787
+ // Convert absolute position
788
+ const absPos = this.getAbsolutePosition(nodeId)
789
+ const newParentNode = this.nodes.get(newParentId)
790
+ const newParentAbs =
791
+ newParentId === this.rootId || newParentNode?.type === 'CANVAS'
792
+ ? { x: 0, y: 0 }
793
+ : this.getAbsolutePosition(newParentId)
794
+
795
+ // Remove from old parent
796
+ if (oldParent) {
797
+ oldParent.childIds = oldParent.childIds.filter((cid) => cid !== nodeId)
798
+ }
799
+
800
+ // Add to new parent
801
+ node.parentId = newParentId
802
+ newParent.childIds.push(nodeId)
803
+
804
+ // Adjust position so node stays in same visual place
805
+ node.x = absPos.x - newParentAbs.x
806
+ node.y = absPos.y - newParentAbs.y
807
+
808
+ this.emitter.emit('node:reparented', nodeId, oldParentId, newParentId)
809
+ }
810
+
811
+ reorderChild(nodeId: string, parentId: string, insertIndex: number): void {
812
+ const node = this.nodes.get(nodeId)
813
+ if (!node) return
814
+
815
+ const oldParent = node.parentId ? this.nodes.get(node.parentId) : undefined
816
+ const newParent = this.nodes.get(parentId)
817
+ if (!newParent) return
818
+
819
+ // Capture original index before removal for same-parent off-by-one correction
820
+ const oldIndex = oldParent === newParent ? oldParent.childIds.indexOf(nodeId) : -1
821
+
822
+ // Remove from old parent
823
+ if (oldParent) {
824
+ oldParent.childIds = oldParent.childIds.filter((cid) => cid !== nodeId)
825
+ }
826
+
827
+ // If same parent and target is after the removed position, adjust down by 1
828
+ let idx = insertIndex
829
+ if (oldParent === newParent && oldIndex !== -1 && idx > oldIndex) {
830
+ idx--
831
+ }
832
+
833
+ node.parentId = parentId
834
+ idx = Math.min(idx, newParent.childIds.length)
835
+ newParent.childIds.splice(idx, 0, nodeId)
836
+
837
+ this.emitter.emit('node:reordered', nodeId, parentId, idx)
838
+ }
839
+
840
+ deleteNode(id: string): void {
841
+ const node = this.nodes.get(id)
842
+ if (!node || id === this.rootId) return
843
+
844
+ if (node.parentId) {
845
+ const parent = this.nodes.get(node.parentId)
846
+ if (parent) {
847
+ parent.childIds = parent.childIds.filter((cid) => cid !== id)
848
+ }
849
+ }
850
+
851
+ for (const childId of Array.from(node.childIds)) {
852
+ this.deleteNode(childId)
853
+ }
854
+
855
+ if (node.type === 'INSTANCE' && node.componentId) {
856
+ this.instanceIndex.get(node.componentId)?.delete(id)
857
+ }
858
+ this.nodes.delete(id)
859
+ this.emitter.emit('node:deleted', id)
860
+ }
861
+
862
+ hitTest(px: number, py: number, scopeId?: string): SceneNode | null {
863
+ const scope = scopeId ?? this.rootId
864
+ return this.hitTestChildren(px, py, scope, 0, 0, false)
865
+ }
866
+
867
+ hitTestDeep(px: number, py: number, scopeId?: string): SceneNode | null {
868
+ const scope = scopeId ?? this.rootId
869
+ return this.hitTestChildren(px, py, scope, 0, 0, true)
870
+ }
871
+
872
+ private static readonly OPAQUE_CONTAINER_TYPES = new Set<NodeType>(['COMPONENT', 'INSTANCE'])
873
+
874
+ private static hasVisibleFillOrStroke(node: SceneNode): boolean {
875
+ return (
876
+ node.fills.some((f) => f.visible) ||
877
+ node.strokes.some((s) => s.visible)
878
+ )
879
+ }
880
+
881
+ private static containsPoint(
882
+ px: number,
883
+ py: number,
884
+ ax: number,
885
+ ay: number,
886
+ node: SceneNode
887
+ ): boolean {
888
+ return px >= ax && px <= ax + node.width && py >= ay && py <= ay + node.height
889
+ }
890
+
891
+ private hitTestOpaqueContainer(
892
+ px: number,
893
+ py: number,
894
+ child: SceneNode,
895
+ childId: string,
896
+ ax: number,
897
+ ay: number,
898
+ deep: boolean
899
+ ): SceneNode | null {
900
+ if (!SceneGraph.containsPoint(px, py, ax, ay, child)) return null
901
+ const childHit = this.hitTestChildren(px, py, childId, ax, ay, deep)
902
+ if (childHit) return child
903
+ if (SceneGraph.hasVisibleFillOrStroke(child)) return child
904
+ return null
905
+ }
906
+
907
+ private hitTestTransparentContainer(
908
+ px: number,
909
+ py: number,
910
+ child: SceneNode,
911
+ childId: string,
912
+ ax: number,
913
+ ay: number,
914
+ deep: boolean
915
+ ): SceneNode | null {
916
+ const deepHit = this.hitTestChildren(px, py, childId, ax, ay, deep)
917
+ if (deepHit) return deepHit
918
+ if (child.type === 'GROUP') return null
919
+ if (SceneGraph.containsPoint(px, py, ax, ay, child) && SceneGraph.hasVisibleFillOrStroke(child)) return child
920
+ return null
921
+ }
922
+
923
+ private hitTestChildren(
924
+ px: number,
925
+ py: number,
926
+ parentId: string,
927
+ offsetX: number,
928
+ offsetY: number,
929
+ deep = false
930
+ ): SceneNode | null {
931
+ const parent = this.nodes.get(parentId)
932
+ if (!parent) return null
933
+
934
+ if (parent.clipsContent) {
935
+ if (!SceneGraph.containsPoint(px, py, offsetX, offsetY, parent)) return null
936
+ }
937
+
938
+ for (let i = parent.childIds.length - 1; i >= 0; i--) {
939
+ const childId = parent.childIds[i]
940
+ const child = this.nodes.get(childId)
941
+ if (!child || !child.visible) continue
942
+
943
+ const ax = offsetX + child.x
944
+ const ay = offsetY + child.y
945
+
946
+ if (CONTAINER_TYPES.has(child.type)) {
947
+ if (SceneGraph.OPAQUE_CONTAINER_TYPES.has(child.type) && !deep) {
948
+ const hit = this.hitTestOpaqueContainer(px, py, child, childId, ax, ay, deep)
949
+ if (hit) return hit
950
+ continue
951
+ }
952
+
953
+ const hit = this.hitTestTransparentContainer(px, py, child, childId, ax, ay, deep)
954
+ if (hit) return hit
955
+ continue
956
+ }
957
+
958
+ if (SceneGraph.containsPoint(px, py, ax, ay, child)) return child
959
+ }
960
+ return null
961
+ }
962
+
963
+ hitTestFrame(
964
+ px: number,
965
+ py: number,
966
+ excludeIds: Set<string>,
967
+ scopeId?: string
968
+ ): SceneNode | null {
969
+ return this.hitTestFrameChildren(px, py, scopeId ?? this.rootId, 0, 0, excludeIds)
970
+ }
971
+
972
+ private hitTestFrameChildren(
973
+ px: number,
974
+ py: number,
975
+ parentId: string,
976
+ offsetX: number,
977
+ offsetY: number,
978
+ excludeIds: Set<string>
979
+ ): SceneNode | null {
980
+ const parent = this.nodes.get(parentId)
981
+ if (!parent) return null
982
+
983
+ // Deepest matching frame wins
984
+ let best: SceneNode | null = null
985
+
986
+ for (const childId of parent.childIds) {
987
+ if (excludeIds.has(childId)) continue
988
+ const child = this.nodes.get(childId)
989
+ if (!child || !child.visible) continue
990
+
991
+ const ax = offsetX + child.x
992
+ const ay = offsetY + child.y
993
+
994
+ if (!CONTAINER_TYPES.has(child.type)) continue
995
+ if (px < ax || px > ax + child.width || py < ay || py > ay + child.height) continue
996
+
997
+ best = child
998
+
999
+ const deeper = this.hitTestFrameChildren(px, py, childId, ax, ay, excludeIds)
1000
+ if (deeper) best = deeper
1001
+ }
1002
+
1003
+ return best
1004
+ }
1005
+
1006
+ cloneTree(
1007
+ sourceId: string,
1008
+ parentId: string,
1009
+ overrides: Partial<SceneNode> = {}
1010
+ ): SceneNode | null {
1011
+ const src = this.nodes.get(sourceId)
1012
+ if (!src) return null
1013
+
1014
+ const { id: _srcId, parentId: _srcParent, childIds: _srcChildren, ...rest } = src
1015
+ const clone = this.createNode(src.type, parentId, { ...rest, ...overrides })
1016
+
1017
+ for (const childId of src.childIds) {
1018
+ this.cloneTree(childId, clone.id)
1019
+ }
1020
+
1021
+ return clone
1022
+ }
1023
+
1024
+ private static readonly INSTANCE_SYNC_PROPS: (keyof SceneNode)[] = [
1025
+ 'width',
1026
+ 'height',
1027
+ 'fills',
1028
+ 'strokes',
1029
+ 'effects',
1030
+ 'opacity',
1031
+ 'cornerRadius',
1032
+ 'topLeftRadius',
1033
+ 'topRightRadius',
1034
+ 'bottomRightRadius',
1035
+ 'bottomLeftRadius',
1036
+ 'independentCorners',
1037
+ 'layoutMode',
1038
+ 'layoutWrap',
1039
+ 'primaryAxisAlign',
1040
+ 'counterAxisAlign',
1041
+ 'primaryAxisSizing',
1042
+ 'counterAxisSizing',
1043
+ 'itemSpacing',
1044
+ 'counterAxisSpacing',
1045
+ 'paddingTop',
1046
+ 'paddingRight',
1047
+ 'paddingBottom',
1048
+ 'paddingLeft',
1049
+ 'gridTemplateColumns',
1050
+ 'gridTemplateRows',
1051
+ 'gridColumnGap',
1052
+ 'gridRowGap',
1053
+ 'gridPosition',
1054
+ 'clipsContent',
1055
+ 'independentStrokeWeights',
1056
+ 'borderTopWeight',
1057
+ 'borderRightWeight',
1058
+ 'borderBottomWeight',
1059
+ 'borderLeftWeight'
1060
+ ]
1061
+
1062
+ private static copyProp(
1063
+ target: Partial<SceneNode> | SceneNode,
1064
+ source: SceneNode,
1065
+ key: keyof SceneNode
1066
+ ): void {
1067
+ const val = source[key]
1068
+ if (key === 'fills') {
1069
+ ;(target as Record<string, unknown>)[key] = copyFills(val as Fill[])
1070
+ } else if (key === 'strokes') {
1071
+ ;(target as Record<string, unknown>)[key] = copyStrokes(val as Stroke[])
1072
+ } else if (key === 'effects') {
1073
+ ;(target as Record<string, unknown>)[key] = copyEffects(val as Effect[])
1074
+ } else if (key === 'styleRuns') {
1075
+ ;(target as Record<string, unknown>)[key] = copyStyleRuns(val as StyleRun[])
1076
+ } else {
1077
+ ;(target as Record<string, unknown>)[key] = Array.isArray(val) ? structuredClone(val) : val
1078
+ }
1079
+ }
1080
+
1081
+ createInstance(
1082
+ componentId: string,
1083
+ parentId: string,
1084
+ overrides: Partial<SceneNode> = {}
1085
+ ): SceneNode | null {
1086
+ const component = this.nodes.get(componentId)
1087
+ if (component?.type !== 'COMPONENT') return null
1088
+
1089
+ const props: Partial<SceneNode> = { name: component.name, componentId }
1090
+ for (const key of SceneGraph.INSTANCE_SYNC_PROPS) {
1091
+ SceneGraph.copyProp(props, component, key)
1092
+ }
1093
+
1094
+ const instance = this.createNode('INSTANCE', parentId, { ...props, ...overrides })
1095
+
1096
+ this.cloneChildrenWithMapping(component.id, instance.id)
1097
+
1098
+ return instance
1099
+ }
1100
+
1101
+ populateInstanceChildren(instanceId: string, componentId: string): void {
1102
+ const instance = this.nodes.get(instanceId)
1103
+ const component = this.nodes.get(componentId)
1104
+ if (!instance || !component || instance.type !== 'INSTANCE') return
1105
+ this.cloneChildrenWithMapping(componentId, instanceId)
1106
+ }
1107
+
1108
+ private cloneChildrenWithMapping(sourceParentId: string, destParentId: string): void {
1109
+ const sourceParent = this.nodes.get(sourceParentId)
1110
+ if (!sourceParent) return
1111
+
1112
+ for (const childId of sourceParent.childIds) {
1113
+ const src = this.nodes.get(childId)
1114
+ if (!src) continue
1115
+
1116
+ const { id: _, parentId: _p, childIds: _c, ...rest } = src
1117
+ const clone = this.createNode(src.type, destParentId, {
1118
+ ...rest,
1119
+ componentId: childId
1120
+ })
1121
+
1122
+ if (src.childIds.length > 0) {
1123
+ this.cloneChildrenWithMapping(childId, clone.id)
1124
+ }
1125
+ }
1126
+ }
1127
+
1128
+ syncInstances(componentId: string): void {
1129
+ const component = this.nodes.get(componentId)
1130
+ if (component?.type !== 'COMPONENT') return
1131
+
1132
+ for (const instance of this.getInstances(componentId)) {
1133
+ // Sync instance-level props (unless overridden)
1134
+ for (const key of SceneGraph.INSTANCE_SYNC_PROPS) {
1135
+ if (key in instance.overrides) continue
1136
+ SceneGraph.copyProp(instance, component, key)
1137
+ }
1138
+
1139
+ // Sync children: match by componentId
1140
+ this.syncChildren(component.id, instance.id, instance.overrides)
1141
+ }
1142
+ }
1143
+
1144
+ private syncChildren(
1145
+ compParentId: string,
1146
+ instParentId: string,
1147
+ overrides: Record<string, unknown>
1148
+ ): void {
1149
+ const compParent = this.nodes.get(compParentId)
1150
+ const instParent = this.nodes.get(instParentId)
1151
+ if (!compParent || !instParent) return
1152
+
1153
+ const instChildMap = new Map<string, SceneNode>()
1154
+ for (const childId of instParent.childIds) {
1155
+ const child = this.nodes.get(childId)
1156
+ if (child?.componentId) instChildMap.set(child.componentId, child)
1157
+ }
1158
+
1159
+ // Add new children from component that don't exist in instance
1160
+ for (const compChildId of compParent.childIds) {
1161
+ if (!instChildMap.has(compChildId)) {
1162
+ const src = this.nodes.get(compChildId)
1163
+ if (!src) continue
1164
+ const { id: _, parentId: _p, childIds: _c, ...rest } = src
1165
+ const clone = this.createNode(src.type, instParentId, {
1166
+ ...rest,
1167
+ componentId: compChildId
1168
+ })
1169
+ if (src.childIds.length > 0) {
1170
+ this.cloneChildrenWithMapping(compChildId, clone.id)
1171
+ }
1172
+ instChildMap.set(compChildId, clone)
1173
+ }
1174
+ }
1175
+
1176
+ // Sync existing children
1177
+ for (const compChildId of compParent.childIds) {
1178
+ const compChild = this.nodes.get(compChildId)
1179
+ const instChild = instChildMap.get(compChildId)
1180
+ if (!compChild || !instChild) continue
1181
+
1182
+ for (const key of SceneGraph.INSTANCE_SYNC_PROPS) {
1183
+ const overrideKey = `${instChild.id}:${key}`
1184
+ if (overrideKey in overrides) continue
1185
+ SceneGraph.copyProp(instChild, compChild, key)
1186
+ }
1187
+
1188
+ for (const key of ['name', 'text', 'fontSize', 'fontWeight', 'fontFamily'] as const) {
1189
+ const overrideKey = `${instChild.id}:${key}`
1190
+ if (overrideKey in overrides) continue
1191
+ SceneGraph.copyProp(instChild, compChild, key)
1192
+ }
1193
+
1194
+ if (compChild.childIds.length > 0) {
1195
+ this.syncChildren(compChildId, instChild.id, overrides)
1196
+ }
1197
+ }
1198
+
1199
+ // Reorder instance children to match component order
1200
+ const compChildOrder = compParent.childIds
1201
+ instParent.childIds.sort((a, b) => {
1202
+ const nodeA = this.nodes.get(a)
1203
+ const nodeB = this.nodes.get(b)
1204
+ const idxA = nodeA?.componentId ? compChildOrder.indexOf(nodeA.componentId) : -1
1205
+ const idxB = nodeB?.componentId ? compChildOrder.indexOf(nodeB.componentId) : -1
1206
+ return idxA - idxB
1207
+ })
1208
+ }
1209
+
1210
+ detachInstance(instanceId: string): void {
1211
+ const node = this.nodes.get(instanceId)
1212
+ if (node?.type !== 'INSTANCE') return
1213
+ if (node.componentId) {
1214
+ this.instanceIndex.get(node.componentId)?.delete(instanceId)
1215
+ }
1216
+ node.type = 'FRAME'
1217
+ node.componentId = null
1218
+ node.overrides = {}
1219
+ }
1220
+
1221
+ getMainComponent(instanceId: string): SceneNode | undefined {
1222
+ const node = this.nodes.get(instanceId)
1223
+ if (!node?.componentId) return undefined
1224
+ return this.nodes.get(node.componentId)
1225
+ }
1226
+
1227
+ getInstances(componentId: string): SceneNode[] {
1228
+ const ids = this.instanceIndex.get(componentId)
1229
+ if (!ids) return []
1230
+ const instances: SceneNode[] = []
1231
+ for (const id of ids) {
1232
+ const node = this.nodes.get(id)
1233
+ if (node) instances.push(node)
1234
+ }
1235
+ return instances
1236
+ }
1237
+
1238
+ flattenTree(parentId?: string, depth = 0): Array<{ node: SceneNode; depth: number }> {
1239
+ const id = parentId ?? this.rootId
1240
+ const parent = this.nodes.get(id)
1241
+ if (!parent) return []
1242
+
1243
+ const result: Array<{ node: SceneNode; depth: number }> = []
1244
+ for (const childId of parent.childIds) {
1245
+ const child = this.nodes.get(childId)
1246
+ if (!child) continue
1247
+ result.push({ node: child, depth })
1248
+ if (child.childIds.length > 0) {
1249
+ result.push(...this.flattenTree(childId, depth + 1))
1250
+ }
1251
+ }
1252
+ return result
1253
+ }
1254
+ }