@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,1297 @@
1
+ import type {
2
+ SceneGraph,
3
+ SceneNode,
4
+ NodeType,
5
+ Fill,
6
+ Stroke,
7
+ Effect,
8
+ LayoutMode,
9
+ Variable,
10
+ VariableCollection,
11
+ VariableType,
12
+ VariableValue
13
+ } from './scene-graph'
14
+ import type { Rect, Vector } from './types'
15
+ import { copyFills, copyStrokes, copyEffects } from './copy'
16
+ import { weightToStyle, styleToWeight } from './fonts'
17
+
18
+ const MIXED = Symbol('mixed')
19
+
20
+ type FigmaFontName = { family: string; style: string }
21
+
22
+ export function computeImageHash(data: Uint8Array): string {
23
+ let h1 = 0x811c9dc5 >>> 0
24
+ let h2 = 0x811c9dc5 >>> 0
25
+ let h3 = 0x811c9dc5 >>> 0
26
+ let h4 = 0x811c9dc5 >>> 0
27
+ let h5 = 0x811c9dc5 >>> 0
28
+ for (let i = 0; i < data.length; i++) {
29
+ const b = data[i]
30
+ switch (i % 5) {
31
+ case 0: h1 ^= b; h1 = Math.imul(h1, 0x01000193) >>> 0; break
32
+ case 1: h2 ^= b; h2 = Math.imul(h2, 0x01000193) >>> 0; break
33
+ case 2: h3 ^= b; h3 = Math.imul(h3, 0x01000193) >>> 0; break
34
+ case 3: h4 ^= b; h4 = Math.imul(h4, 0x01000193) >>> 0; break
35
+ case 4: h5 ^= b; h5 = Math.imul(h5, 0x01000193) >>> 0; break
36
+ }
37
+ }
38
+ return [h1, h2, h3, h4, h5].map(h => h.toString(16).padStart(8, '0')).join('')
39
+ }
40
+
41
+ const INTERNAL_ID = Symbol('id')
42
+ const INTERNAL_GRAPH = Symbol('graph')
43
+ const INTERNAL_API = Symbol('api')
44
+
45
+ class FigmaNodeProxy {
46
+ [INTERNAL_ID]: string;
47
+ [INTERNAL_GRAPH]: SceneGraph;
48
+ [INTERNAL_API]: FigmaAPI
49
+
50
+ constructor(id: string, graph: SceneGraph, api: FigmaAPI) {
51
+ this[INTERNAL_ID] = id
52
+ this[INTERNAL_GRAPH] = graph
53
+ this[INTERNAL_API] = api
54
+ }
55
+
56
+ private _raw(): SceneNode {
57
+ const n = this[INTERNAL_GRAPH].getNode(this[INTERNAL_ID])
58
+ if (!n) throw new Error(`Node ${this[INTERNAL_ID]} has been removed`)
59
+ return n
60
+ }
61
+
62
+ get id(): string {
63
+ return this[INTERNAL_ID]
64
+ }
65
+
66
+ get type(): NodeType {
67
+ return this._raw().type
68
+ }
69
+
70
+ get name(): string {
71
+ return this._raw().name
72
+ }
73
+
74
+ set name(v: string) {
75
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { name: v })
76
+ }
77
+
78
+ get removed(): boolean {
79
+ return !this[INTERNAL_GRAPH].getNode(this[INTERNAL_ID])
80
+ }
81
+
82
+ // --- Geometry ---
83
+
84
+ get x(): number {
85
+ return this._raw().x
86
+ }
87
+
88
+ set x(v: number) {
89
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { x: v })
90
+ }
91
+
92
+ get y(): number {
93
+ return this._raw().y
94
+ }
95
+
96
+ set y(v: number) {
97
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { y: v })
98
+ }
99
+
100
+ get width(): number {
101
+ return this._raw().width
102
+ }
103
+
104
+ get height(): number {
105
+ return this._raw().height
106
+ }
107
+
108
+ get rotation(): number {
109
+ return this._raw().rotation
110
+ }
111
+
112
+ set rotation(v: number) {
113
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { rotation: v })
114
+ }
115
+
116
+ resize(width: number, height: number): void {
117
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { width, height })
118
+ }
119
+
120
+ resizeWithoutConstraints(width: number, height: number): void {
121
+ this.resize(width, height)
122
+ }
123
+
124
+ get absoluteTransform(): [[number, number, number], [number, number, number]] {
125
+ const pos = this[INTERNAL_GRAPH].getAbsolutePosition(this[INTERNAL_ID])
126
+ return [
127
+ [1, 0, pos.x],
128
+ [0, 1, pos.y]
129
+ ]
130
+ }
131
+
132
+ get absoluteBoundingBox(): Rect {
133
+ return this[INTERNAL_GRAPH].getAbsoluteBounds(this[INTERNAL_ID])
134
+ }
135
+
136
+ get absoluteRenderBounds(): Rect {
137
+ return this.absoluteBoundingBox
138
+ }
139
+
140
+ // --- Visual ---
141
+
142
+ get fills(): readonly Fill[] {
143
+ return Object.freeze(copyFills(this._raw().fills))
144
+ }
145
+
146
+ set fills(v: readonly Fill[]) {
147
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { fills: [...v] })
148
+ }
149
+
150
+ get strokes(): readonly Stroke[] {
151
+ return Object.freeze(copyStrokes(this._raw().strokes))
152
+ }
153
+
154
+ set strokes(v: readonly Stroke[]) {
155
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokes: [...v] })
156
+ }
157
+
158
+ get effects(): readonly Effect[] {
159
+ return Object.freeze(copyEffects(this._raw().effects))
160
+ }
161
+
162
+ set effects(v: readonly Effect[]) {
163
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { effects: [...v] })
164
+ }
165
+
166
+ get opacity(): number {
167
+ return this._raw().opacity
168
+ }
169
+
170
+ set opacity(v: number) {
171
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { opacity: v })
172
+ }
173
+
174
+ get visible(): boolean {
175
+ return this._raw().visible
176
+ }
177
+
178
+ set visible(v: boolean) {
179
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { visible: v })
180
+ }
181
+
182
+ get locked(): boolean {
183
+ return this._raw().locked
184
+ }
185
+
186
+ set locked(v: boolean) {
187
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { locked: v })
188
+ }
189
+
190
+ get blendMode(): string {
191
+ return this._raw().blendMode
192
+ }
193
+
194
+ set blendMode(v: string) {
195
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { blendMode: v as SceneNode['blendMode'] })
196
+ }
197
+
198
+ get clipsContent(): boolean {
199
+ return this._raw().clipsContent
200
+ }
201
+
202
+ set clipsContent(v: boolean) {
203
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { clipsContent: v })
204
+ }
205
+
206
+ // --- Corner Radius ---
207
+
208
+ get cornerRadius(): number | typeof MIXED {
209
+ const n = this._raw()
210
+ if (n.independentCorners) return MIXED
211
+ return n.cornerRadius
212
+ }
213
+
214
+ set cornerRadius(v: number | typeof MIXED) {
215
+ if (v === MIXED) return
216
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
217
+ cornerRadius: v,
218
+ topLeftRadius: v,
219
+ topRightRadius: v,
220
+ bottomRightRadius: v,
221
+ bottomLeftRadius: v,
222
+ independentCorners: false
223
+ })
224
+ }
225
+
226
+ get topLeftRadius(): number {
227
+ return this._raw().topLeftRadius
228
+ }
229
+
230
+ set topLeftRadius(v: number) {
231
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
232
+ topLeftRadius: v,
233
+ independentCorners: true
234
+ })
235
+ }
236
+
237
+ get topRightRadius(): number {
238
+ return this._raw().topRightRadius
239
+ }
240
+
241
+ set topRightRadius(v: number) {
242
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
243
+ topRightRadius: v,
244
+ independentCorners: true
245
+ })
246
+ }
247
+
248
+ get bottomLeftRadius(): number {
249
+ return this._raw().bottomLeftRadius
250
+ }
251
+
252
+ set bottomLeftRadius(v: number) {
253
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
254
+ bottomLeftRadius: v,
255
+ independentCorners: true
256
+ })
257
+ }
258
+
259
+ get bottomRightRadius(): number {
260
+ return this._raw().bottomRightRadius
261
+ }
262
+
263
+ set bottomRightRadius(v: number) {
264
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
265
+ bottomRightRadius: v,
266
+ independentCorners: true
267
+ })
268
+ }
269
+
270
+ get cornerSmoothing(): number {
271
+ return this._raw().cornerSmoothing
272
+ }
273
+
274
+ set cornerSmoothing(v: number) {
275
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { cornerSmoothing: v })
276
+ }
277
+
278
+ // --- Stroke details ---
279
+
280
+ get strokeWeight(): number {
281
+ const s = this._raw().strokes
282
+ return s.length > 0 ? s[0].weight : 0
283
+ }
284
+
285
+ set strokeWeight(v: number) {
286
+ const n = this._raw()
287
+ if (n.strokes.length > 0) {
288
+ const strokes = copyStrokes(n.strokes)
289
+ strokes[0].weight = v
290
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokes })
291
+ }
292
+ }
293
+
294
+ get strokeAlign(): string {
295
+ const s = this._raw().strokes
296
+ return s.length > 0 ? s[0].align : 'INSIDE'
297
+ }
298
+
299
+ set strokeAlign(v: string) {
300
+ const n = this._raw()
301
+ if (n.strokes.length > 0) {
302
+ const strokes = copyStrokes(n.strokes)
303
+ strokes[0].align = v as Stroke['align']
304
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokes })
305
+ }
306
+ }
307
+
308
+ get dashPattern(): readonly number[] {
309
+ return Object.freeze([...this._raw().dashPattern])
310
+ }
311
+
312
+ set dashPattern(v: readonly number[]) {
313
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { dashPattern: [...v] })
314
+ }
315
+
316
+ get strokeCap(): string {
317
+ return this._raw().strokeCap
318
+ }
319
+
320
+ set strokeCap(v: string) {
321
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokeCap: v as SceneNode['strokeCap'] })
322
+ }
323
+
324
+ get strokeJoin(): string {
325
+ return this._raw().strokeJoin
326
+ }
327
+
328
+ set strokeJoin(v: string) {
329
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokeJoin: v as SceneNode['strokeJoin'] })
330
+ }
331
+
332
+ get strokeMiterLimit(): number {
333
+ return this._raw().strokeMiterLimit
334
+ }
335
+
336
+ set strokeMiterLimit(v: number) {
337
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokeMiterLimit: v })
338
+ }
339
+
340
+ get strokeTopWeight(): number {
341
+ return this._raw().borderTopWeight
342
+ }
343
+
344
+ set strokeTopWeight(v: number) {
345
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
346
+ borderTopWeight: v,
347
+ independentStrokeWeights: true
348
+ })
349
+ }
350
+
351
+ get strokeBottomWeight(): number {
352
+ return this._raw().borderBottomWeight
353
+ }
354
+
355
+ set strokeBottomWeight(v: number) {
356
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
357
+ borderBottomWeight: v,
358
+ independentStrokeWeights: true
359
+ })
360
+ }
361
+
362
+ get strokeLeftWeight(): number {
363
+ return this._raw().borderLeftWeight
364
+ }
365
+
366
+ set strokeLeftWeight(v: number) {
367
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
368
+ borderLeftWeight: v,
369
+ independentStrokeWeights: true
370
+ })
371
+ }
372
+
373
+ get strokeRightWeight(): number {
374
+ return this._raw().borderRightWeight
375
+ }
376
+
377
+ set strokeRightWeight(v: number) {
378
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
379
+ borderRightWeight: v,
380
+ independentStrokeWeights: true
381
+ })
382
+ }
383
+
384
+ // --- Text ---
385
+
386
+ get characters(): string {
387
+ return this._raw().text
388
+ }
389
+
390
+ set characters(v: string) {
391
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { text: v })
392
+ }
393
+
394
+ get fontSize(): number {
395
+ return this._raw().fontSize
396
+ }
397
+
398
+ set fontSize(v: number) {
399
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { fontSize: v })
400
+ }
401
+
402
+ get fontName(): FigmaFontName {
403
+ const n = this._raw()
404
+ return { family: n.fontFamily, style: weightToStyle(n.fontWeight, n.italic) }
405
+ }
406
+
407
+ set fontName(v: FigmaFontName) {
408
+ const weight = styleToWeight(v.style)
409
+ const italic = v.style.toLowerCase().includes('italic')
410
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
411
+ fontFamily: v.family,
412
+ fontWeight: weight,
413
+ italic
414
+ })
415
+ }
416
+
417
+ get fontWeight(): number {
418
+ return this._raw().fontWeight
419
+ }
420
+
421
+ set fontWeight(v: number) {
422
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { fontWeight: v })
423
+ }
424
+
425
+ get textAlignHorizontal(): string {
426
+ return this._raw().textAlignHorizontal
427
+ }
428
+
429
+ set textAlignHorizontal(v: string) {
430
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
431
+ textAlignHorizontal: v as SceneNode['textAlignHorizontal']
432
+ })
433
+ }
434
+
435
+ get textAlignVertical(): string {
436
+ return this._raw().textAlignVertical
437
+ }
438
+
439
+ set textAlignVertical(v: string) {
440
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
441
+ textAlignVertical: v as SceneNode['textAlignVertical']
442
+ })
443
+ }
444
+
445
+ get textAutoResize(): string {
446
+ return this._raw().textAutoResize
447
+ }
448
+
449
+ set textAutoResize(v: string) {
450
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
451
+ textAutoResize: v as SceneNode['textAutoResize']
452
+ })
453
+ }
454
+
455
+ get letterSpacing(): number {
456
+ return this._raw().letterSpacing
457
+ }
458
+
459
+ set letterSpacing(v: number) {
460
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { letterSpacing: v })
461
+ }
462
+
463
+ get lineHeight(): number | null {
464
+ return this._raw().lineHeight
465
+ }
466
+
467
+ set lineHeight(v: number | null) {
468
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { lineHeight: v })
469
+ }
470
+
471
+ get textCase(): string {
472
+ return this._raw().textCase
473
+ }
474
+
475
+ set textCase(v: string) {
476
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { textCase: v as SceneNode['textCase'] })
477
+ }
478
+
479
+ get textDecoration(): string {
480
+ return this._raw().textDecoration
481
+ }
482
+
483
+ set textDecoration(v: string) {
484
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
485
+ textDecoration: v as SceneNode['textDecoration']
486
+ })
487
+ }
488
+
489
+ get maxLines(): number | null {
490
+ return this._raw().maxLines
491
+ }
492
+
493
+ set maxLines(v: number | null) {
494
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { maxLines: v })
495
+ }
496
+
497
+ get textTruncation(): string {
498
+ return this._raw().textTruncation
499
+ }
500
+
501
+ set textTruncation(v: string) {
502
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
503
+ textTruncation: v as SceneNode['textTruncation']
504
+ })
505
+ }
506
+
507
+ get autoRename(): boolean {
508
+ return this._raw().autoRename
509
+ }
510
+
511
+ set autoRename(v: boolean) {
512
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { autoRename: v })
513
+ }
514
+
515
+ insertCharacters(start: number, characters: string): void {
516
+ const n = this._raw()
517
+ const text = n.text.slice(0, start) + characters + n.text.slice(start)
518
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { text })
519
+ }
520
+
521
+ deleteCharacters(start: number, end: number): void {
522
+ const n = this._raw()
523
+ const text = n.text.slice(0, start) + n.text.slice(end)
524
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { text })
525
+ }
526
+
527
+ // --- Auto-layout ---
528
+
529
+ get layoutMode(): LayoutMode {
530
+ return this._raw().layoutMode
531
+ }
532
+
533
+ set layoutMode(v: LayoutMode) {
534
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { layoutMode: v })
535
+ }
536
+
537
+ get primaryAxisAlignItems(): string {
538
+ return this._raw().primaryAxisAlign
539
+ }
540
+
541
+ set primaryAxisAlignItems(v: string) {
542
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
543
+ primaryAxisAlign: v as SceneNode['primaryAxisAlign']
544
+ })
545
+ }
546
+
547
+ get counterAxisAlignItems(): string {
548
+ return this._raw().counterAxisAlign
549
+ }
550
+
551
+ set counterAxisAlignItems(v: string) {
552
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
553
+ counterAxisAlign: v as SceneNode['counterAxisAlign']
554
+ })
555
+ }
556
+
557
+ get itemSpacing(): number {
558
+ return this._raw().itemSpacing
559
+ }
560
+
561
+ set itemSpacing(v: number) {
562
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { itemSpacing: v })
563
+ }
564
+
565
+ get counterAxisSpacing(): number {
566
+ return this._raw().counterAxisSpacing
567
+ }
568
+
569
+ set counterAxisSpacing(v: number) {
570
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { counterAxisSpacing: v })
571
+ }
572
+
573
+ get paddingTop(): number {
574
+ return this._raw().paddingTop
575
+ }
576
+
577
+ set paddingTop(v: number) {
578
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { paddingTop: v })
579
+ }
580
+
581
+ get paddingRight(): number {
582
+ return this._raw().paddingRight
583
+ }
584
+
585
+ set paddingRight(v: number) {
586
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { paddingRight: v })
587
+ }
588
+
589
+ get paddingBottom(): number {
590
+ return this._raw().paddingBottom
591
+ }
592
+
593
+ set paddingBottom(v: number) {
594
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { paddingBottom: v })
595
+ }
596
+
597
+ get paddingLeft(): number {
598
+ return this._raw().paddingLeft
599
+ }
600
+
601
+ set paddingLeft(v: number) {
602
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { paddingLeft: v })
603
+ }
604
+
605
+ get layoutWrap(): string {
606
+ return this._raw().layoutWrap
607
+ }
608
+
609
+ set layoutWrap(v: string) {
610
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { layoutWrap: v as SceneNode['layoutWrap'] })
611
+ }
612
+
613
+ get primaryAxisSizingMode(): string {
614
+ return this._raw().primaryAxisSizing === 'HUG' ? 'AUTO' : this._raw().primaryAxisSizing
615
+ }
616
+
617
+ set primaryAxisSizingMode(v: string) {
618
+ const mapped = v === 'AUTO' ? 'HUG' : v
619
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
620
+ primaryAxisSizing: mapped as SceneNode['primaryAxisSizing']
621
+ })
622
+ }
623
+
624
+ get counterAxisSizingMode(): string {
625
+ return this._raw().counterAxisSizing === 'HUG' ? 'AUTO' : this._raw().counterAxisSizing
626
+ }
627
+
628
+ set counterAxisSizingMode(v: string) {
629
+ const mapped = v === 'AUTO' ? 'HUG' : v
630
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
631
+ counterAxisSizing: mapped as SceneNode['counterAxisSizing']
632
+ })
633
+ }
634
+
635
+ get counterAxisAlignContent(): string {
636
+ return this._raw().counterAxisAlignContent
637
+ }
638
+
639
+ set counterAxisAlignContent(v: string) {
640
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
641
+ counterAxisAlignContent: v as SceneNode['counterAxisAlignContent']
642
+ })
643
+ }
644
+
645
+ get itemReverseZIndex(): boolean {
646
+ return this._raw().itemReverseZIndex
647
+ }
648
+
649
+ set itemReverseZIndex(v: boolean) {
650
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { itemReverseZIndex: v })
651
+ }
652
+
653
+ get strokesIncludedInLayout(): boolean {
654
+ return this._raw().strokesIncludedInLayout
655
+ }
656
+
657
+ set strokesIncludedInLayout(v: boolean) {
658
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { strokesIncludedInLayout: v })
659
+ }
660
+
661
+ // --- Layout child props ---
662
+
663
+ get layoutPositioning(): string {
664
+ return this._raw().layoutPositioning
665
+ }
666
+
667
+ set layoutPositioning(v: string) {
668
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
669
+ layoutPositioning: v as SceneNode['layoutPositioning']
670
+ })
671
+ }
672
+
673
+ get layoutGrow(): number {
674
+ return this._raw().layoutGrow
675
+ }
676
+
677
+ set layoutGrow(v: number) {
678
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { layoutGrow: v })
679
+ }
680
+
681
+ get layoutAlign(): string {
682
+ const n = this._raw()
683
+ if (n.layoutAlignSelf === 'AUTO') return 'INHERIT'
684
+ return n.layoutAlignSelf
685
+ }
686
+
687
+ set layoutAlign(v: string) {
688
+ const mapped = v === 'INHERIT' ? 'AUTO' : v
689
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
690
+ layoutAlignSelf: mapped as SceneNode['layoutAlignSelf']
691
+ })
692
+ }
693
+
694
+ get layoutSizingHorizontal(): string {
695
+ const n = this._raw()
696
+ const parent = n.parentId ? this[INTERNAL_GRAPH].getNode(n.parentId) : undefined
697
+ const refLayout = parent && parent.layoutMode !== 'NONE' ? parent.layoutMode : n.layoutMode
698
+ if (refLayout === 'NONE') return 'FIXED'
699
+ return refLayout === 'HORIZONTAL' ? n.primaryAxisSizing : n.counterAxisSizing
700
+ }
701
+
702
+ set layoutSizingHorizontal(v: string) {
703
+ const n = this._raw()
704
+ const parent = n.parentId ? this[INTERNAL_GRAPH].getNode(n.parentId) : undefined
705
+ const refLayout = parent && parent.layoutMode !== 'NONE' ? parent.layoutMode : n.layoutMode
706
+ if (refLayout === 'VERTICAL') {
707
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
708
+ counterAxisSizing: v as SceneNode['counterAxisSizing']
709
+ })
710
+ } else {
711
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
712
+ primaryAxisSizing: v as SceneNode['primaryAxisSizing']
713
+ })
714
+ }
715
+ }
716
+
717
+ get layoutSizingVertical(): string {
718
+ const n = this._raw()
719
+ const parent = n.parentId ? this[INTERNAL_GRAPH].getNode(n.parentId) : undefined
720
+ const refLayout = parent && parent.layoutMode !== 'NONE' ? parent.layoutMode : n.layoutMode
721
+ if (refLayout === 'NONE') return 'FIXED'
722
+ return refLayout === 'VERTICAL' ? n.primaryAxisSizing : n.counterAxisSizing
723
+ }
724
+
725
+ set layoutSizingVertical(v: string) {
726
+ const n = this._raw()
727
+ const parent = n.parentId ? this[INTERNAL_GRAPH].getNode(n.parentId) : undefined
728
+ const refLayout = parent && parent.layoutMode !== 'NONE' ? parent.layoutMode : n.layoutMode
729
+ if (refLayout === 'HORIZONTAL') {
730
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
731
+ counterAxisSizing: v as SceneNode['counterAxisSizing']
732
+ })
733
+ } else {
734
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
735
+ primaryAxisSizing: v as SceneNode['primaryAxisSizing']
736
+ })
737
+ }
738
+ }
739
+
740
+ // --- Constraints ---
741
+
742
+ get constraints(): { horizontal: string; vertical: string } {
743
+ const n = this._raw()
744
+ return { horizontal: n.horizontalConstraint, vertical: n.verticalConstraint }
745
+ }
746
+
747
+ set constraints(v: { horizontal: string; vertical: string }) {
748
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], {
749
+ horizontalConstraint: v.horizontal as SceneNode['horizontalConstraint'],
750
+ verticalConstraint: v.vertical as SceneNode['verticalConstraint']
751
+ })
752
+ }
753
+
754
+ // --- Dimension constraints ---
755
+
756
+ get minWidth(): number | null {
757
+ return this._raw().minWidth
758
+ }
759
+
760
+ set minWidth(v: number | null) {
761
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { minWidth: v })
762
+ }
763
+
764
+ get maxWidth(): number | null {
765
+ return this._raw().maxWidth
766
+ }
767
+
768
+ set maxWidth(v: number | null) {
769
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { maxWidth: v })
770
+ }
771
+
772
+ get minHeight(): number | null {
773
+ return this._raw().minHeight
774
+ }
775
+
776
+ set minHeight(v: number | null) {
777
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { minHeight: v })
778
+ }
779
+
780
+ get maxHeight(): number | null {
781
+ return this._raw().maxHeight
782
+ }
783
+
784
+ set maxHeight(v: number | null) {
785
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { maxHeight: v })
786
+ }
787
+
788
+ // --- Mask ---
789
+
790
+ get isMask(): boolean {
791
+ return this._raw().isMask
792
+ }
793
+
794
+ set isMask(v: boolean) {
795
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { isMask: v })
796
+ }
797
+
798
+ get maskType(): string {
799
+ return this._raw().maskType
800
+ }
801
+
802
+ set maskType(v: string) {
803
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { maskType: v as SceneNode['maskType'] })
804
+ }
805
+
806
+ // --- UI state ---
807
+
808
+ get expanded(): boolean {
809
+ return this._raw().expanded
810
+ }
811
+
812
+ set expanded(v: boolean) {
813
+ this[INTERNAL_GRAPH].updateNode(this[INTERNAL_ID], { expanded: v })
814
+ }
815
+
816
+ // --- Components ---
817
+
818
+ get mainComponent(): FigmaNodeProxy | null {
819
+ const n = this._raw()
820
+ if (!n.componentId) return null
821
+ const comp = this[INTERNAL_GRAPH].getNode(n.componentId)
822
+ if (!comp) return null
823
+ return this[INTERNAL_API].wrapNode(comp.id)
824
+ }
825
+
826
+ createInstance(): FigmaNodeProxy {
827
+ const n = this._raw()
828
+ if (n.type !== 'COMPONENT') throw new Error('createInstance() can only be called on components')
829
+ const pageId = this[INTERNAL_API].currentPageId
830
+ const inst = this[INTERNAL_GRAPH].createInstance(n.id, pageId)
831
+ if (!inst) throw new Error('Failed to create instance')
832
+ return this[INTERNAL_API].wrapNode(inst.id)
833
+ }
834
+
835
+ // --- Tree ---
836
+
837
+ get parent(): FigmaNodeProxy | null {
838
+ const n = this._raw()
839
+ if (!n.parentId) return null
840
+ return this[INTERNAL_API].wrapNode(n.parentId)
841
+ }
842
+
843
+ get children(): FigmaNodeProxy[] {
844
+ return this[INTERNAL_GRAPH]
845
+ .getChildren(this[INTERNAL_ID])
846
+ .map((c) => this[INTERNAL_API].wrapNode(c.id))
847
+ }
848
+
849
+ appendChild(child: FigmaNodeProxy): void {
850
+ this[INTERNAL_GRAPH].reparentNode(child[INTERNAL_ID], this[INTERNAL_ID])
851
+ }
852
+
853
+ insertChild(index: number, child: FigmaNodeProxy): void {
854
+ this[INTERNAL_GRAPH].reparentNode(child[INTERNAL_ID], this[INTERNAL_ID])
855
+ this[INTERNAL_GRAPH].reorderChild(child[INTERNAL_ID], this[INTERNAL_ID], index)
856
+ }
857
+
858
+ clone(): FigmaNodeProxy {
859
+ const n = this._raw()
860
+ const parentId = n.parentId ?? this[INTERNAL_API].currentPageId
861
+ const cloned = this[INTERNAL_GRAPH].cloneTree(this[INTERNAL_ID], parentId)
862
+ if (!cloned) throw new Error(`Failed to clone node ${this[INTERNAL_ID]}`)
863
+ return this[INTERNAL_API].wrapNode(cloned.id)
864
+ }
865
+
866
+ remove(): void {
867
+ this[INTERNAL_GRAPH].deleteNode(this[INTERNAL_ID])
868
+ }
869
+
870
+ findAll(callback?: (node: FigmaNodeProxy) => boolean): FigmaNodeProxy[] {
871
+ const results: FigmaNodeProxy[] = []
872
+ const walk = (id: string) => {
873
+ for (const child of this[INTERNAL_GRAPH].getChildren(id)) {
874
+ const proxy = this[INTERNAL_API].wrapNode(child.id)
875
+ if (!callback || callback(proxy)) results.push(proxy)
876
+ walk(child.id)
877
+ }
878
+ }
879
+ walk(this[INTERNAL_ID])
880
+ return results
881
+ }
882
+
883
+ findOne(callback: (node: FigmaNodeProxy) => boolean): FigmaNodeProxy | null {
884
+ const walk = (id: string): FigmaNodeProxy | null => {
885
+ for (const child of this[INTERNAL_GRAPH].getChildren(id)) {
886
+ const proxy = this[INTERNAL_API].wrapNode(child.id)
887
+ if (callback(proxy)) return proxy
888
+ const found = walk(child.id)
889
+ if (found) return found
890
+ }
891
+ return null
892
+ }
893
+ return walk(this[INTERNAL_ID])
894
+ }
895
+
896
+ findChild(callback: (node: FigmaNodeProxy) => boolean): FigmaNodeProxy | null {
897
+ for (const child of this[INTERNAL_GRAPH].getChildren(this[INTERNAL_ID])) {
898
+ const proxy = this[INTERNAL_API].wrapNode(child.id)
899
+ if (callback(proxy)) return proxy
900
+ }
901
+ return null
902
+ }
903
+
904
+ findChildren(callback?: (node: FigmaNodeProxy) => boolean): FigmaNodeProxy[] {
905
+ return this[INTERNAL_GRAPH]
906
+ .getChildren(this[INTERNAL_ID])
907
+ .map((c) => this[INTERNAL_API].wrapNode(c.id))
908
+ .filter((proxy) => !callback || callback(proxy))
909
+ }
910
+
911
+ findAllWithCriteria(criteria: { types?: string[] }): FigmaNodeProxy[] {
912
+ const types = criteria.types ? new Set(criteria.types) : null
913
+ return this.findAll((node) => !types || types.has(node.type))
914
+ }
915
+
916
+ // --- Serialization ---
917
+
918
+ toJSON(): Record<string, unknown> {
919
+ const n = this._raw()
920
+ const obj: Record<string, unknown> = {
921
+ id: n.id,
922
+ type: n.type,
923
+ name: n.name,
924
+ x: n.x,
925
+ y: n.y,
926
+ width: n.width,
927
+ height: n.height
928
+ }
929
+ if (n.fills.length > 0) obj.fills = n.fills
930
+ if (n.strokes.length > 0) obj.strokes = n.strokes
931
+ if (n.effects.length > 0) obj.effects = n.effects
932
+ if (n.opacity !== 1) obj.opacity = n.opacity
933
+ if (n.cornerRadius > 0) obj.cornerRadius = n.cornerRadius
934
+ if (!n.visible) obj.visible = false
935
+ if (n.text) obj.characters = n.text
936
+ if (n.layoutMode !== 'NONE') {
937
+ obj.layoutMode = n.layoutMode
938
+ obj.itemSpacing = n.itemSpacing
939
+ }
940
+ const children = this[INTERNAL_GRAPH].getChildren(this[INTERNAL_ID])
941
+ if (children.length > 0) {
942
+ obj.children = children.map((c) => this[INTERNAL_API].wrapNode(c.id).toJSON())
943
+ }
944
+ return obj
945
+ }
946
+
947
+ toString(): string {
948
+ const n = this._raw()
949
+ return `[${n.type} "${n.name}" ${n.id}]`
950
+ }
951
+
952
+ [Symbol.for('nodejs.util.inspect.custom')](): string {
953
+ return this.toString()
954
+ }
955
+ }
956
+
957
+ export class FigmaAPI {
958
+ readonly graph: SceneGraph
959
+ private _currentPageId: string
960
+ private _selection: FigmaNodeProxy[] = []
961
+ private _nodeCache = new Map<string, FigmaNodeProxy>()
962
+ private _pageProxies = new WeakSet<FigmaNodeProxy>()
963
+
964
+ readonly mixed = MIXED
965
+
966
+ constructor(graph: SceneGraph) {
967
+ this.graph = graph
968
+ const pages = graph.getPages()
969
+ this._currentPageId = pages[0]?.id ?? graph.rootId
970
+ }
971
+
972
+ get currentPageId(): string {
973
+ return this._currentPageId
974
+ }
975
+
976
+ wrapNode(id: string): FigmaNodeProxy {
977
+ let proxy = this._nodeCache.get(id)
978
+ if (!proxy) {
979
+ proxy = new FigmaNodeProxy(id, this.graph, this)
980
+ this._nodeCache.set(id, proxy)
981
+ }
982
+ return proxy
983
+ }
984
+
985
+ private _ensurePageProxy(
986
+ proxy: FigmaNodeProxy
987
+ ): FigmaNodeProxy & { selection: FigmaNodeProxy[] } {
988
+ if (!this._pageProxies.has(proxy)) {
989
+ Object.defineProperty(proxy, 'selection', {
990
+ get: () => this._selection,
991
+ set: (nodes: FigmaNodeProxy[]) => {
992
+ this._selection = nodes
993
+ },
994
+ enumerable: true,
995
+ configurable: true
996
+ })
997
+ this._pageProxies.add(proxy)
998
+ }
999
+ return proxy as FigmaNodeProxy & { selection: FigmaNodeProxy[] }
1000
+ }
1001
+
1002
+ get root(): FigmaNodeProxy {
1003
+ return this.wrapNode(this.graph.rootId)
1004
+ }
1005
+
1006
+ get currentPage(): FigmaNodeProxy & { selection: FigmaNodeProxy[] } {
1007
+ return this._ensurePageProxy(this.wrapNode(this._currentPageId))
1008
+ }
1009
+
1010
+ set currentPage(page: FigmaNodeProxy) {
1011
+ this._currentPageId = page[INTERNAL_ID]
1012
+ }
1013
+
1014
+ getNodeById(id: string): FigmaNodeProxy | null {
1015
+ const node = this.graph.getNode(id)
1016
+ return node ? this.wrapNode(id) : null
1017
+ }
1018
+
1019
+ // --- Node Creation ---
1020
+
1021
+ private _createNode(type: NodeType): FigmaNodeProxy {
1022
+ const node = this.graph.createNode(type, this._currentPageId)
1023
+ return this.wrapNode(node.id)
1024
+ }
1025
+
1026
+ createFrame(): FigmaNodeProxy {
1027
+ return this._createNode('FRAME')
1028
+ }
1029
+
1030
+ createRectangle(): FigmaNodeProxy {
1031
+ return this._createNode('RECTANGLE')
1032
+ }
1033
+
1034
+ createEllipse(): FigmaNodeProxy {
1035
+ return this._createNode('ELLIPSE')
1036
+ }
1037
+
1038
+ createText(): FigmaNodeProxy {
1039
+ return this._createNode('TEXT')
1040
+ }
1041
+
1042
+ createLine(): FigmaNodeProxy {
1043
+ return this._createNode('LINE')
1044
+ }
1045
+
1046
+ createPolygon(): FigmaNodeProxy {
1047
+ return this._createNode('POLYGON')
1048
+ }
1049
+
1050
+ createStar(): FigmaNodeProxy {
1051
+ return this._createNode('STAR')
1052
+ }
1053
+
1054
+ createVector(): FigmaNodeProxy {
1055
+ return this._createNode('VECTOR')
1056
+ }
1057
+
1058
+ createComponent(): FigmaNodeProxy {
1059
+ return this._createNode('COMPONENT')
1060
+ }
1061
+
1062
+ createSection(): FigmaNodeProxy {
1063
+ return this._createNode('SECTION')
1064
+ }
1065
+
1066
+ createPage(): FigmaNodeProxy {
1067
+ const page = this.graph.addPage('Page')
1068
+ return this.wrapNode(page.id)
1069
+ }
1070
+
1071
+ // --- Grouping ---
1072
+
1073
+ group(nodes: FigmaNodeProxy[], parent: FigmaNodeProxy): FigmaNodeProxy {
1074
+ const groupNode = this.graph.createNode('GROUP', parent[INTERNAL_ID])
1075
+ for (const n of nodes) {
1076
+ this.graph.reparentNode(n[INTERNAL_ID], groupNode.id)
1077
+ }
1078
+ return this.wrapNode(groupNode.id)
1079
+ }
1080
+
1081
+ ungroup(node: FigmaNodeProxy): void {
1082
+ const raw = this.graph.getNode(node[INTERNAL_ID])
1083
+ if (raw?.type !== 'GROUP') return
1084
+ const parentId = raw.parentId ?? this._currentPageId
1085
+ for (const childId of Array.from(raw.childIds)) {
1086
+ this.graph.reparentNode(childId, parentId)
1087
+ }
1088
+ this.graph.deleteNode(node[INTERNAL_ID])
1089
+ }
1090
+
1091
+ createComponentFromNode(node: FigmaNodeProxy): FigmaNodeProxy {
1092
+ const raw = this.graph.getNode(node[INTERNAL_ID])
1093
+ if (!raw) throw new Error('Node not found')
1094
+ const parentId = raw.parentId ?? this._currentPageId
1095
+ const comp = this.graph.createNode('COMPONENT', parentId)
1096
+ this.graph.updateNode(comp.id, {
1097
+ name: raw.name,
1098
+ width: raw.width,
1099
+ height: raw.height,
1100
+ x: raw.x,
1101
+ y: raw.y,
1102
+ fills: copyFills(raw.fills),
1103
+ strokes: copyStrokes(raw.strokes),
1104
+ effects: copyEffects(raw.effects),
1105
+ cornerRadius: raw.cornerRadius,
1106
+ topLeftRadius: raw.topLeftRadius,
1107
+ topRightRadius: raw.topRightRadius,
1108
+ bottomRightRadius: raw.bottomRightRadius,
1109
+ bottomLeftRadius: raw.bottomLeftRadius,
1110
+ independentCorners: raw.independentCorners,
1111
+ opacity: raw.opacity,
1112
+ layoutMode: raw.layoutMode,
1113
+ primaryAxisAlign: raw.primaryAxisAlign,
1114
+ counterAxisAlign: raw.counterAxisAlign,
1115
+ itemSpacing: raw.itemSpacing,
1116
+ paddingTop: raw.paddingTop,
1117
+ paddingRight: raw.paddingRight,
1118
+ paddingBottom: raw.paddingBottom,
1119
+ paddingLeft: raw.paddingLeft
1120
+ })
1121
+ for (const childId of raw.childIds) {
1122
+ this.graph.cloneTree(childId, comp.id)
1123
+ }
1124
+ this.graph.deleteNode(node[INTERNAL_ID])
1125
+ return this.wrapNode(comp.id)
1126
+ }
1127
+
1128
+ // --- Variables ---
1129
+
1130
+ getVariableById(id: string): Variable | null {
1131
+ return this.graph.variables.get(id) ?? null
1132
+ }
1133
+
1134
+ getLocalVariables(type?: string): Variable[] {
1135
+ const vars = [...this.graph.variables.values()]
1136
+ if (type) return vars.filter((v) => v.type === type)
1137
+ return vars
1138
+ }
1139
+
1140
+ getLocalVariableCollections(): VariableCollection[] {
1141
+ return [...this.graph.variableCollections.values()]
1142
+ }
1143
+
1144
+ getVariableCollectionById(id: string): VariableCollection | null {
1145
+ return this.graph.variableCollections.get(id) ?? null
1146
+ }
1147
+
1148
+ // --- Variable/Collection CRUD ---
1149
+
1150
+ createVariable(
1151
+ name: string,
1152
+ type: VariableType,
1153
+ collectionId: string,
1154
+ value?: VariableValue
1155
+ ): Variable {
1156
+ return this.graph.createVariable(name, type, collectionId, value)
1157
+ }
1158
+
1159
+ setVariableValue(variableId: string, modeId: string, value: VariableValue): void {
1160
+ const variable = this.graph.variables.get(variableId)
1161
+ if (!variable) throw new Error(`Variable "${variableId}" not found`)
1162
+ variable.valuesByMode[modeId] = value
1163
+ }
1164
+
1165
+ deleteVariable(id: string): void {
1166
+ this.graph.removeVariable(id)
1167
+ }
1168
+
1169
+ createVariableCollection(name: string): VariableCollection {
1170
+ return this.graph.createCollection(name)
1171
+ }
1172
+
1173
+ deleteVariableCollection(id: string): void {
1174
+ this.graph.removeCollection(id)
1175
+ }
1176
+
1177
+ bindVariable(nodeId: string, field: string, variableId: string): void {
1178
+ this.graph.bindVariable(nodeId, field, variableId)
1179
+ }
1180
+
1181
+ unbindVariable(nodeId: string, field: string): void {
1182
+ this.graph.unbindVariable(nodeId, field)
1183
+ }
1184
+
1185
+ // --- Boolean Operations ---
1186
+
1187
+ booleanOperation(
1188
+ operation: 'UNION' | 'SUBTRACT' | 'INTERSECT' | 'EXCLUDE',
1189
+ nodeIds: string[]
1190
+ ): FigmaNodeProxy {
1191
+ if (nodeIds.length < 2) throw new Error('Need at least 2 nodes for boolean operation')
1192
+ const nodes = nodeIds.map((id) => this.graph.getNode(id))
1193
+ const first = nodes[0]
1194
+ if (!first || nodes.some((n) => !n)) throw new Error('One or more nodes not found')
1195
+ const parentId = first.parentId ?? this._currentPageId
1196
+ const group = this.graph.createNode('GROUP', parentId, {
1197
+ name: `Boolean ${operation.toLowerCase()}`,
1198
+ x: first.x,
1199
+ y: first.y,
1200
+ width: first.width,
1201
+ height: first.height
1202
+ })
1203
+ for (const id of nodeIds) {
1204
+ this.graph.reparentNode(id, group.id)
1205
+ }
1206
+ return this.wrapNode(group.id)
1207
+ }
1208
+
1209
+ // --- Flatten ---
1210
+
1211
+ flattenNode(nodeIds: string[]): FigmaNodeProxy {
1212
+ if (nodeIds.length === 0) throw new Error('Need at least 1 node to flatten')
1213
+ const first = this.graph.getNode(nodeIds[0])
1214
+ if (!first) throw new Error('Node not found')
1215
+ const parentId = first.parentId ?? this._currentPageId
1216
+ const vector = this.graph.createNode('VECTOR', parentId, {
1217
+ name: 'Flatten',
1218
+ x: first.x,
1219
+ y: first.y,
1220
+ width: first.width,
1221
+ height: first.height,
1222
+ fills: copyFills(first.fills)
1223
+ })
1224
+ for (const id of nodeIds) {
1225
+ this.graph.deleteNode(id)
1226
+ }
1227
+ return this.wrapNode(vector.id)
1228
+ }
1229
+
1230
+ // --- Viewport ---
1231
+
1232
+ private _viewport = { x: 0, y: 0, zoom: 1 }
1233
+
1234
+ get viewport(): { center: Vector; zoom: number; scrollAndZoomIntoView: (nodes: readonly { absoluteBoundingBox: Rect }[]) => void } {
1235
+ return {
1236
+ center: { x: this._viewport.x, y: this._viewport.y },
1237
+ zoom: this._viewport.zoom,
1238
+ scrollAndZoomIntoView: (nodes) => {
1239
+ let minX = Infinity
1240
+ let minY = Infinity
1241
+ let maxX = -Infinity
1242
+ let maxY = -Infinity
1243
+ for (const node of nodes) {
1244
+ const b = node.absoluteBoundingBox
1245
+ minX = Math.min(minX, b.x)
1246
+ minY = Math.min(minY, b.y)
1247
+ maxX = Math.max(maxX, b.x + b.width)
1248
+ maxY = Math.max(maxY, b.y + b.height)
1249
+ }
1250
+ if (minX === Infinity) return
1251
+
1252
+ const padding = 80
1253
+ const contentW = maxX - minX + padding * 2
1254
+ const contentH = maxY - minY + padding * 2
1255
+ const viewW = typeof window !== 'undefined' ? window.innerWidth : 1280
1256
+ const viewH = typeof window !== 'undefined' ? window.innerHeight : 720
1257
+ const zoom = Math.min(viewW / contentW, viewH / contentH, 1)
1258
+ this._viewport = { x: (minX + maxX) / 2, y: (minY + maxY) / 2, zoom }
1259
+ }
1260
+ }
1261
+ }
1262
+
1263
+ set viewport(v: { center: Vector; zoom: number }) {
1264
+ this._viewport = { x: v.center.x, y: v.center.y, zoom: v.zoom }
1265
+ }
1266
+
1267
+ createImage(data: Uint8Array): { hash: string } {
1268
+ const hash = computeImageHash(data)
1269
+ this.graph.images.set(hash, data)
1270
+ return { hash }
1271
+ }
1272
+
1273
+ // --- Stubs ---
1274
+
1275
+ async loadFontAsync(_fontName: FigmaFontName): Promise<void> {
1276
+ // No-op: we don't gate text editing on font loading
1277
+ }
1278
+
1279
+ notify(message: string): { cancel: () => void } {
1280
+ if (typeof console !== 'undefined') console.log(`[figma.notify] ${message}`)
1281
+ // eslint-disable-next-line no-empty-function
1282
+ return { cancel() {} }
1283
+ }
1284
+
1285
+ // eslint-disable-next-line no-empty-function
1286
+ commitUndo(): void {}
1287
+ // eslint-disable-next-line no-empty-function
1288
+ triggerUndo(): void {}
1289
+
1290
+ exportImage?: (
1291
+ nodeIds: string[],
1292
+ options: { scale?: number; format?: 'PNG' | 'JPG' | 'WEBP'; quality?: number }
1293
+ ) => Promise<Uint8Array | null>
1294
+ }
1295
+
1296
+ export { FigmaNodeProxy }
1297
+ export type { FigmaFontName }