@pixerate/editor 0.24.0 → 0.25.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 (133) hide show
  1. package/README.md +71 -1
  2. package/THIRD_PARTY_NOTICES.md +29 -0
  3. package/dist/agent/index.cjs +10 -9
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +1 -1
  6. package/dist/agent/index.d.ts +1 -1
  7. package/dist/agent/index.js +4 -3
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +3173 -340
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +617 -4
  12. package/dist/canvas/index.d.ts +617 -4
  13. package/dist/canvas/index.js +3105 -272
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  16. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  17. package/dist/{chunk-XNORY7M6.cjs → chunk-7WISYZFO.cjs} +105 -50
  18. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  19. package/dist/{chunk-OLZJXL4R.cjs → chunk-AL6JMGWW.cjs} +59 -25
  20. package/dist/chunk-AL6JMGWW.cjs.map +1 -0
  21. package/dist/chunk-BVP3TSLH.cjs +69 -0
  22. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  23. package/dist/{chunk-MCSJFWAO.cjs → chunk-C7P22VIP.cjs} +670 -96
  24. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  25. package/dist/{chunk-PGNZKFQS.js → chunk-ECCCYKGL.js} +76 -21
  26. package/dist/chunk-ECCCYKGL.js.map +1 -0
  27. package/dist/{chunk-3CN2WD3X.js → chunk-FH6F67DN.js} +646 -72
  28. package/dist/chunk-FH6F67DN.js.map +1 -0
  29. package/dist/chunk-HRMEDT2L.js +1406 -0
  30. package/dist/chunk-HRMEDT2L.js.map +1 -0
  31. package/dist/{chunk-ITBSKI26.js → chunk-IXHSYOVY.js} +941 -9
  32. package/dist/chunk-IXHSYOVY.js.map +1 -0
  33. package/dist/{chunk-FODNLX5N.js → chunk-NAXVVS5I.js} +38 -4
  34. package/dist/chunk-NAXVVS5I.js.map +1 -0
  35. package/dist/chunk-O5RYH2PG.js +69 -0
  36. package/dist/chunk-O5RYH2PG.js.map +1 -0
  37. package/dist/{chunk-SBCZ6RN5.cjs → chunk-PO6A6QQH.cjs} +1039 -107
  38. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  39. package/dist/{chunk-3LV6RRPH.js → chunk-QGYDWPRM.js} +2 -2
  40. package/dist/{chunk-U5KWZU4X.js → chunk-RR7F5GUM.js} +2632 -129
  41. package/dist/chunk-RR7F5GUM.js.map +1 -0
  42. package/dist/{chunk-5S3Z4PPF.js → chunk-UAKDG62Z.js} +232 -25
  43. package/dist/chunk-UAKDG62Z.js.map +1 -0
  44. package/dist/{chunk-SML64VJG.cjs → chunk-V3CHHWOE.cjs} +4 -4
  45. package/dist/{chunk-SML64VJG.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  46. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  47. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  48. package/dist/{chunk-D27YFC46.cjs → chunk-X4N4WUKB.cjs} +267 -60
  49. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  50. package/dist/commands/index.cjs +74 -1
  51. package/dist/commands/index.cjs.map +1 -1
  52. package/dist/commands/index.d.cts +34 -1
  53. package/dist/commands/index.d.ts +34 -1
  54. package/dist/commands/index.js +73 -0
  55. package/dist/commands/index.js.map +1 -1
  56. package/dist/compare/index.cjs +3 -3
  57. package/dist/compare/index.d.cts +1 -1
  58. package/dist/compare/index.d.ts +1 -1
  59. package/dist/compare/index.js +2 -2
  60. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  61. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  62. package/dist/{documents-BQjzJNe2.d.ts → documents-CHT1U5Vs.d.ts} +10 -0
  63. package/dist/{documents-KRTPhvst.d.cts → documents-Gp7wb0Me.d.cts} +10 -0
  64. package/dist/extensions/index.cjs +53 -6
  65. package/dist/extensions/index.cjs.map +1 -1
  66. package/dist/extensions/index.d.cts +12 -4
  67. package/dist/extensions/index.d.ts +12 -4
  68. package/dist/extensions/index.js +53 -6
  69. package/dist/generation/index.cjs +1013 -69
  70. package/dist/generation/index.cjs.map +1 -1
  71. package/dist/generation/index.d.cts +474 -13
  72. package/dist/generation/index.d.ts +474 -13
  73. package/dist/generation/index.js +976 -32
  74. package/dist/generation/index.js.map +1 -1
  75. package/dist/image-editor/index.cjs +3385 -1275
  76. package/dist/image-editor/index.cjs.map +1 -1
  77. package/dist/image-editor/index.d.cts +693 -128
  78. package/dist/image-editor/index.d.ts +693 -128
  79. package/dist/image-editor/index.js +3325 -1215
  80. package/dist/image-editor/index.js.map +1 -1
  81. package/dist/{index-fJrlkJCd.d.ts → index-7a6bHTLy.d.ts} +494 -5
  82. package/dist/{index-BhJBSm2d.d.cts → index-Csd5qI8u.d.cts} +494 -5
  83. package/dist/index.cjs +1491 -45
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.d.cts +110 -19
  86. package/dist/index.d.ts +110 -19
  87. package/dist/index.js +1464 -18
  88. package/dist/index.js.map +1 -1
  89. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  90. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  91. package/dist/model-C1zApQM8.d.cts +219 -0
  92. package/dist/model-C1zApQM8.d.ts +219 -0
  93. package/dist/persistence/index.d.cts +3 -120
  94. package/dist/persistence/index.d.ts +3 -120
  95. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.cts} +12 -1
  96. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.ts} +12 -1
  97. package/dist/serializers/index.cjs +12 -4
  98. package/dist/serializers/index.cjs.map +1 -1
  99. package/dist/serializers/index.d.cts +39 -12
  100. package/dist/serializers/index.d.ts +39 -12
  101. package/dist/serializers/index.js +12 -4
  102. package/dist/spreadsheet/index.cjs +127 -3
  103. package/dist/spreadsheet/index.cjs.map +1 -1
  104. package/dist/spreadsheet/index.d.cts +431 -11
  105. package/dist/spreadsheet/index.d.ts +431 -11
  106. package/dist/spreadsheet/index.js +127 -3
  107. package/dist/storage-BpFfspHm.d.cts +120 -0
  108. package/dist/storage-BpFfspHm.d.ts +120 -0
  109. package/dist/tokens/index.cjs +115 -0
  110. package/dist/tokens/index.cjs.map +1 -0
  111. package/dist/tokens/index.d.cts +212 -0
  112. package/dist/tokens/index.d.ts +212 -0
  113. package/dist/tokens/index.js +115 -0
  114. package/dist/tokens/index.js.map +1 -0
  115. package/dist/{track-changes-BK5sVf4_.d.cts → track-changes-D_VAPELi.d.cts} +95 -11
  116. package/dist/{track-changes-CDjCrwKe.d.ts → track-changes-Db_fGEvx.d.ts} +95 -11
  117. package/package.json +6 -1
  118. package/dist/chunk-3CN2WD3X.js.map +0 -1
  119. package/dist/chunk-5S3Z4PPF.js.map +0 -1
  120. package/dist/chunk-D27YFC46.cjs.map +0 -1
  121. package/dist/chunk-FODNLX5N.js.map +0 -1
  122. package/dist/chunk-ITBSKI26.js.map +0 -1
  123. package/dist/chunk-MA2F5KHF.cjs +0 -3223
  124. package/dist/chunk-MA2F5KHF.cjs.map +0 -1
  125. package/dist/chunk-MCSJFWAO.cjs.map +0 -1
  126. package/dist/chunk-OLZJXL4R.cjs.map +0 -1
  127. package/dist/chunk-PGNZKFQS.js.map +0 -1
  128. package/dist/chunk-SBCZ6RN5.cjs.map +0 -1
  129. package/dist/chunk-U5KWZU4X.js.map +0 -1
  130. package/dist/chunk-XNORY7M6.cjs.map +0 -1
  131. package/dist/controller-BhLLAoi8.d.cts +0 -1000
  132. package/dist/controller-BhLLAoi8.d.ts +0 -1000
  133. /package/dist/{chunk-3LV6RRPH.js.map → chunk-QGYDWPRM.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","../../src/canvas/clock.ts","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts","../../src/canvas/arrange.ts","../../src/canvas/guides.ts","../../src/canvas/connectors.ts","../../src/canvas/scenes.ts","../../src/canvas/polygonBoolean.ts","../../src/canvas/curveFit.ts","../../src/canvas/shapeGeometry.ts","../../src/canvas/shapeNodes.ts","../../src/canvas/resizeSnap.ts","../../src/canvas/autoLayout.ts","../../src/canvas/arrangeController.ts","../../src/canvas/measure.ts","../../src/canvas/autoLayoutController.ts"],"names":["next","best","inside","toIndex","dot","corners","out","formatNumber","dist","defaultId","layout","add","unit","lim","EPS"],"mappings":"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MAAA;AACD,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGS;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACN,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACAC,MAAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACL,IAAA;AACQ,IAAA;AACR,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACE,IAAA;AACF,IAAA;AACI,IAAA;AACT,EAAA;AACO,EAAA;AACD,EAAA;AACN,EAAA;AAGQ,IAAA;AACD,IAAA;AACF,IAAA;AACJ,IAAA;AAEK,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACE,EAAA;AACD,IAAA;AACA,IAAA;AACF,IAAA;AACG,IAAA;AACP,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAES;AACA,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACF,EAAA;AACD;AAgBgB;AAKX,EAAA;AACG,EAAA;AACR;AAEa;AAEA;AAEA;AAEA;AAQG;AACX,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACN,EAAA;AACQ,EAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA;AACK,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AX26CY;AACA;AYnlEH;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAqBM;AAIO;AACA;AACA;AACC;AAEL;AACE,EAAA;AACH,EAAA;AACR;AAES;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACC,IAAA;AACA,IAAA;AACR,EAAA;AACD;AAGgB;AACR,EAAA;AACR;AAES;AACF,EAAA;AACN,EAAA;AACM,IAAA;AACG,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAAA;AACA,EAAA;AACD,EAAA;AACD,IAAA;AACA,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACA,MAAA;AACA,MAAA;AACN,IAAA;AACM,IAAA;AACA,IAAA;AACP,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACA,EAAA;AACF,IAAA;AACA,IAAA;AACP,EAAA;AACK,EAAA;AACC,IAAA;AACA,IAAA;AACN,EAAA;AAEM,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGS;AACA,EAAA;AACE,EAAA;AACD,EAAA;AACF,IAAA;AACE,IAAA;AACH,MAAA;AACE,MAAA;AACP,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAGS;AACE,EAAA;AACA,EAAA;AACJ,EAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACC,EAAA;AACR;AAOS;AACJ,EAAA;AACC,EAAA;AACC,EAAA;AACG,EAAA;AACR,IAAA;AACM,IAAA;AACC,IAAA;AACC,IAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AACM,IAAA;AACR,EAAA;AACQ,EAAA;AACT;AAES;AACE,EAAA;AACA,EAAA;AACH,IAAA;AACF,IAAA;AACJ,IAAA;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;AACA,IAAA;AACT,EAAA;AACK,EAAA;AACE,EAAA;AACR;AAcM;AACY,mBAAA;AACR,EAAA;AACQ,mBAAA;AACA,mBAAA;AAEjB,EAAA;AACM,IAAA;AACL,IAAA;AACO,MAAA;AACA,MAAA;AACD,QAAA;AACJ,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACH,QAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACN,IAAA;AACA,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AAE8B,EAAA;AACvB,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAAA;AAGkB,EAAA;AACb,IAAA;AACC,IAAA;AACD,IAAA;AACA,MAAA;AACJ,IAAA;AACM,MAAA;AACN,IAAA;AACD,EAAA;AAEU,EAAA;AACH,IAAA;AACA,IAAA;AACF,IAAA;AACG,IAAA;AACF,MAAA;AACC,MAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AACD;AAES;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACP;AAES;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACP;AAEM;AAGG;AACE,EAAA;AACF,EAAA;AACC,EAAA;AACF,IAAA;AACC,IAAA;AACP,EAAA;AACF;AAGS;AACE,EAAA;AACA,EAAA;AACJ,EAAA;AACE,EAAA;AACJ,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACS,IAAA;AACT,EAAA;AACD;AAGS;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACJ,EAAA;AACE,EAAA;AACA,EAAA;AACG,EAAA;AACL,EAAA;AACC,EAAA;AACN;AAES;AACE,EAAA;AACF,EAAA;AACJ,EAAA;AACG,IAAA;AACC,IAAA;AACR,EAAA;AACMA,EAAAA;AACDA,EAAAA;AACL,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAA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H,IAAA;AACJ,EAAA;AACK,EAAA;AACE,IAAA;AACH,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAES;AACA,EAAA;AACD,EAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACP,EAAA;AACF;AAES;AACE,EAAA;AACJ,EAAA;AACI,EAAA;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACAC,IAAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACM,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AAES;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACP,EAAA;AACC,IAAA;AACA,IAAA;AACO,IAAA;AACA,IAAA;AACR,EAAA;AACQ,EAAA;AACA,EAAA;AAEJ,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACI,EAAA;AACI,EAAA;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACC,QAAA;AACE,QAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACD;AASS;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACA,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACA,EAAA;AACF;AASgB;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACL,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAGgB;AACL,EAAA;AACV,EAAA;AACS,EAAA;AACV;AAMgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AAEA,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACS,EAAA;AACV;AAOgB;AAML,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGgB;AAKR,EAAA;AACR;AAGgB;AACR,EAAA;AAGR;AAGgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAQgB;AAKT,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AA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AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","sourcesContent":[null,"/**\n * The time source that canvas animations and agent performances play on.\n *\n * Defaults to `realClock` (requestAnimationFrame and setTimeout).\n * A `ManualClock` moves only when told to via `advance(ms)` or `tick(fps)`,\n * enabling deterministic frame-by-frame stepping for testing, video generation,\n * or headless server environments.\n */\n\nexport interface Clock {\n\t/** Current time in milliseconds. */\n\tnow(): number;\n\t/** Resolves on the next animation frame. */\n\tframe(): Promise<void>;\n\t/** Resolves after `ms` milliseconds of this clock's time. */\n\tsleep(ms: number): Promise<void>;\n\t/**\n\t * Whether the clock follows wall time. Live clocks stop or degrade when the\n\t * tab is in the background (browsers pause animation frames), so transitions\n\t * can commit immediately rather than leaving operations hanging.\n\t */\n\treadonly live: boolean;\n}\n\n/** Whether the browser tab is hidden (where RAF pauses). */\nexport const pageHidden = (): boolean =>\n\ttypeof document !== 'undefined' && document.hidden;\n\n/** Real-time clock using requestAnimationFrame and setTimeout. */\nexport const realClock: Clock = {\n\tlive: true,\n\tnow: () => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n\tframe: () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\tif (typeof requestAnimationFrame === 'function' && !pageHidden()) {\n\t\t\t\trequestAnimationFrame(() => resolve());\n\t\t\t} else {\n\t\t\t\tsetTimeout(resolve, 16);\n\t\t\t}\n\t\t}),\n\tsleep: (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, ms)))\n};\n\nconst settle = () => new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n/**\n * A deterministic clock that only moves forward when `advance` or `tick` is called.\n * Allows step-by-step frame testing and video rendering without waiting for real time.\n */\nexport class ManualClock implements Clock {\n\treadonly live = false;\n\tprivate time: number;\n\tprivate frames: (() => void)[] = [];\n\tprivate sleepers: { at: number; seq: number; resolve: () => void }[] = [];\n\tprivate seq = 0;\n\n\tconstructor(start = 0) {\n\t\tthis.time = start;\n\t}\n\n\tpublic now(): number {\n\t\treturn this.time;\n\t}\n\n\tpublic frame(): Promise<void> {\n\t\treturn new Promise<void>((resolve) => this.frames.push(resolve));\n\t}\n\n\tpublic sleep(ms: number): Promise<void> {\n\t\treturn new Promise<void>((resolve) =>\n\t\t\tthis.sleepers.push({\n\t\t\t\tat: this.time + Math.max(0, ms),\n\t\t\t\tseq: this.seq++,\n\t\t\t\tresolve\n\t\t\t})\n\t\t);\n\t}\n\n\t/** Whether any frames or sleep timers are currently pending. */\n\tpublic get pending(): boolean {\n\t\treturn this.frames.length > 0 || this.sleepers.length > 0;\n\t}\n\n\t/** Advances time by `ms`, resolving due sleep timers in order, followed by queued frames. */\n\tpublic async advance(ms: number): Promise<void> {\n\t\tconst end = this.time + Math.max(0, ms);\n\t\tawait settle();\n\n\t\twhile (true) {\n\t\t\tlet next: (typeof this.sleepers)[number] | undefined;\n\t\t\tfor (const s of this.sleepers) {\n\t\t\t\tif (s.at <= end && (!next || s.at < next.at || (s.at === next.at && s.seq < next.seq))) {\n\t\t\t\t\tnext = s;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!next) break;\n\n\t\t\tthis.sleepers.splice(this.sleepers.indexOf(next), 1);\n\t\t\tthis.time = Math.max(this.time, next.at);\n\t\t\tnext.resolve();\n\t\t\tawait settle();\n\t\t}\n\n\t\tthis.time = end;\n\t\tconst dueFrames = this.frames.splice(0, this.frames.length);\n\t\tfor (const resolveFrame of dueFrames) {\n\t\t\tresolveFrame();\n\t\t}\n\t\tawait settle();\n\t}\n\n\t/** Convenience method to advance by 1 frame at a given fps (default: 60fps = 16.67ms). */\n\tpublic async tick(fps = 60): Promise<void> {\n\t\tawait this.advance(Math.round(1000 / fps));\n\t}\n}\n","import type {\n\tCanvasNode,\n\tCanvasNodeStyle,\n\tFanOutTrajectoryOptions,\n\tNodeTransition,\n\tTrajectoryFunction,\n\tTrajectoryPoint,\n\tXYPosition\n} from './types.js';\n\n/**\n * Standard cubic ease-out function (pure math, framework-agnostic).\n */\nexport const defaultEasing = (t: number): number => --t * t * t + 1;\n\n/**\n * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.\n */\nexport const linearTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t},\n\t\tscale: progress,\n\t\topacity: Math.min(1, progress * 1.5)\n\t};\n};\n\n/**\n * Linear position trajectory: pure position interpolation without altering scale or opacity.\n */\nexport const linearPositionTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t}\n\t};\n};\n\n/**\n * Creates a fan-out trajectory where nodes arc outward from origin to target.\n */\nexport function createFanOutTrajectory(options: FanOutTrajectoryOptions = {}): TrajectoryFunction {\n\tconst {\n\t\tcurvature = 0.4,\n\t\tstartScale = 0,\n\t\tendScale = 1,\n\t\tstartOpacity = 0,\n\t\tendOpacity = 1\n\t} = options;\n\n\treturn (\n\t\torigin: XYPosition,\n\t\ttarget: XYPosition,\n\t\tprogress: number,\n\t\tindex: number,\n\t\ttotal: number\n\t): TrajectoryPoint => {\n\t\tconst dx = target.x - origin.x;\n\t\tconst dy = target.y - origin.y;\n\n\t\t// Normalized index from -1 (top) to +1 (bottom)\n\t\tconst normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;\n\n\t\t// Perpendicular control point offset to create smooth fan arc\n\t\tconst midX = origin.x + dx * 0.4;\n\t\tconst midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;\n\n\t\t// Quadratic Bezier interpolation: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;\n\n\t\tconst scale = startScale + (endScale - startScale) * progress;\n\t\tconst opacity = startOpacity + (endOpacity - startOpacity) * progress;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale,\n\t\t\topacity\n\t\t};\n\t};\n}\n\n/**\n * Creates a custom quadratic Bezier trajectory with a specified control point resolver.\n */\nexport function createBezierTrajectory(\n\tgetControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition\n): TrajectoryFunction {\n\treturn (origin: XYPosition, target: XYPosition, progress: number): TrajectoryPoint => {\n\t\tconst cp = getControlPoint(origin, target);\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale: progress,\n\t\t\topacity: progress\n\t\t};\n\t};\n}\n\nimport { realClock, type Clock } from './clock.js';\n\n/**\n * Returns a copy of `style` with its opacity set to `opacity`.\n *\n * Supports both CSS strings (`\"color: red; opacity: 0.5;\"`, used by SvelteFlow)\n * and style objects (`{ color: 'red', opacity: 0.5 }`, used by React Flow / xyflow).\n * When `style` is empty, `emptyFormat` decides which representation is produced.\n * Never mutates the input.\n */\nexport function withStyleOpacity(\n\tstyle: CanvasNodeStyle | undefined | null,\n\topacity: number,\n\temptyFormat: 'string' | 'object' = 'string'\n): CanvasNodeStyle {\n\tif (style !== null && typeof style === 'object') {\n\t\treturn { ...style, opacity };\n\t}\n\tif (typeof style === 'string' && style.trim() !== '') {\n\t\tconst rest = style\n\t\t\t.replace(/(^|;)\\s*opacity\\s*:\\s*[^;]*;?/gi, '$1')\n\t\t\t.trim()\n\t\t\t.replace(/;+\\s*$/, '')\n\t\t\t.trim();\n\t\treturn `${rest ? rest + '; ' : ''}opacity: ${opacity};`;\n\t}\n\treturn emptyFormat === 'object' ? { opacity } : `opacity: ${opacity};`;\n}\n\nexport interface MultiNodeTransitionOptions<TNode extends CanvasNode = CanvasNode> {\n\tdefaultDuration?: number;\n\tdefaultEasing?: (t: number) => number;\n\tclock?: Clock;\n\t/**\n\t * Style representation to create for nodes that have no `style` yet when an opacity\n\t * is applied. Defaults to `'string'` (SvelteFlow); pass `'object'` for React Flow.\n\t * Existing styles always keep their representation.\n\t */\n\tstyleFormat?: 'string' | 'object';\n\t/**\n\t * Called on every frame with updated copies of the transitioning nodes, in the same\n\t * order as the `transitions` array. Input nodes are never mutated.\n\t */\n\tonUpdate?: (nodes: TNode[]) => void;\n\t/** Called once all transitions finished, with the final node copies. */\n\tonComplete?: (nodes: TNode[]) => void;\n}\n\n/**\n * Coordinates and animates multiple node transitions simultaneously.\n *\n * The input nodes are treated as immutable: every frame produces updated copies\n * (position, `data.scale` / `data.opacity`, and `style` opacity) which are handed to\n * `onUpdate` and finally `onComplete`. Merge them into your node state by `id`.\n */\nexport function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(\n\ttransitions: NodeTransition<TNode>[],\n\toptions: MultiNodeTransitionOptions<TNode> = {}\n): () => void {\n\tif (transitions.length === 0) {\n\t\toptions.onComplete?.([]);\n\t\treturn () => {};\n\t}\n\n\tconst {\n\t\tdefaultDuration = 400,\n\t\tdefaultEasing: easingFunc = defaultEasing,\n\t\tclock = realClock,\n\t\tstyleFormat = 'string',\n\t\tonUpdate,\n\t\tonComplete\n\t} = options;\n\n\tlet isCancelled = false;\n\tconst startTime = clock.now();\n\n\tconst items = transitions.map((tr, i) => ({\n\t\t...tr,\n\t\tindex: tr.index ?? i,\n\t\ttotal: tr.total ?? transitions.length,\n\t\tduration: tr.duration ?? defaultDuration,\n\t\teasing: tr.easing ?? easingFunc,\n\t\tdelay: tr.delay ?? 0,\n\t\ttrajectory: tr.trajectory ?? linearTrajectory\n\t}));\n\n\t// Latest copy of every node; starts out as the untouched inputs.\n\tconst current: TNode[] = items.map((item) => item.node);\n\n\tfunction frame() {\n\t\tif (isCancelled) return;\n\n\t\tconst now = clock.now();\n\t\tlet allCompleted = true;\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst elapsed = now - (startTime + item.delay);\n\n\t\t\tif (elapsed < 0) {\n\t\t\t\tallCompleted = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Guard zero duration and division by zero (referenced in GOTCHAS.md)\n\t\t\tconst rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);\n\t\t\tconst progress = item.duration <= 0 ? 1 : item.easing(rawProgress);\n\n\t\t\tconst point = item.trajectory(item.from, item.to, progress, item.index, item.total);\n\n\t\t\tconst next: TNode = { ...item.node, position: { ...point.position } };\n\n\t\t\tif (point.scale !== undefined || point.opacity !== undefined) {\n\t\t\t\tconst data: Record<string, any> = { ...(item.node.data ?? {}) };\n\t\t\t\tif (point.scale !== undefined) {\n\t\t\t\t\tdata.scale = point.scale;\n\t\t\t\t}\n\t\t\t\tif (point.opacity !== undefined) {\n\t\t\t\t\tdata.opacity = point.opacity;\n\t\t\t\t\t(next as CanvasNode).style = withStyleOpacity(item.node.style, point.opacity, styleFormat);\n\t\t\t\t}\n\t\t\t\t(next as CanvasNode).data = data;\n\t\t\t}\n\n\t\t\tcurrent[i] = next;\n\n\t\t\tif (rawProgress < 1) {\n\t\t\t\tallCompleted = false;\n\t\t\t}\n\t\t}\n\n\t\tonUpdate?.(current.slice());\n\n\t\tif (!allCompleted) {\n\t\t\tclock.frame().then(frame);\n\t\t} else {\n\t\t\tonComplete?.(current.slice());\n\t\t}\n\t}\n\n\tframe();\n\n\treturn () => {\n\t\tisCancelled = true;\n\t};\n}\n","import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasNode,\n\tDirectionalDisplacementOptions,\n\tRect,\n\tReflowDisplacementOptions,\n\tXYPosition\n} from './types.js';\n\n/**\n * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.\n */\nexport function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tdefaultWidth = 200,\n\tdefaultHeight = 60\n): Rect {\n\tif (nodes.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst width = node.measured?.width ?? (node as any).width ?? defaultWidth;\n\t\tconst height = node.measured?.height ?? (node as any).height ?? defaultHeight;\n\n\t\tminX = Math.min(minX, node.position.x);\n\t\tminY = Math.min(minY, node.position.y);\n\t\tmaxX = Math.max(maxX, node.position.x + width);\n\t\tmaxY = Math.max(maxY, node.position.y + height);\n\t}\n\n\treturn {\n\t\tx: minX,\n\t\ty: minY,\n\t\twidth: maxX - minX,\n\t\theight: maxY - minY\n\t};\n}\n\n/**\n * Pure directional displacement calculation.\n * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.\n */\nexport function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {\n\texistingNodes: TNode[];\n\toriginNode: TNode;\n\tchildNodes: TNode[];\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n\toptions?: DirectionalDisplacementOptions;\n}): Map<string, XYPosition> {\n\tconst {\n\t\texistingNodes,\n\t\toriginNode,\n\t\tchildNodes,\n\t\tgetNodesBounds,\n\t\toptions = {}\n\t} = params;\n\n\tconst {\n\t\tdirection = 'right',\n\t\tgap = 40,\n\t\tspreadSiblingLanes = false,\n\t\tsiblingTolerance = 50,\n\t\tminY\n\t} = options;\n\n\tconst displacedMap = new Map<string, XYPosition>();\n\tif (childNodes.length === 0 || existingNodes.length === 0) {\n\t\treturn displacedMap;\n\t}\n\n\tconst childBounds = getNodesBounds\n\t\t? getNodesBounds(childNodes)\n\t\t: computeNodesBoundingBox(childNodes);\n\n\tconst originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;\n\tconst originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;\n\n\t// The delta space required along primary axis\n\tconst deltaPrimary =\n\t\tdirection === 'right' || direction === 'left'\n\t\t\t? childBounds.width + gap\n\t\t\t: childBounds.height + gap;\n\n\t// Vertical displacement parameters: upward and downward row clearance\n\tconst deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);\n\tconst deltaDown = Math.max(\n\t\t0,\n\t\tchildBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap\n\t);\n\n\t// Both above and below displace equally so the row expands symmetrically\n\tconst equalDelta = Math.max(deltaUp, deltaDown);\n\n\tfor (const node of existingNodes) {\n\t\tif (node.id === originNode.id) continue;\n\n\t\tconst isSiblingRow =\n\t\t\tMath.abs(node.position.y - originNode.position.y) < siblingTolerance;\n\n\t\tswitch (direction) {\n\t\t\tcase 'right': {\n\t\t\t\tif (node.position.x > originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x + deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\tif (node.position.x < originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x - deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'down': {\n\t\t\t\tif (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'up': {\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'vertical': {\n\t\t\t\tif (isSiblingRow) break;\n\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - equalDelta\n\t\t\t\t\t});\n\t\t\t\t} else if (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + equalDelta\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sibling lane spreading to avoid overlap among child nodes\n\tif (spreadSiblingLanes && (direction === 'right' || direction === 'left')) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tconst node = existingNodes.find((n) => n.id === nodeId);\n\t\t\tif (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {\n\t\t\t\tconst isAbove = node.position.y < originNode.position.y;\n\t\t\t\tdisplacedMap.set(nodeId, {\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y + (isAbove ? -deltaUp : deltaDown)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Floor constraint\n\tif (minY !== undefined) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tif (pos.y < minY) {\n\t\t\t\tdisplacedMap.set(nodeId, { x: pos.x, y: minY });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn displacedMap;\n}\n\n/**\n * Calculates Dagre reflow displacement preserving the position of an anchor node.\n */\nexport function calculateDagreReflowDisplacement<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(params: {\n\tcurrentNodes: TNode[];\n\tcurrentEdges: TEdge[];\n\tfutureNodes: TNode[];\n\tfutureEdges: TEdge[];\n\toptions: ReflowDisplacementOptions;\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n}): Map<string, XYPosition> {\n\tconst {\n\t\tcurrentNodes,\n\t\tfutureNodes,\n\t\tfutureEdges,\n\t\toptions\n\t} = params;\n\n\tconst {\n\t\tanchorNodeId,\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = 200,\n\t\tdefaultNodeHeight = 60\n\t} = options;\n\n\tconst anchorNode = currentNodes.find((n) => n.id === anchorNodeId);\n\tif (!anchorNode) return new Map();\n\n\tconst g = new dagre.graphlib.Graph();\n\tg.setDefaultEdgeLabel(() => ({}));\n\tg.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(futureNodes.map((n) => n.id));\n\n\tfutureNodes.forEach((node) => {\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\t\tg.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tfutureEdges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tg.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(g);\n\n\tconst anchorLayoutPos = g.node(anchorNodeId);\n\tif (!anchorLayoutPos) return new Map();\n\n\tconst anchorWidth = anchorNode.measured?.width ?? (anchorNode as any).width ?? defaultNodeWidth;\n\tconst anchorHeight = anchorNode.measured?.height ?? (anchorNode as any).height ?? defaultNodeHeight;\n\n\tconst anchorLayoutUpperLeft = {\n\t\tx: anchorLayoutPos.x - anchorWidth / 2,\n\t\ty: anchorLayoutPos.y - anchorHeight / 2\n\t};\n\n\tconst deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;\n\tconst deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;\n\n\tconst displacementMap = new Map<string, XYPosition>();\n\n\tcurrentNodes.forEach((node) => {\n\t\tif (node.id === anchorNodeId) return;\n\n\t\tconst layoutNode = g.node(node.id);\n\t\tif (!layoutNode) return;\n\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\n\t\tconst upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;\n\t\tconst upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;\n\n\t\tif (\n\t\t\tMath.abs(upperLeftX - node.position.x) > 1 ||\n\t\t\tMath.abs(upperLeftY - node.position.y) > 1\n\t\t) {\n\t\t\tdisplacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });\n\t\t}\n\t});\n\n\treturn displacementMap;\n}\n","import type {\n\tCanvasClipboardPayload,\n\tCanvasEdge,\n\tCanvasNode,\n\tXYPosition\n} from './types.js';\n\n/**\n * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.\n */\nexport function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {\n\tconst target = (event as any).target as HTMLElement | null;\n\tif (!target) return false;\n\tif (\n\t\ttarget.tagName === 'INPUT' ||\n\t\ttarget.tagName === 'TEXTAREA' ||\n\t\ttarget.tagName === 'SELECT' ||\n\t\ttarget.isContentEditable ||\n\t\ttarget.closest?.('[contenteditable=\"true\"]') ||\n\t\ttarget.closest?.('.ProseMirror')\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Validates that an object conforms to minimal CanvasNode shape:\n * a string `id` and a `position` whose `x` / `y` are finite numbers.\n */\nexport function isValidCanvasNode(obj: unknown): obj is CanvasNode {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; position?: unknown };\n\tif (typeof candidate.id !== 'string') return false;\n\tconst position = candidate.position as { x?: unknown; y?: unknown } | null | undefined;\n\treturn (\n\t\ttypeof position === 'object' &&\n\t\tposition !== null &&\n\t\ttypeof position.x === 'number' &&\n\t\tNumber.isFinite(position.x) &&\n\t\ttypeof position.y === 'number' &&\n\t\tNumber.isFinite(position.y)\n\t);\n}\n\n/**\n * Validates that an object conforms to minimal CanvasEdge shape (string `id`, `source`, `target`).\n */\nexport function isValidCanvasEdge(obj: unknown): obj is CanvasEdge {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; source?: unknown; target?: unknown };\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.source === 'string' &&\n\t\ttypeof candidate.target === 'string'\n\t);\n}\n\n/**\n * Checks if a string is a valid HTTP/HTTPS URL.\n */\nexport function isValidUrl(url: string): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:';\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Serializes nodes and optional edges into clipboard JSON payload.\n */\nexport function serializeCanvasNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(nodes: TNode[], edges?: TEdge[]): string {\n\tconst payload: CanvasClipboardPayload<TNode, TEdge> = {\n\t\tversion: 1,\n\t\tnodes,\n\t\tedges\n\t};\n\treturn JSON.stringify(payload);\n}\n\n/**\n * Parses raw JSON string into a CanvasClipboardPayload if valid.\n */\nexport function parseCanvasClipboardData<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(raw: string): CanvasClipboardPayload<TNode, TEdge> | null {\n\ttry {\n\t\tconst parsed = JSON.parse(raw);\n\t\tif (\n\t\t\ttypeof parsed === 'object' &&\n\t\t\tparsed !== null &&\n\t\t\tArray.isArray(parsed.nodes) &&\n\t\t\tparsed.nodes.length > 0 &&\n\t\t\tparsed.nodes.every(isValidCanvasNode)\n\t\t) {\n\t\t\t// Untrusted input: drop malformed edges instead of crashing downstream consumers.\n\t\t\tconst edges = Array.isArray(parsed.edges)\n\t\t\t\t? (parsed.edges as unknown[]).filter(isValidCanvasEdge)\n\t\t\t\t: undefined;\n\t\t\treturn { ...parsed, edges } as CanvasClipboardPayload<TNode, TEdge>;\n\t\t}\n\t} catch {\n\t\t// Not JSON or invalid payload\n\t}\n\treturn null;\n}\n\n/**\n * Generates unique new IDs for pasted nodes and updates edge references accordingly.\n *\n * - Nodes without a valid id / finite numeric position are skipped.\n * - `parentId` (and legacy `parentNode`) references to other pasted nodes are remapped to\n * the new IDs; such children keep their parent-relative position (no extra offset).\n * References to parents outside the pasted set are left unchanged.\n * - Edges are kept only when both endpoints were pasted; a non-array `edges` is ignored.\n */\nexport function remapPastedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[] | null | undefined = [],\n\toffset: XYPosition = { x: 30, y: 30 },\n\tidGenerator: () => string = () => `node-${Math.random().toString(36).substring(2, 9)}`\n): { nodes: TNode[]; edges: TEdge[] } {\n\tconst validNodes = (Array.isArray(nodes) ? nodes : []).filter(isValidCanvasNode) as TNode[];\n\tconst validEdges = (Array.isArray(edges) ? edges : []).filter(isValidCanvasEdge) as TEdge[];\n\n\tconst dx = Number.isFinite(offset?.x) ? offset.x : 0;\n\tconst dy = Number.isFinite(offset?.y) ? offset.y : 0;\n\n\tconst idMap = new Map<string, string>();\n\tfor (const node of validNodes) {\n\t\tidMap.set(node.id, idGenerator());\n\t}\n\n\tconst newNodes = validNodes.map((node) => {\n\t\tconst next: CanvasNode = {\n\t\t\t...node,\n\t\t\tid: idMap.get(node.id)!,\n\t\t\tposition: {\n\t\t\t\tx: node.position.x + dx,\n\t\t\t\ty: node.position.y + dy\n\t\t\t},\n\t\t\tselected: true\n\t\t};\n\n\t\tlet parentRemapped = false;\n\t\tfor (const key of ['parentId', 'parentNode'] as const) {\n\t\t\tconst parent = (node as CanvasNode)[key];\n\t\t\tif (typeof parent === 'string' && idMap.has(parent)) {\n\t\t\t\tnext[key] = idMap.get(parent)!;\n\t\t\t\tparentRemapped = true;\n\t\t\t}\n\t\t}\n\t\tif (parentRemapped) {\n\t\t\t// Position is relative to the (also pasted, also offset) parent.\n\t\t\tnext.position = { x: node.position.x, y: node.position.y };\n\t\t}\n\n\t\treturn next as TNode;\n\t});\n\n\tconst newEdges = validEdges\n\t\t.filter((e) => idMap.has(e.source) && idMap.has(e.target))\n\t\t.map((edge) => ({\n\t\t\t...edge,\n\t\t\tid: `edge-${Math.random().toString(36).substring(2, 9)}`,\n\t\t\tsource: idMap.get(edge.source)!,\n\t\t\ttarget: idMap.get(edge.target)!,\n\t\t\tselected: false\n\t\t}));\n\n\treturn { nodes: newNodes, edges: newEdges };\n}\n\n/**\n * Repositions nodes so their bounding box origin matches the given target position.\n */\nexport function repositionNodesTo<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\ttargetOrigin: XYPosition\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\tconst minX = Math.min(...nodes.map((n) => n.position.x));\n\tconst minY = Math.min(...nodes.map((n) => n.position.y));\n\tconst deltaX = targetOrigin.x - minX;\n\tconst deltaY = targetOrigin.y - minY;\n\n\treturn nodes.map((n) => ({\n\t\t...n,\n\t\tposition: {\n\t\t\tx: n.position.x + deltaX,\n\t\t\ty: n.position.y + deltaY\n\t\t}\n\t}));\n}\n","import type { CanvasEdge, CanvasNode, Rect } from './types.js';\n\nexport interface CanvasHeightOptions {\n\t/** Smallest height returned (default 320). */\n\tmin?: number;\n\t/** Largest height returned; taller content is scaled/zoomed (default 560). */\n\tmax?: number;\n\t/** Extra padding around the content (default 48). */\n\tpadding?: number;\n}\n\nfunction clampHeight(contentHeight: number, options: CanvasHeightOptions = {}): number {\n\tconst { min = 320, max = 560, padding = 48 } = options;\n\treturn Math.round(Math.min(max, Math.max(min, contentHeight + padding)));\n}\n\n/**\n * Calculates the bounding box extent of all positioned nodes, or `null` for an empty graph.\n */\nexport function layoutBounds<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[]\n): Rect | null {\n\tif (nodes.length === 0) return null;\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst x = node.position.x;\n\t\tconst y = node.position.y;\n\t\tconst width = node.width ?? 180;\n\t\tconst height = node.height ?? 80;\n\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x + width);\n\t\tmaxY = Math.max(maxY, y + height);\n\t}\n\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Returns a clamped container height that fits the layout at 1:1 scale without overflow.\n */\nexport function canvasHeight<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\toptions?: CanvasHeightOptions\n): number {\n\treturn clampHeight(layoutBounds(nodes)?.height ?? 0, options);\n}\n\nconst DEFAULT_RANK_HEIGHT = 80 + 72; // Average node height + default rank separation\nconst NODES_PER_RANK = 2;\n\n/**\n * Estimates the container height to reserve before layout calculation finishes,\n * based on node and edge counts alone. Prevents layout shift (CLS).\n */\nexport function estimateCanvasHeight<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions?: CanvasHeightOptions\n): number {\n\tconst ranks = Math.max(1, Math.ceil(nodes.length / NODES_PER_RANK));\n\treturn clampHeight(ranks * DEFAULT_RANK_HEIGHT, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n\treturn a + (b - a) * t;\n}\n\n/**\n * Interpolates node positions and sizes between two layouts partway at progress `t` (0 = from, 1 = to).\n * Nodes present in both interpolate smoothly; nodes newly added in `to` snap to target.\n */\nexport function blendLayout<TNode extends CanvasNode = CanvasNode>(\n\tfrom: TNode[],\n\tto: TNode[],\n\tt: number\n): TNode[] {\n\tif (t >= 1) return to;\n\n\tconst fromMap = new Map<string, TNode>(from.map((n) => [n.id, n]));\n\n\treturn to.map((target) => {\n\t\tconst origin = fromMap.get(target.id);\n\t\tif (!origin) return target;\n\n\t\treturn {\n\t\t\t...target,\n\t\t\tposition: {\n\t\t\t\tx: mix(origin.position.x, target.position.x, t),\n\t\t\t\ty: mix(origin.position.y, target.position.y, t)\n\t\t\t},\n\t\t\twidth:\n\t\t\t\torigin.width !== undefined && target.width !== undefined\n\t\t\t\t\t? mix(origin.width, target.width, t)\n\t\t\t\t\t: target.width,\n\t\t\theight:\n\t\t\t\torigin.height !== undefined && target.height !== undefined\n\t\t\t\t\t? mix(origin.height, target.height, t)\n\t\t\t\t\t: target.height\n\t\t};\n\t});\n}\n\n/**\n * Filters out nodes that are not touched by any edge.\n *\n * During LLM streaming, nodes often arrive a chunk or token before the edge\n * that connects them. Displaying them immediately causes them to be placed as root\n * nodes only to jump into their correct position once the edge arrives.\n * Hiding orphan nodes during active streaming keeps the layout calm and jitter-free.\n */\nexport function connectedOnly<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[]\n): TNode[] {\n\tif (edges.length === 0) return nodes;\n\n\tconst linkedNodeIds = new Set<string>();\n\tfor (const edge of edges) {\n\t\tlinkedNodeIds.add(edge.source);\n\t\tlinkedNodeIds.add(edge.target);\n\t}\n\n\tconst filtered = nodes.filter((node) => linkedNodeIds.has(node.id));\n\t// If filtering would remove everything, return the original nodes\n\treturn filtered.length > 0 ? filtered : nodes;\n}\n","import { realClock, type Clock } from './clock.js';\nimport { ensureLayout } from './layout.js';\nimport { linearPositionTrajectory, runMultiNodeTransition } from './trajectory.js';\nimport type { CanvasEdge, CanvasNode, IncrementalLayoutOptions, NodeTransition, XYPosition } from './types.js';\nimport { connectedOnly } from './view.js';\n\nexport interface StreamingLayoutManagerOptions {\n\tthrottleMs?: number;\n\tglideDurationMs?: number;\n\tconnectedOnly?: boolean;\n\tclock?: Clock;\n\tlayoutOptions?: IncrementalLayoutOptions;\n\tonLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n}\n\n/**\n * Manages throttled incremental layout recalculation and smooth tweened node gliding\n * during active streaming (e.g. LLM streaming or graph generation).\n *\n * - `pushStreamUpdate` is a true throttle (leading + trailing edge): the first update\n * lays out immediately, further updates within `throttleMs` are coalesced and the\n * latest one is laid out when the window closes. Layouts therefore keep running at\n * most once per `throttleMs` while a stream is active, instead of being postponed\n * until the stream goes quiet.\n * - Every layout is computed from the graph (respecting `layoutOptions.storedPositions`\n * as pinned/manual coordinates) and each known node glides from its *current*\n * position (the `currentPositions` argument) to its newly computed position.\n */\nexport class StreamingLayoutManager<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> {\n\tprivate throttleMs: number;\n\tprivate glideDurationMs: number;\n\tprivate connectedOnly: boolean;\n\tprivate clock: Clock;\n\tprivate layoutOptions: IncrementalLayoutOptions;\n\tprivate onLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n\n\tprivate pendingTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pendingArgs: [TNode[], TEdge[], Map<string, XYPosition>] | null = null;\n\tprivate activeTransitionCancel: (() => void) | null = null;\n\tprivate targetPositions = new Map<string, XYPosition>();\n\tprivate livePositions = new Map<string, XYPosition>();\n\n\tconstructor(options: StreamingLayoutManagerOptions = {}) {\n\t\tthis.throttleMs = options.throttleMs ?? 200;\n\t\tthis.glideDurationMs = options.glideDurationMs ?? 300;\n\t\tthis.connectedOnly = options.connectedOnly ?? false;\n\t\tthis.clock = options.clock ?? realClock;\n\t\tthis.layoutOptions = options.layoutOptions ?? { direction: 'LR' };\n\t\tthis.onLayoutUpdated = options.onLayoutUpdated;\n\t}\n\n\t/**\n\t * Feed incoming nodes and edges into the streaming manager.\n\t * Throttles rapid streaming bursts (leading + trailing edge) and triggers smooth layout gliding.\n\t */\n\tpublic pushStreamUpdate(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\t// Inside the throttle window: remember the latest update for the trailing edge.\n\t\t\tthis.pendingArgs = [nodes, edges, currentPositions];\n\t\t\treturn;\n\t\t}\n\n\t\t// Leading edge: lay out immediately and open a throttle window.\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t\tthis.startWindow();\n\t}\n\n\tprivate startWindow(): void {\n\t\tthis.pendingTimer = setTimeout(() => {\n\t\t\tthis.pendingTimer = null;\n\t\t\tconst args = this.pendingArgs;\n\t\t\tthis.pendingArgs = null;\n\t\t\tif (args) {\n\t\t\t\t// Trailing edge: lay out the latest coalesced update and keep throttling.\n\t\t\t\tthis.recalculateAndGlide(...args);\n\t\t\t\tthis.startWindow();\n\t\t\t}\n\t\t}, Math.max(0, this.throttleMs));\n\t}\n\n\t/**\n\t * Forces immediate calculation and cancels pending throttled timers.\n\t */\n\tpublic flush(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tthis.clearPending();\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t}\n\n\t/**\n\t * Latest computed layout targets (the positions nodes are gliding towards).\n\t */\n\tpublic getTargetPositions(): Map<string, XYPosition> {\n\t\treturn new Map(this.targetPositions);\n\t}\n\n\tprivate clearPending(): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tthis.pendingArgs = null;\n\t}\n\n\tprivate recalculateAndGlide(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tconst targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;\n\t\tif (targetNodes.length === 0) return;\n\n\t\t// Where nodes are right now: the caller's view, overridden by any glide still in\n\t\t// flight (the caller may not have applied the latest animated frame yet).\n\t\tconst startPositions = new Map<string, XYPosition>(currentPositions);\n\t\tif (this.activeTransitionCancel) {\n\t\t\tfor (const [id, pos] of this.livePositions) {\n\t\t\t\tstartPositions.set(id, pos);\n\t\t\t}\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\n\t\t// Compute fresh layout targets. Only explicitly stored (pinned/manual) positions\n\t\t// are preserved; current positions are the glide *origin*, not the destination.\n\t\tconst layouted = ensureLayout(targetNodes, edges, this.layoutOptions);\n\n\t\tconst nextPositions = new Map<string, XYPosition>();\n\t\tconst transitions: NodeTransition<TNode>[] = [];\n\n\t\tfor (const node of layouted) {\n\t\t\tconst target = { ...node.position };\n\t\t\tnextPositions.set(node.id, target);\n\n\t\t\tconst current = startPositions.get(node.id);\n\t\t\tif (!current) continue; // brand-new node: appears directly at its target\n\n\t\t\tconst distance = Math.hypot(target.x - current.x, target.y - current.y);\n\t\t\tif (distance > 2) {\n\t\t\t\ttransitions.push({\n\t\t\t\t\tnode: { ...node, position: { ...current } },\n\t\t\t\t\tfrom: current,\n\t\t\t\t\tto: target,\n\t\t\t\t\ttrajectory: linearPositionTrajectory,\n\t\t\t\t\tduration: this.glideDurationMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.targetPositions = nextPositions;\n\n\t\tif (transitions.length === 0) {\n\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\treturn;\n\t\t}\n\n\t\tconst live = new Map<string, XYPosition>(nextPositions);\n\t\tfor (const trans of transitions) {\n\t\t\tlive.set(trans.node.id, { ...trans.from });\n\t\t}\n\t\tthis.livePositions = live;\n\n\t\tlet finished = false;\n\t\tconst cancel = runMultiNodeTransition(transitions, {\n\t\t\tclock: this.clock,\n\t\t\tonUpdate: (updated) => {\n\t\t\t\tconst frame = new Map<string, XYPosition>(this.livePositions);\n\t\t\t\tfor (const node of updated) {\n\t\t\t\t\tframe.set(node.id, { ...node.position });\n\t\t\t\t}\n\t\t\t\tthis.livePositions = frame;\n\t\t\t\tthis.onLayoutUpdated?.(frame);\n\t\t\t},\n\t\t\tonComplete: () => {\n\t\t\t\tfinished = true;\n\t\t\t\tthis.activeTransitionCancel = null;\n\t\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\t}\n\t\t});\n\t\t// A zero-duration glide completes synchronously; don't keep a stale canceller.\n\t\tthis.activeTransitionCancel = finished ? null : cancel;\n\t}\n\n\tpublic destroy(): void {\n\t\tthis.clearPending();\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\t}\n}\n","import { boundsOf, estimateNodeSize } from './layout.js';\nimport type { CanvasNode, CanvasNodeStyle, Rect, XYPosition } from './types.js';\n\n/**\n * Edge (or centre line) used by `alignNodes`.\n * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.\n */\nexport type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\n\n/** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */\nexport type DistributeAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.\n */\nexport type DistributeMode = 'gaps' | 'centres';\n\n/** Dimension(s) copied by `matchSize`. */\nexport type SizeDimension = 'width' | 'height' | 'both';\n\n/**\n * Partial node change produced by the arrange helpers. `position` is in the node's own\n * coordinate space (relative to its `parentId` parent when it has one).\n */\nexport interface CanvasNodeUpdate {\n\tid: string;\n\tposition?: XYPosition;\n\twidth?: number;\n\theight?: number;\n\tzIndex?: number;\n}\n\nexport interface NodeRectOptions {\n\t/** Width used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultWidth?: number;\n\t/** Height used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultHeight?: number;\n\t/** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */\n\testimate?: boolean;\n}\n\n/** Data key marking a node as locked. See `lockNodes`. */\nexport const LOCKED_DATA_KEY = 'locked';\n/** Data key holding the interaction flags a node had before it was locked. */\nexport const LOCKED_PREVIOUS_FLAGS_KEY = 'lockedPrevious';\n\nfunction parsePx(value: unknown): number | undefined {\n\tif (typeof value === 'number' && Number.isFinite(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst match = /^\\s*(-?\\d+(?:\\.\\d+)?)(px)?\\s*$/i.exec(value);\n\t\tif (match) return Number(match[1]);\n\t}\n\treturn undefined;\n}\n\n/**\n * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)\n * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.\n */\nexport function readStyleSize(style: CanvasNodeStyle | undefined | null): {\n\twidth?: number;\n\theight?: number;\n} {\n\tif (!style) return {};\n\tif (typeof style === 'object') {\n\t\treturn { width: parsePx(style.width), height: parsePx(style.height) };\n\t}\n\tconst read = (prop: string) => {\n\t\tconst match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style);\n\t\treturn match ? parsePx(match[1]) : undefined;\n\t};\n\treturn { width: read('width'), height: read('height') };\n}\n\n/**\n * Returns `style` with its pixel `width` / `height` replaced, keeping its representation\n * (string or object). Only dimensions already present in the style are rewritten, because\n * xyflow applies the node's own `width` / `height` props otherwise.\n */\nexport function withStyleSize(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tsize: { width?: number; height?: number }\n): CanvasNodeStyle | undefined {\n\tif (!style) return style ?? undefined;\n\tif (typeof style === 'object') {\n\t\tconst next: Record<string, any> = { ...style };\n\t\tif (size.width !== undefined && 'width' in next) next.width = size.width;\n\t\tif (size.height !== undefined && 'height' in next) next.height = size.height;\n\t\treturn next;\n\t}\n\tlet next = style;\n\tfor (const prop of ['width', 'height'] as const) {\n\t\tconst value = size[prop];\n\t\tif (value === undefined) continue;\n\t\tnext = next.replace(\n\t\t\tnew RegExp(`((?:^|;)\\\\s*${prop}\\\\s*:\\\\s*)[^;]+`, 'i'),\n\t\t\t`$1${value}px`\n\t\t);\n\t}\n\treturn next;\n}\n\n/**\n * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel\n * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).\n */\nexport function getNodeSize(\n\tnode: CanvasNode,\n\toptions: NodeRectOptions = {}\n): { width: number; height: number } {\n\tconst styleSize = readStyleSize(node.style);\n\tlet width = node.measured?.width ?? node.width ?? styleSize.width;\n\tlet height = node.measured?.height ?? node.height ?? styleSize.height;\n\tif (width === undefined || height === undefined) {\n\t\tconst fallback =\n\t\t\toptions.estimate === false\n\t\t\t\t? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 }\n\t\t\t\t: estimateNodeSize(node, options.defaultWidth, options.defaultHeight);\n\t\twidth ??= fallback.width;\n\t\theight ??= fallback.height;\n\t}\n\treturn { width, height };\n}\n\n/**\n * Absolute (flow-space) position of a node, following its `parentId` chain.\n * Cycles and missing parents are tolerated.\n */\nexport function getAbsolutePosition(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>\n): XYPosition {\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set<string>([node.id]);\n\tlet parentId = node.parentId;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tconst parent = lookup.get(parentId);\n\t\tif (!parent) break;\n\t\tseen.add(parentId);\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId;\n\t}\n\treturn { x, y };\n}\n\n/** Builds an id lookup for a node array. */\nexport function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\n/** Absolute flow-space rectangle of a node. */\nexport function getNodeRect(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions?: NodeRectOptions\n): Rect {\n\tconst { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\treturn { x, y, ...getNodeSize(node, options) };\n}\n\n/**\n * Whether a node has been locked with `lockNodes`.\n */\nexport function isLocked(node: CanvasNode | undefined | null): boolean {\n\treturn node?.data?.[LOCKED_DATA_KEY] === true;\n}\n\n/**\n * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`\n * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`\n * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and\n * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.\n */\nexport function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || isLocked(node)) return node;\n\t\tconst previous: Record<string, boolean> = {};\n\t\tif (node.draggable !== undefined) previous.draggable = node.draggable;\n\t\tif (node.deletable !== undefined) previous.deletable = node.deletable;\n\t\treturn {\n\t\t\t...node,\n\t\t\tdraggable: false,\n\t\t\tdeletable: false,\n\t\t\tdata: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }\n\t\t};\n\t});\n}\n\n/**\n * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.\n */\nexport function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || !isLocked(node)) return node;\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},\n\t\t\t...data\n\t\t} = node.data as Record<string, any>;\n\t\tconst next = { ...node, data } as TNode;\n\t\tdelete next.draggable;\n\t\tdelete next.deletable;\n\t\tif (previous.draggable !== undefined) next.draggable = previous.draggable;\n\t\tif (previous.deletable !== undefined) next.deletable = previous.deletable;\n\t\treturn next;\n\t});\n}\n\n/** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */\nexport function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\tconst anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));\n\treturn anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);\n}\n\ninterface Item {\n\tnode: CanvasNode;\n\trect: Rect;\n\tlocked: boolean;\n}\n\nfunction collect(nodes: CanvasNode[], ids: Iterable<string>, options?: NodeRectOptions) {\n\tconst lookup = createNodeLookup(nodes);\n\tconst items: Item[] = [];\n\tconst seen = new Set<string>();\n\tfor (const id of ids) {\n\t\tconst node = lookup.get(id);\n\t\tif (!node || seen.has(id)) continue;\n\t\tseen.add(id);\n\t\titems.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });\n\t}\n\treturn { lookup, items };\n}\n\n/**\n * Converts absolute target positions into node-relative `position` updates. When a parent\n * moves in the same operation, children are made relative to the parent's new position.\n */\nfunction toPositionUpdates(\n\tlookup: Map<string, CanvasNode>,\n\ttargets: Map<string, XYPosition>\n): CanvasNodeUpdate[] {\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const [id, abs] of targets) {\n\t\tconst node = lookup.get(id)!;\n\t\tlet position = abs;\n\t\tif (node.parentId && lookup.has(node.parentId)) {\n\t\t\tconst parent = lookup.get(node.parentId)!;\n\t\t\tconst parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);\n\t\t\tposition = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };\n\t\t}\n\t\tif (position.x !== node.position.x || position.y !== node.position.y) {\n\t\t\tupdates.push({ id, position });\n\t\t}\n\t}\n\treturn updates;\n}\n\nexport interface AlignNodesOptions extends NodeRectOptions {\n\t/**\n\t * What to align to: the selection bounding box (default) or a fixed rectangle.\n\t * Locked nodes never move but still count towards the selection bounds, so they act as anchors.\n\t */\n\tto?: 'selection' | Rect;\n}\n\n/**\n * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's\n * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless\n * `options.to` is a rectangle. Locked nodes are not moved.\n */\nexport function alignNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tedge: AlignEdge,\n\toptions: AlignNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst fixed = typeof options.to === 'object' ? options.to : null;\n\tif (items.length === 0 || (!fixed && items.length < 2)) return [];\n\tconst bounds = fixed ?? boundsOf(items.map((i) => i.rect))!;\n\n\tconst targets = new Map<string, XYPosition>();\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tlet { x, y } = rect;\n\t\tswitch (edge) {\n\t\t\tcase 'left':\n\t\t\t\tx = bounds.x;\n\t\t\t\tbreak;\n\t\t\tcase 'center':\n\t\t\t\tx = bounds.x + bounds.width / 2 - rect.width / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'right':\n\t\t\t\tx = bounds.x + bounds.width - rect.width;\n\t\t\t\tbreak;\n\t\t\tcase 'top':\n\t\t\t\ty = bounds.y;\n\t\t\t\tbreak;\n\t\t\tcase 'middle':\n\t\t\t\ty = bounds.y + bounds.height / 2 - rect.height / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'bottom':\n\t\t\t\ty = bounds.y + bounds.height - rect.height;\n\t\t\t\tbreak;\n\t\t}\n\t\ttargets.set(node.id, { x, y });\n\t}\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface DistributeNodesOptions extends NodeRectOptions {\n\t/** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */\n\tgap?: number;\n}\n\nfunction distributeAxis(\n\titems: Item[],\n\thorizontal: boolean,\n\tmode: DistributeMode,\n\tgapOverride: number | undefined,\n\ttargets: Map<string, XYPosition>\n) {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst size = (r: Rect) => (horizontal ? r.width : r.height);\n\tconst sorted = [...items].sort(\n\t\t(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)\n\t);\n\tconst first = sorted[0].rect;\n\tconst last = sorted[sorted.length - 1].rect;\n\tconst n = sorted.length;\n\tconst set = (item: Item, value: number) => {\n\t\tif (item.locked) return;\n\t\tconst current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };\n\t\ttargets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });\n\t};\n\n\tif (mode === 'centres') {\n\t\tconst c0 = start(first) + size(first) / 2;\n\t\tconst c1 = start(last) + size(last) / 2;\n\t\tconst step = (c1 - c0) / (n - 1);\n\t\tsorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));\n\t\treturn;\n\t}\n\n\tconst span = start(last) + size(last) - start(first);\n\tconst total = sorted.reduce((sum, item) => sum + size(item.rect), 0);\n\tconst gap = gapOverride ?? (span - total) / (n - 1);\n\tlet cursor = start(first);\n\tfor (const item of sorted) {\n\t\tset(item, cursor);\n\t\tcursor += size(item.rect) + gap;\n\t}\n}\n\n/**\n * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).\n * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)\n * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit\n * `gap`). Locked nodes keep their place in the order but are not moved.\n */\nexport function distributeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\taxis: DistributeAxis,\n\tmode: DistributeMode = 'gaps',\n\toptions: DistributeNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst minimum = options.gap !== undefined && mode === 'gaps' ? 2 : 3;\n\tif (items.length < minimum) return [];\n\tconst targets = new Map<string, XYPosition>();\n\tif (axis !== 'vertical') distributeAxis(items, true, mode, options.gap, targets);\n\tif (axis !== 'horizontal') distributeAxis(items, false, mode, options.gap, targets);\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface MatchSizeOptions extends NodeRectOptions {\n\t/**\n\t * Size source: the `largest` (default) or `smallest` node, the `first` id given,\n\t * or a specific node id.\n\t */\n\treference?: 'largest' | 'smallest' | 'first' | { id: string };\n\t/** Keep each node's centre in place instead of its top-left corner (default `false`). */\n\tkeepCentre?: boolean;\n}\n\n/**\n * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size\n * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can\n * be the reference.\n */\nexport function matchSize(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tdimension: SizeDimension,\n\toptions: MatchSizeOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { items } = collect(nodes, ids, options);\n\tif (items.length < 2) return [];\n\tconst reference = options.reference ?? 'largest';\n\n\tconst pick = (dim: 'width' | 'height'): number => {\n\t\tif (typeof reference === 'object') {\n\t\t\tconst item = items.find((i) => i.node.id === reference.id);\n\t\t\tif (item) return item.rect[dim];\n\t\t}\n\t\tif (reference === 'first') return items[0].rect[dim];\n\t\tconst values = items.map((i) => i.rect[dim]);\n\t\treturn reference === 'smallest' ? Math.min(...values) : Math.max(...values);\n\t};\n\tconst width = dimension !== 'height' ? pick('width') : undefined;\n\tconst height = dimension !== 'width' ? pick('height') : undefined;\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tconst update: CanvasNodeUpdate = { id: node.id };\n\t\tif (width !== undefined && width !== rect.width) update.width = width;\n\t\tif (height !== undefined && height !== rect.height) update.height = height;\n\t\tif (update.width === undefined && update.height === undefined) continue;\n\t\tif (options.keepCentre) {\n\t\t\tupdate.position = {\n\t\t\t\tx: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,\n\t\t\t\ty: node.position.y - ((update.height ?? rect.height) - rect.height) / 2\n\t\t\t};\n\t\t}\n\t\tupdates.push(update);\n\t}\n\treturn updates;\n}\n\n/** Z-order operation. */\nexport type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';\n\n/**\n * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).\n *\n * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal\n * `zIndex`, by array order. This computes that effective order for the siblings that share\n * the targets' parent, applies the operation, and returns `zIndex` updates assigning each\n * sibling its new rank. The array order is left alone, because xyflow requires parents to\n * precede their children. Locked nodes are not restacked. \"Forward\" moves past the next\n * sibling the node overlaps, falling back to the next sibling when nothing overlaps.\n */\nexport function reorderNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\toperation: ZOrderOperation,\n\toptions: NodeRectOptions = {}\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targetIds = new Set(\n\t\t[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))\n\t);\n\tif (targetIds.size === 0) return [];\n\n\t// Restack each sibling group (nodes sharing a parent) independently.\n\tconst groups = new Map<string, CanvasNode[]>();\n\tfor (const node of nodes) {\n\t\tconst key = node.parentId ?? '';\n\t\tif (!groups.has(key)) groups.set(key, []);\n\t\tgroups.get(key)!.push(node);\n\t}\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const siblings of groups.values()) {\n\t\tif (!siblings.some((n) => targetIds.has(n.id))) continue;\n\t\tconst order = siblings\n\t\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t\t.map((entry) => entry.node);\n\t\tconst isTarget = (n: CanvasNode) => targetIds.has(n.id);\n\t\tlet next: CanvasNode[];\n\n\t\tif (operation === 'bringToFront') {\n\t\t\tnext = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];\n\t\t} else if (operation === 'sendToBack') {\n\t\t\tnext = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];\n\t\t} else {\n\t\t\tnext = [...order];\n\t\t\tconst forward = operation === 'bringForward';\n\t\t\tconst rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));\n\t\t\tconst intersects = (a: Rect, b: Rect) =>\n\t\t\t\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n\t\t\t// Walk from the end we are moving towards so adjacent targets keep their relative order.\n\t\t\tconst indices = next.map((_, i) => i);\n\t\t\tif (forward) indices.reverse();\n\t\t\tfor (const i of indices) {\n\t\t\t\tconst node = next[i];\n\t\t\t\tif (!isTarget(node)) continue;\n\t\t\t\tconst step = forward ? 1 : -1;\n\t\t\t\tlet j = i + step;\n\t\t\t\tlet swapWith = -1;\n\t\t\t\tlet firstNonTarget = -1;\n\t\t\t\twhile (j >= 0 && j < next.length) {\n\t\t\t\t\tif (!isTarget(next[j])) {\n\t\t\t\t\t\tif (firstNonTarget === -1) firstNonTarget = j;\n\t\t\t\t\t\tif (intersects(rects.get(node.id)!, rects.get(next[j].id)!)) {\n\t\t\t\t\t\t\tswapWith = j;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tj += step;\n\t\t\t\t}\n\t\t\t\tif (swapWith === -1) swapWith = firstNonTarget;\n\t\t\t\tif (swapWith === -1) continue;\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\tnext.splice(swapWith, 0, node);\n\t\t\t}\n\t\t}\n\n\t\tif (next.every((node, i) => node === order[i])) continue;\n\t\tnext.forEach((node, rank) => {\n\t\t\tconst current = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\t\tif (current !== rank) updates.push({ id: node.id, zIndex: rank });\n\t\t});\n\t}\n\treturn updates;\n}\n\n/**\n * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep\n * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present\n * in `style` (string or object). An existing `measured` size is updated too, so geometry\n * helpers see the new size before xyflow re-measures the node.\n */\nexport function applyNodeUpdates<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tupdates: CanvasNodeUpdate[]\n): TNode[] {\n\tif (updates.length === 0) return nodes;\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const update of updates) {\n\t\tbyId.set(update.id, { ...byId.get(update.id), ...update });\n\t}\n\treturn nodes.map((node) => {\n\t\tconst update = byId.get(node.id);\n\t\tif (!update) return node;\n\t\tconst next = { ...node } as TNode;\n\t\tif (update.position) next.position = { ...update.position };\n\t\tif (update.zIndex !== undefined) (next as CanvasNode).zIndex = update.zIndex;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tif (update.width !== undefined) next.width = update.width;\n\t\t\tif (update.height !== undefined) next.height = update.height;\n\t\t\tif (node.style) next.style = withStyleSize(node.style, update);\n\t\t\tif (node.measured) {\n\t\t\t\tnext.measured = {\n\t\t\t\t\t...node.measured,\n\t\t\t\t\t...(update.width !== undefined ? { width: update.width } : {}),\n\t\t\t\t\t...(update.height !== undefined ? { height: update.height } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t});\n}\n\n/**\n * Inverse of a set of updates against the current nodes, for undo. Restores the previous\n * position / size / zIndex of every node an update touches.\n */\nexport function invertNodeUpdates(\n\tnodes: CanvasNode[],\n\tupdates: CanvasNodeUpdate[]\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst inverse: CanvasNodeUpdate[] = [];\n\tfor (const update of updates) {\n\t\tconst node = lookup.get(update.id);\n\t\tif (!node) continue;\n\t\tconst back: CanvasNodeUpdate = { id: node.id };\n\t\tif (update.position) back.position = { ...node.position };\n\t\tif (update.zIndex !== undefined) back.zIndex = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tconst size = getNodeSize(node);\n\t\t\tif (update.width !== undefined) back.width = size.width;\n\t\t\tif (update.height !== undefined) back.height = size.height;\n\t\t}\n\t\tinverse.push(back);\n\t}\n\treturn inverse;\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/**\n * A guide line to render while dragging, in flow coordinates.\n * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.\n * `measure` lines (with a distance `label`) and dashed `extension` lines come from the\n * distance measuring helpers (`measureBetween`, `measureSelection`).\n */\nexport interface GuideLine {\n\torientation: 'vertical' | 'horizontal';\n\tkind: 'edge' | 'centre' | 'spacing' | 'measure' | 'extension';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n\t/** Text drawn at the middle of the line (measuring distances). */\n\tlabel?: string;\n}\n\n/**\n * Snaps `value` to a grid with the given `spacing` and `origin`. A spacing of 0 or less\n * (or a non-finite one) leaves the value unchanged.\n *\n * Ported from vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nexport function snapToGrid(value: number, spacing: number, origin = 0): number {\n\tif (!(spacing > 0) || !Number.isFinite(spacing)) return value;\n\treturn Math.round((value - origin) / spacing) * spacing + origin;\n}\n\nexport interface SnapGuidesOptions {\n\t/** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** Snap distance in screen pixels (default 6). */\n\tthreshold?: number;\n\t/** Snap edges to other shapes' edges (default `true`). */\n\tedges?: boolean;\n\t/** Snap centres to other shapes' centres (default `true`). */\n\tcentres?: boolean;\n\t/** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */\n\tspacing?: boolean;\n\t/**\n\t * Grid spacing in flow units (default 0, off). An axis that does not snap to a shape snaps\n\t * its top-left (drag) or moving edge (resize) to the grid; grid snaps draw no guides.\n\t */\n\tgridSize?: number;\n\t/** Grid origin in flow units (default `{ x: 0, y: 0 }`). */\n\tgridOrigin?: XYPosition;\n}\n\nexport interface SnapResult {\n\t/** Snapped top-left of the moving rectangle. */\n\tposition: XYPosition;\n\t/** Offset applied to the input rectangle (`position - rect`). */\n\toffset: XYPosition;\n\t/** Guide segments to draw for the snap that was applied. */\n\tguides: GuideLine[];\n\t/** Whether x snapped to a shape (grid snaps do not count). */\n\tsnappedX: boolean;\n\tsnappedY: boolean;\n}\n\ninterface AxisEntries {\n\tvalues: Float64Array;\n\trects: Rect[];\n}\n\nfunction buildAxis(entries: { value: number; rect: Rect }[]): AxisEntries {\n\tentries.sort((a, b) => a.value - b.value);\n\treturn {\n\t\tvalues: Float64Array.from(entries.map((e) => e.value)),\n\t\trects: entries.map((e) => e.rect)\n\t};\n}\n\n/** First index whose value is >= `v`. */\nfunction lowerBound(values: Float64Array, v: number): number {\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/**\n * Pre-sorted snap targets for a set of static rectangles. Build it once when a drag starts\n * and reuse it on every pointer move; each query is a binary search, so snapping stays\n * cheap with hundreds or thousands of shapes.\n */\nexport interface GuideIndex {\n\trects: Rect[];\n\tedgeX: AxisEntries;\n\tcentreX: AxisEntries;\n\tedgeY: AxisEntries;\n\tcentreY: AxisEntries;\n}\n\n/** Builds a `GuideIndex` from the rectangles the moving shape may snap to. */\nexport function createGuideIndex(rects: Rect[]): GuideIndex {\n\tconst edgeX: { value: number; rect: Rect }[] = [];\n\tconst centreX: { value: number; rect: Rect }[] = [];\n\tconst edgeY: { value: number; rect: Rect }[] = [];\n\tconst centreY: { value: number; rect: Rect }[] = [];\n\tfor (const rect of rects) {\n\t\tedgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });\n\t\tcentreX.push({ value: rect.x + rect.width / 2, rect });\n\t\tedgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });\n\t\tcentreY.push({ value: rect.y + rect.height / 2, rect });\n\t}\n\treturn {\n\t\trects,\n\t\tedgeX: buildAxis(edgeX),\n\t\tcentreX: buildAxis(centreX),\n\t\tedgeY: buildAxis(edgeY),\n\t\tcentreY: buildAxis(centreY)\n\t};\n}\n\n/** Nearest indexed value within `tolerance` of `v`, as a signed delta, or `null`. */\nfunction nearest(axis: AxisEntries, v: number, tolerance: number): number | null {\n\tconst { values } = axis;\n\tlet best: number | null = null;\n\tfor (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {\n\t\tconst delta = values[i] - v;\n\t\tif (best === null || Math.abs(delta) < Math.abs(best)) best = delta;\n\t}\n\treturn best;\n}\n\n/** Rectangles whose indexed value equals `v` (within `eps`). */\nfunction matches(axis: AxisEntries, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {\n\t\tout.push(rects[i]);\n\t}\n\treturn out;\n}\n\ninterface SpacingCandidate {\n\tdelta: number;\n\tguides: GuideLine[];\n}\n\n/**\n * Equal-spacing candidates along one axis. Looks at shapes in the same row (horizontal) or\n * column (vertical) as the moving rectangle and proposes: centring between the nearest\n * neighbours, or repeating the gap a neighbour has to its own neighbour.\n */\nfunction spacingCandidate(\n\trects: Rect[],\n\tmoving: Rect,\n\thorizontal: boolean,\n\ttolerance: number\n): SpacingCandidate | null {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst end = (r: Rect) => (horizontal ? r.x + r.width : r.y + r.height);\n\tconst crossStart = (r: Rect) => (horizontal ? r.y : r.x);\n\tconst crossEnd = (r: Rect) => (horizontal ? r.y + r.height : r.x + r.width);\n\tconst size = end(moving) - start(moving);\n\tconst inLane = (r: Rect, lane: Rect) => crossStart(r) < crossEnd(lane) && crossEnd(r) > crossStart(lane);\n\n\tconst lane = rects.filter((r) => inLane(r, moving));\n\tif (lane.length === 0) return null;\n\n\tlet before: Rect | null = null;\n\tlet after: Rect | null = null;\n\tfor (const r of lane) {\n\t\tif (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;\n\t\tif (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;\n\t}\n\n\tconst neighbourOf = (r: Rect, forward: boolean): Rect | null => {\n\t\tlet best: Rect | null = null;\n\t\tfor (const other of lane) {\n\t\t\tif (other === r || !inLane(other, r)) continue;\n\t\t\tif (forward ? start(other) >= end(r) : end(other) <= start(r)) {\n\t\t\t\tif (!best || (forward ? start(other) < start(best) : end(other) > end(best))) best = other;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst crossMid = (a: Rect, b: Rect) =>\n\t\t(Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;\n\tconst segment = (from: number, to: number, cross: number): GuideLine =>\n\t\thorizontal\n\t\t\t? { orientation: 'horizontal', kind: 'spacing', x1: from, y1: cross, x2: to, y2: cross }\n\t\t\t: { orientation: 'vertical', kind: 'spacing', x1: cross, y1: from, x2: cross, y2: to };\n\n\tconst candidates: { pos: number; guides: (pos: number) => GuideLine[] }[] = [];\n\tif (before && after) {\n\t\tconst b = before;\n\t\tconst a = after;\n\t\tconst pos = (end(b) + start(a) - size) / 2;\n\t\tif (pos >= end(b)) {\n\t\t\tcandidates.push({\n\t\t\t\tpos,\n\t\t\t\tguides: (p) => [\n\t\t\t\t\tsegment(end(b), p, crossMid(b, moving)),\n\t\t\t\t\tsegment(p + size, start(a), crossMid(a, moving))\n\t\t\t\t]\n\t\t\t});\n\t\t}\n\t}\n\tif (before) {\n\t\tconst b = before;\n\t\tconst prev = neighbourOf(b, false);\n\t\tif (prev) {\n\t\t\tconst gap = start(b) - end(prev);\n\t\t\tcandidates.push({\n\t\t\t\tpos: end(b) + gap,\n\t\t\t\tguides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]\n\t\t\t});\n\t\t}\n\t}\n\tif (after) {\n\t\tconst a = after;\n\t\tconst next = neighbourOf(a, true);\n\t\tif (next) {\n\t\t\tconst gap = start(next) - end(a);\n\t\t\tcandidates.push({\n\t\t\t\tpos: start(a) - gap - size,\n\t\t\t\tguides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet best: SpacingCandidate | null = null;\n\tfor (const c of candidates) {\n\t\tconst delta = c.pos - start(moving);\n\t\tif (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {\n\t\t\tbest = { delta, guides: c.guides(c.pos) };\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Computes smart-guide snapping for a moving rectangle (a dragged node or a selection's\n * bounding box) against other shapes. Edges snap to edges and centres to centres within\n * `threshold` screen pixels (converted to flow units via `zoom`). Each axis snaps\n * independently to its nearest candidate. Returns the snapped position and the guides to draw.\n */\nexport function computeSnapGuides(\n\tmoving: Rect,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapGuidesOptions = {}\n): SnapResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst useSpacing = options.spacing !== false;\n\tconst eps = 0.5 / zoom;\n\n\tconst snapAxis = (horizontal: boolean) => {\n\t\tconst start = horizontal ? moving.x : moving.y;\n\t\tconst size = horizontal ? moving.width : moving.height;\n\t\tconst edgeAxis = horizontal ? index.edgeX : index.edgeY;\n\t\tconst centreAxis = horizontal ? index.centreX : index.centreY;\n\t\tlet delta: number | null = null;\n\t\tconst consider = (d: number | null) => {\n\t\t\tif (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;\n\t\t};\n\t\tif (useEdges) {\n\t\t\tconsider(nearest(edgeAxis, start, tolerance));\n\t\t\tconsider(nearest(edgeAxis, start + size, tolerance));\n\t\t}\n\t\tif (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));\n\t\tconst spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;\n\t\tif (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {\n\t\t\treturn { delta: spacing.delta, spacing };\n\t\t}\n\t\treturn { delta: delta as number | null, spacing: null };\n\t};\n\n\tconst sx = snapAxis(true);\n\tconst sy = snapAxis(false);\n\tconst grid = options.gridSize ?? 0;\n\tconst gridDelta = (v: number, origin: number) => snapToGrid(v, grid, origin) - v;\n\tconst dx = sx.delta ?? (grid > 0 ? gridDelta(moving.x, options.gridOrigin?.x ?? 0) : 0);\n\tconst dy = sy.delta ?? (grid > 0 ? gridDelta(moving.y, options.gridOrigin?.y ?? 0) : 0);\n\tconst snapped: Rect = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };\n\tconst guides: GuideLine[] = [];\n\n\tconst alignmentGuides = (horizontal: boolean) => {\n\t\tconst start = horizontal ? snapped.x : snapped.y;\n\t\tconst size = horizontal ? snapped.width : snapped.height;\n\t\tconst lines: { value: number; kind: 'edge' | 'centre'; axis: AxisEntries }[] = [];\n\t\tif (useEdges) {\n\t\t\tconst axis = horizontal ? index.edgeX : index.edgeY;\n\t\t\tlines.push({ value: start, kind: 'edge', axis }, { value: start + size, kind: 'edge', axis });\n\t\t}\n\t\tif (useCentres) {\n\t\t\tlines.push({ value: start + size / 2, kind: 'centre', axis: horizontal ? index.centreX : index.centreY });\n\t\t}\n\t\tfor (const line of lines) {\n\t\t\tconst aligned = matches(line.axis, line.value, eps);\n\t\t\tif (aligned.length === 0) continue;\n\t\t\tconst span = boundsOf([snapped, ...aligned])!;\n\t\t\tguides.push(\n\t\t\t\thorizontal\n\t\t\t\t\t? { orientation: 'vertical', kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height }\n\t\t\t\t\t: { orientation: 'horizontal', kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }\n\t\t\t);\n\t\t}\n\t};\n\n\tif (sx.delta !== null) {\n\t\tif (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));\n\t\telse alignmentGuides(true);\n\t}\n\tif (sy.delta !== null) {\n\t\tif (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));\n\t\telse alignmentGuides(false);\n\t}\n\n\treturn {\n\t\tposition: { x: snapped.x, y: snapped.y },\n\t\toffset: { x: dx, y: dy },\n\t\tguides,\n\t\tsnappedX: sx.delta !== null,\n\t\tsnappedY: sy.delta !== null\n\t};\n}\n\n/** Spacing guides are computed before the other axis snaps; shift their cross coordinate to match. */\nfunction shiftGuides(guides: GuideLine[], dx: number, dy: number): GuideLine[] {\n\treturn guides.map((g) =>\n\t\tg.orientation === 'horizontal'\n\t\t\t? { ...g, y1: g.y1 + dy, y2: g.y2 + dy }\n\t\t\t: { ...g, x1: g.x1 + dx, x2: g.x2 + dx }\n\t);\n}\n\n/** Ids of the given nodes plus every descendant (children move with their parent). */\nexport function collectDescendantIds(nodes: CanvasNode[], ids: Iterable<string>): Set<string> {\n\tconst result = new Set(ids);\n\tlet grew = true;\n\twhile (grew) {\n\t\tgrew = false;\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && result.has(node.parentId) && !result.has(node.id)) {\n\t\t\t\tresult.add(node.id);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Builds a `GuideIndex` of every node except the dragged ones and their descendants.\n * Call once at drag start and pass it to `snapNodeDrag` on every move.\n */\nexport function createNodeGuideIndex(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: NodeRectOptions & { filter?: (node: CanvasNode) => boolean } = {}\n): GuideIndex {\n\tconst exclude = collectDescendantIds(nodes, draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst rects: Rect[] = [];\n\tfor (const node of nodes) {\n\t\tif (exclude.has(node.id) || node.hidden === true) continue;\n\t\tif (options.filter && !options.filter(node)) continue;\n\t\trects.push(getNodeRect(node, lookup, options));\n\t}\n\treturn createGuideIndex(rects);\n}\n\nexport interface SnapNodeDragOptions extends SnapGuidesOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex`, reused across drag frames. Built on demand when omitted. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeDragResult extends SnapResult {\n\t/** Position updates moving every dragged node by `offset` (node-relative coordinates). */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a drag of one or more nodes. The dragged nodes' bounding box is snapped against all\n * other nodes; the resulting offset is applied to each dragged node. Dragged nodes whose\n * parent is also dragged are skipped, because they move with the parent.\n */\nexport function snapNodeDrag(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: SnapNodeDragOptions = {}\n): SnapNodeDragResult {\n\tconst ids = new Set(draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst dragged = [...ids].map((id) => lookup.get(id)).filter((n): n is CanvasNode => !!n);\n\tconst empty: SnapNodeDragResult = {\n\t\tposition: { x: 0, y: 0 },\n\t\toffset: { x: 0, y: 0 },\n\t\tguides: [],\n\t\tsnappedX: false,\n\t\tsnappedY: false,\n\t\tupdates: []\n\t};\n\tif (dragged.length === 0) return empty;\n\n\tconst bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)))!;\n\tconst index = options.index ?? createNodeGuideIndex(nodes, ids, options);\n\tconst result = computeSnapGuides(bounds, index, options);\n\tconst { x: dx, y: dy } = result.offset;\n\tconst updates: CanvasNodeUpdate[] = [];\n\tif (dx !== 0 || dy !== 0) {\n\t\tfor (const node of dragged) {\n\t\t\tif (node.parentId && ids.has(node.parentId)) continue;\n\t\t\tupdates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });\n\t\t}\n\t}\n\treturn { ...result, updates };\n}\n","import { centre } from './layout.js';\nimport type { Position, Rect, Side, XYPosition } from './types.js';\n\n/**\n * Connection line styles supported by both SvelteFlow and React Flow\n * (`connectionLineType` / edge `type`).\n */\nexport type CanvasConnectionLineType = 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';\n\nexport const CONNECTION_LINE_TYPES: readonly CanvasConnectionLineType[] = [\n\t'default',\n\t'straight',\n\t'step',\n\t'smoothstep',\n\t'simplebezier'\n];\n\n/** Maps a handle `Position` to the placement `Side` vocabulary used by `placeBeside`. */\nexport function positionToSide(position: Position): Side {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\t\treturn 'below';\n\t\tdefault:\n\t\t\treturn position;\n\t}\n}\n\n/** Maps a placement `Side` to the handle `Position` vocabulary used by xyflow. */\nexport function sideToPosition(side: Side): Position {\n\tswitch (side) {\n\t\tcase 'above':\n\t\t\treturn 'top';\n\t\tcase 'below':\n\t\t\treturn 'bottom';\n\t\tdefault:\n\t\t\treturn side;\n\t}\n}\n\n/** The handle position facing the other way. */\nexport function oppositePosition(position: Position): Position {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'bottom';\n\t\tcase 'bottom':\n\t\t\treturn 'top';\n\t\tcase 'left':\n\t\t\treturn 'right';\n\t\tdefault:\n\t\t\treturn 'left';\n\t}\n}\n\n/** Midpoint of a rectangle's side, where a centred handle sits. */\nexport function handlePoint(rect: Rect, position: Position): XYPosition {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: rect.y + rect.height / 2 };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: rect.y + rect.height / 2 };\n\t}\n}\n\nexport interface ConnectorSnapTarget {\n\tid: string;\n\trect: Rect;\n\t/** Sides that can accept a connection (default: all four). */\n\tpositions?: Position[];\n}\n\nexport interface ConnectorSnapOptions {\n\t/** Viewport zoom; `radius` is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** How far outside a shape the pointer may be and still attach, in screen pixels (default 24). */\n\tradius?: number;\n\t/** Node ids that cannot be attached to, e.g. the connection's source node. */\n\texcludeIds?: Iterable<string>;\n\t/**\n\t * `handle` snaps to the side's midpoint (a centred handle, default). `edge` snaps to the\n\t * closest point along that side, for floating edges.\n\t */\n\tmode?: 'handle' | 'edge';\n}\n\nexport interface ConnectorSnap {\n\tnodeId: string;\n\tposition: Position;\n\tside: Side;\n\t/** Attachment point in flow coordinates. */\n\tpoint: XYPosition;\n\t/** Distance from the pointer to `point`, in flow units. */\n\tdistance: number;\n}\n\n/**\n * Finds the shape side a connection line should attach to for a pointer (flow coordinates).\n * Shapes the pointer is inside win over shapes it is merely near; among those, the closest\n * allowed side wins. Returns `null` when nothing is within `radius`.\n */\nexport function findConnectorSnap(\n\tpointer: XYPosition,\n\ttargets: ConnectorSnapTarget[],\n\toptions: ConnectorSnapOptions = {}\n): ConnectorSnap | null {\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst radius = (options.radius ?? 24) / zoom;\n\tconst exclude = new Set(options.excludeIds ?? []);\n\tconst all: Position[] = ['top', 'right', 'bottom', 'left'];\n\n\tlet best: (ConnectorSnap & { inside: boolean; rank: number }) | null = null;\n\tfor (const target of targets) {\n\t\tif (exclude.has(target.id)) continue;\n\t\tconst { rect } = target;\n\t\t// Cheap rejection: pointer outside the rect grown by the radius.\n\t\tif (\n\t\t\tpointer.x < rect.x - radius ||\n\t\t\tpointer.x > rect.x + rect.width + radius ||\n\t\t\tpointer.y < rect.y - radius ||\n\t\t\tpointer.y > rect.y + rect.height + radius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst inside =\n\t\t\tpointer.x >= rect.x &&\n\t\t\tpointer.x <= rect.x + rect.width &&\n\t\t\tpointer.y >= rect.y &&\n\t\t\tpointer.y <= rect.y + rect.height;\n\n\t\tfor (const position of target.positions ?? all) {\n\t\t\tconst point =\n\t\t\t\toptions.mode === 'edge' ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);\n\t\t\t// Rank sides by distance from the pointer to the side line, so a pointer near the\n\t\t\t// middle of a wide shape attaches to the side it is closest to.\n\t\t\tconst sidePoint = closestPointOnSide(rect, position, pointer);\n\t\t\tconst rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);\n\t\t\tconst distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);\n\t\t\tif (!inside && rank > radius) continue;\n\t\t\tconst better =\n\t\t\t\t!best ||\n\t\t\t\t(inside && !best.inside) ||\n\t\t\t\t(inside === best.inside && rank < best.rank);\n\t\t\tif (better) {\n\t\t\t\tbest = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };\n\t\t\t}\n\t\t}\n\t}\n\tif (!best) return null;\n\tconst { inside: _inside, rank: _rank, ...snap } = best;\n\treturn snap;\n}\n\nfunction closestPointOnSide(rect: Rect, position: Position, p: XYPosition): XYPosition {\n\tconst clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));\n\tconst clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: clampX, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: clampX, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: clampY };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: clampY };\n\t}\n}\n\n/**\n * Picks the facing sides for a connection between two shapes, the way Freeform attaches\n * connection lines: along the axis with the larger centre offset.\n */\nexport function nearestHandlePositions(\n\tsource: Rect,\n\ttarget: Rect\n): { source: Position; target: Position } {\n\tconst a = centre(source);\n\tconst b = centre(target);\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tif (Math.abs(dx) >= Math.abs(dy)) {\n\t\treturn dx >= 0 ? { source: 'right', target: 'left' } : { source: 'left', target: 'right' };\n\t}\n\treturn dy >= 0 ? { source: 'bottom', target: 'top' } : { source: 'top', target: 'bottom' };\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/**\n * Viewport transform in xyflow's convention: `screen = flow * zoom + (x, y)`.\n */\nexport interface CanvasViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/**\n * A named, saved view of the canvas (Freeform \"Scenes\").\n */\nexport interface CanvasScene {\n\tid: string;\n\tname: string;\n\tviewport: CanvasViewport;\n\t/** Optional nodes the scene is about, e.g. to re-fit the scene after they move. */\n\tnodeIds?: string[];\n}\n\nexport interface CanvasScenesState {\n\tscenes: readonly CanvasScene[];\n\t/** Id of the scene last shown, or `null`. */\n\tcurrentId: string | null;\n\t/** Index of the current scene, or `-1`. */\n\tcurrentIndex: number;\n}\n\nexport interface CanvasScenesOptions {\n\tinitialScenes?: CanvasScene[];\n\t/** Id generator for new scenes (default: `crypto.randomUUID` with a counter fallback). */\n\tcreateId?: () => string;\n\t/** Whether `next` / `previous` wrap around at the ends (default `false`). */\n\twrap?: boolean;\n\tonChange?: (state: CanvasScenesState) => void;\n}\n\nexport interface SerializedCanvasScenes {\n\tversion: 1;\n\tscenes: CanvasScene[];\n}\n\nlet fallbackCounter = 0;\nfunction defaultId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID();\n\t}\n\tfallbackCounter += 1;\n\treturn `scene-${Date.now().toString(36)}-${fallbackCounter}`;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Validates the shape of a scene. */\nexport function isValidCanvasScene(value: unknown): value is CanvasScene {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst scene = value as Record<string, any>;\n\tconst vp = scene.viewport;\n\treturn (\n\t\ttypeof scene.id === 'string' &&\n\t\ttypeof scene.name === 'string' &&\n\t\ttypeof vp === 'object' &&\n\t\tvp !== null &&\n\t\tisFiniteNumber(vp.x) &&\n\t\tisFiniteNumber(vp.y) &&\n\t\tisFiniteNumber(vp.zoom) &&\n\t\tvp.zoom > 0 &&\n\t\t(scene.nodeIds === undefined ||\n\t\t\t(Array.isArray(scene.nodeIds) && scene.nodeIds.every((id: unknown) => typeof id === 'string')))\n\t);\n}\n\nfunction cloneScene(scene: CanvasScene): CanvasScene {\n\tconst copy: CanvasScene = {\n\t\tid: scene.id,\n\t\tname: scene.name,\n\t\tviewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }\n\t};\n\tif (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];\n\treturn copy;\n}\n\n/**\n * Parses serialized scenes (a JSON string, a `{ version, scenes }` object or a bare array).\n * Invalid entries and duplicate ids are dropped.\n */\nexport function parseCanvasScenes(input: string | unknown): CanvasScene[] {\n\tlet value: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst list = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === 'object' && value !== null && Array.isArray((value as any).scenes)\n\t\t\t? (value as any).scenes\n\t\t\t: [];\n\tconst seen = new Set<string>();\n\tconst scenes: CanvasScene[] = [];\n\tfor (const entry of list) {\n\t\tif (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;\n\t\tseen.add(entry.id);\n\t\tscenes.push(cloneScene(entry));\n\t}\n\treturn scenes;\n}\n\n/**\n * Headless, framework-agnostic store of named viewports. Snapshots returned by `getState()`\n * are cached and only replaced on change, so it can back React's `useSyncExternalStore`.\n */\nexport class CanvasScenesStore {\n\tprivate scenes: CanvasScene[];\n\tprivate currentId: string | null = null;\n\tprivate cached: CanvasScenesState;\n\tprivate listeners = new Set<(state: CanvasScenesState) => void>();\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (state: CanvasScenesState) => void;\n\twrap: boolean;\n\n\tconstructor(options: CanvasScenesOptions = {}) {\n\t\tthis.scenes = parseCanvasScenes(options.initialScenes ?? []);\n\t\tthis.createId = options.createId ?? defaultId;\n\t\tthis.wrap = options.wrap ?? false;\n\t\tthis.onChange = options.onChange;\n\t\tthis.cached = this.snapshot();\n\t}\n\n\tprivate snapshot(): CanvasScenesState {\n\t\tconst currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;\n\t\treturn {\n\t\t\tscenes: this.scenes,\n\t\t\tcurrentId: currentIndex === -1 ? null : this.currentId,\n\t\t\tcurrentIndex\n\t\t};\n\t}\n\n\tprivate commit(scenes: CanvasScene[] = this.scenes) {\n\t\tthis.scenes = scenes;\n\t\tif (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;\n\t\tthis.cached = this.snapshot();\n\t\tthis.onChange?.(this.cached);\n\t\tfor (const listener of this.listeners) listener(this.cached);\n\t}\n\n\tgetState(): CanvasScenesState {\n\t\treturn this.cached;\n\t}\n\n\tgetScene(id: string): CanvasScene | undefined {\n\t\treturn this.scenes.find((s) => s.id === id);\n\t}\n\n\tsubscribe(listener: (state: CanvasScenesState) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** First unused \"Scene N\" name. */\n\tnextDefaultName(): string {\n\t\tconst names = new Set(this.scenes.map((s) => s.name));\n\t\tlet n = this.scenes.length + 1;\n\t\twhile (names.has(`Scene ${n}`)) n += 1;\n\t\treturn `Scene ${n}`;\n\t}\n\n\t/**\n\t * Adds a scene (appended, or inserted at `index`) and makes it current.\n\t */\n\tadd(input: { viewport: CanvasViewport; name?: string; nodeIds?: string[]; id?: string }, index?: number): CanvasScene {\n\t\tconst scene = cloneScene({\n\t\t\tid: input.id && !this.getScene(input.id) ? input.id : this.createId(),\n\t\t\tname: input.name?.trim() || this.nextDefaultName(),\n\t\t\tviewport: input.viewport,\n\t\t\tnodeIds: input.nodeIds\n\t\t});\n\t\tif (!isValidCanvasScene(scene)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tconst at = index === undefined ? scenes.length : Math.max(0, Math.min(scenes.length, index));\n\t\tscenes.splice(at, 0, scene);\n\t\tthis.currentId = scene.id;\n\t\tthis.commit(scenes);\n\t\treturn scene;\n\t}\n\n\t/** Patches a scene's name, viewport or node ids. Returns the updated scene. */\n\tupdate(id: string, patch: Partial<Omit<CanvasScene, 'id'>>): CanvasScene | undefined {\n\t\tconst index = this.scenes.findIndex((s) => s.id === id);\n\t\tif (index === -1) return undefined;\n\t\tconst current = this.scenes[index];\n\t\tconst next = cloneScene({\n\t\t\t...current,\n\t\t\t...patch,\n\t\t\tname: patch.name !== undefined ? patch.name.trim() || current.name : current.name,\n\t\t\tviewport: patch.viewport ?? current.viewport,\n\t\t\tid\n\t\t});\n\t\tif (!isValidCanvasScene(next)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tscenes[index] = next;\n\t\tthis.commit(scenes);\n\t\treturn next;\n\t}\n\n\trename(id: string, name: string): CanvasScene | undefined {\n\t\treturn this.update(id, { name });\n\t}\n\n\t/** Moves a scene to `toIndex` (clamped). */\n\tmove(id: string, toIndex: number): void {\n\t\tconst from = this.scenes.findIndex((s) => s.id === id);\n\t\tif (from === -1) return;\n\t\tconst to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));\n\t\tif (from === to) return;\n\t\tconst scenes = [...this.scenes];\n\t\tconst [scene] = scenes.splice(from, 1);\n\t\tscenes.splice(to, 0, scene);\n\t\tthis.commit(scenes);\n\t}\n\n\t/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */\n\treorder(ids: string[]): void {\n\t\tconst byId = new Map(this.scenes.map((s) => [s.id, s]));\n\t\tconst ordered: CanvasScene[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst scene = byId.get(id);\n\t\t\tif (scene) {\n\t\t\t\tordered.push(scene);\n\t\t\t\tbyId.delete(id);\n\t\t\t}\n\t\t}\n\t\tthis.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.getScene(id)) return;\n\t\tthis.commit(this.scenes.filter((s) => s.id !== id));\n\t}\n\n\tclear(): void {\n\t\tthis.currentId = null;\n\t\tthis.commit([]);\n\t}\n\n\t/** Marks a scene as current and returns it. */\n\tgoTo(id: string): CanvasScene | undefined {\n\t\tconst scene = this.getScene(id);\n\t\tif (!scene) return undefined;\n\t\tif (this.currentId !== id) {\n\t\t\tthis.currentId = id;\n\t\t\tthis.commit();\n\t\t}\n\t\treturn scene;\n\t}\n\n\tprivate step(direction: 1 | -1): CanvasScene | undefined {\n\t\tconst count = this.scenes.length;\n\t\tif (count === 0) return undefined;\n\t\tconst index = this.cached.currentIndex;\n\t\tlet next: number;\n\t\tif (index === -1) next = direction === 1 ? 0 : count - 1;\n\t\telse next = index + direction;\n\t\tif (next < 0 || next >= count) {\n\t\t\tif (!this.wrap) return undefined;\n\t\t\tnext = (next + count) % count;\n\t\t}\n\t\treturn this.goTo(this.scenes[next].id);\n\t}\n\n\t/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */\n\tnext(): CanvasScene | undefined {\n\t\treturn this.step(1);\n\t}\n\n\t/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */\n\tprevious(): CanvasScene | undefined {\n\t\treturn this.step(-1);\n\t}\n\n\ttoJSON(): SerializedCanvasScenes {\n\t\treturn { version: 1, scenes: this.scenes.map(cloneScene) };\n\t}\n\n\tserialize(): string {\n\t\treturn JSON.stringify(this.toJSON());\n\t}\n\n\t/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */\n\tload(input: string | unknown): void {\n\t\tthis.currentId = null;\n\t\tthis.commit(parseCanvasScenes(input));\n\t}\n}\n\nexport function createCanvasScenesStore(options?: CanvasScenesOptions): CanvasScenesStore {\n\treturn new CanvasScenesStore(options);\n}\n\nexport interface FitViewportOptions {\n\t/** Padding around the content as a fraction of the content size per side (default 0.1, like xyflow). */\n\tpadding?: number;\n\tminZoom?: number;\n\tmaxZoom?: number;\n}\n\n/**\n * Viewport that fits `rect` into a container of `size`, centred (xyflow `fitView` maths).\n */\nexport function fitViewportToRect(\n\trect: Rect,\n\tsize: { width: number; height: number },\n\toptions: FitViewportOptions = {}\n): CanvasViewport {\n\tconst { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;\n\tconst paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);\n\tconst paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);\n\tconst raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);\n\tconst zoom = Math.min(maxZoom, Math.max(minZoom, raw));\n\tconst cx = rect.x + rect.width / 2;\n\tconst cy = rect.y + rect.height / 2;\n\treturn { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };\n}\n\n/**\n * Viewport that fits the given nodes (all nodes when `ids` is omitted), or `null` when none match.\n */\nexport function fitViewportToNodes(\n\tnodes: CanvasNode[],\n\tsize: { width: number; height: number },\n\tids?: Iterable<string>,\n\toptions: FitViewportOptions & NodeRectOptions = {}\n): CanvasViewport | null {\n\tconst lookup = createNodeLookup(nodes);\n\tconst wanted = ids ? new Set(ids) : null;\n\tconst rects = nodes\n\t\t.filter((n) => (wanted ? wanted.has(n.id) : true))\n\t\t.map((n) => getNodeRect(n, lookup, options));\n\tconst bounds = boundsOf(rects);\n\treturn bounds ? fitViewportToRect(bounds, size, options) : null;\n}\n","/**\n * Polygon boolean operations (union, intersection, difference, xor and divide) on\n * multi-polygons with holes.\n *\n * Algorithm (overlay + winding classification):\n * 1. Every input edge is collected into one segment set. Vertices are merged through a\n * tolerance-aware point pool, so points closer than `epsilon` become one vertex.\n * 2. Segments are split at every crossing, T-junction and collinear overlap until no two\n * segments cross (a planar arrangement). Splitting is repeated because snapping a new\n * vertex can move a segment by up to `epsilon`.\n * 3. Coincident segments (shared edges) are merged, keeping for every operand the signed\n * number of its edges that run along the segment.\n * 4. For every segment the winding number of each operand on its right side is computed\n * with a ray cast from the segment midpoint (the segment itself excluded), and the left\n * side follows from the signed multiplicities. The fill rule turns windings into\n * inside / outside flags and the operation decides whether each side is in the result.\n * 5. Segments with the result on exactly one side are kept, oriented with the result on\n * their left, and traced into rings (taking the sharpest left turn at shared vertices,\n * so rings that touch at a vertex stay separate simple rings). Rings with a positive\n * signed area are outer rings; negative ones are holes, assigned to the smallest outer\n * ring that contains them.\n *\n * Handles shared edges, touching vertices, collinear overlaps, holes, nested and disjoint\n * inputs and self-intersecting input rings (resolved by the operand's fill rule).\n *\n * Limits: the overlay is O(n²) in the number of edges (pruned by a sweep on x), which is\n * comfortable for canvas shapes (thousands of edges) but not for GIS-scale data. Features\n * narrower than `epsilon` collapse. Coordinates must be finite.\n */\nimport type { Point } from './types.js';\n\n/** A closed ring of points. The closing point may be repeated or omitted. */\nexport type PolygonRing = Point[];\n/** A polygon: the outer ring followed by its holes. */\nexport type PolygonWithHoles = PolygonRing[];\n/** A set of polygons. */\nexport type MultiPolygon = PolygonWithHoles[];\n\n/** SVG fill rule used to decide which areas of a ring set are inside. */\nexport type PolygonFillRule = 'nonzero' | 'evenodd';\n\n/** Boolean operations. `xor` is Freeform's Exclude. */\nexport type PolygonBooleanOperation = 'union' | 'intersection' | 'difference' | 'xor';\n\n/**\n * A raw ring set with a fill rule (e.g. flattened SVG path data). Ring orientation is\n * significant for `nonzero`.\n */\nexport interface PolygonOperand {\n\trings: PolygonRing[];\n\tfillRule?: PolygonFillRule;\n}\n\nexport type PolygonInput = MultiPolygon | PolygonOperand;\n\nexport interface PolygonBooleanOptions {\n\t/**\n\t * Distance below which points are treated as equal. Defaults to `1e-9` times the\n\t * largest coordinate magnitude of the inputs (at least `1e-9`).\n\t */\n\tepsilon?: number;\n}\n\n/** One region of `dividePolygons`, with the indices of the operands that cover it. */\nexport interface PolygonPiece {\n\tpolygon: PolygonWithHoles;\n\toperands: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Basic measures\n// ---------------------------------------------------------------------------\n\n/** Signed shoelace area of a ring (positive when counter-clockwise in a y-up frame). */\nexport function ringSignedArea(ring: readonly Point[]): number {\n\tlet sum = 0;\n\tconst n = ring.length;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst [x1, y1] = ring[i];\n\t\tconst [x2, y2] = ring[(i + 1) % n];\n\t\tsum += x1 * y2 - x2 * y1;\n\t}\n\treturn sum / 2;\n}\n\n/** Area of a polygon (outer ring minus holes). */\nexport function polygonArea(polygon: PolygonWithHoles): number {\n\tif (polygon.length === 0) return 0;\n\tlet area = Math.abs(ringSignedArea(polygon[0]));\n\tfor (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));\n\treturn area;\n}\n\n/** Total area of a multi-polygon (holes subtracted). */\nexport function multiPolygonArea(multi: MultiPolygon): number {\n\tlet total = 0;\n\tfor (const polygon of multi) total += polygonArea(polygon);\n\treturn total;\n}\n\n/** Axis-aligned bounds of a multi-polygon (or ring list), or `null` when empty. */\nexport function multiPolygonBounds(\n\tinput: MultiPolygon | PolygonRing[]\n): { minX: number; minY: number; maxX: number; maxY: number } | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tconst visit = (ring: PolygonRing) => {\n\t\tfor (const [x, y] of ring) {\n\t\t\tif (x < minX) minX = x;\n\t\t\tif (y < minY) minY = y;\n\t\t\tif (x > maxX) maxX = x;\n\t\t\tif (y > maxY) maxY = y;\n\t\t}\n\t};\n\tfor (const item of input as any[]) {\n\t\tif (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {\n\t\t\tfor (const ring of item as PolygonRing[]) visit(ring);\n\t\t} else {\n\t\t\tvisit(item as PolygonRing);\n\t\t}\n\t}\n\treturn minX === Infinity ? null : { minX, minY, maxX, maxY };\n}\n\n/**\n * Whether a point is inside a ring set under a fill rule. Points exactly on an edge may\n * report either side.\n */\nexport function pointInRings(\n\tpoint: Point,\n\trings: readonly (readonly Point[])[],\n\tfillRule: PolygonFillRule = 'nonzero'\n): boolean {\n\tconst [px, py] = point;\n\tlet winding = 0;\n\tfor (const ring of rings) {\n\t\tconst n = ring.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst [ax, ay] = ring[i];\n\t\t\tconst [bx, by] = ring[(i + 1) % n];\n\t\t\tif (ay <= py && by > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding++;\n\t\t\t} else if (by <= py && ay > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding--;\n\t\t\t}\n\t\t}\n\t}\n\treturn fillRule === 'evenodd' ? (winding & 1) === 1 : winding !== 0;\n}\n\n/** Whether a point is inside a multi-polygon (holes excluded). */\nexport function pointInMultiPolygon(point: Point, multi: MultiPolygon): boolean {\n\treturn multi.some((polygon) => pointInRings(point, polygon, 'evenodd'));\n}\n\n// ---------------------------------------------------------------------------\n// Operand normalisation\n// ---------------------------------------------------------------------------\n\nfunction cleanRing(ring: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tconst last = out[out.length - 1];\n\t\tif (last && last[0] === p[0] && last[1] === p[1]) continue;\n\t\tout.push([p[0], p[1]]);\n\t}\n\twhile (out.length > 1) {\n\t\tconst first = out[0];\n\t\tconst last = out[out.length - 1];\n\t\tif (first[0] === last[0] && first[1] === last[1]) out.pop();\n\t\telse break;\n\t}\n\treturn out;\n}\n\nfunction isOperand(input: PolygonInput): input is PolygonOperand {\n\treturn !Array.isArray(input) && typeof input === 'object' && input !== null && Array.isArray(input.rings);\n}\n\ninterface NormalizedOperand {\n\trings: Point[][];\n\tfillRule: PolygonFillRule;\n}\n\n/**\n * Multi-polygons are re-oriented (outer rings positive, holes negative) and filled with\n * `nonzero`, so overlapping polygons in one operand behave as their union.\n */\nfunction normalizeInput(input: PolygonInput): NormalizedOperand {\n\tif (isOperand(input)) {\n\t\treturn {\n\t\t\trings: input.rings.map(cleanRing).filter((r) => r.length >= 3),\n\t\t\tfillRule: input.fillRule ?? 'nonzero'\n\t\t};\n\t}\n\tconst rings: Point[][] = [];\n\tfor (const polygon of input ?? []) {\n\t\tpolygon.forEach((raw, index) => {\n\t\t\tconst ring = cleanRing(raw);\n\t\t\tif (ring.length < 3) return;\n\t\t\tconst area = ringSignedArea(ring);\n\t\t\tif (area === 0) return;\n\t\t\tconst wantPositive = index === 0;\n\t\t\trings.push(area > 0 === wantPositive ? ring : ring.reverse());\n\t\t});\n\t}\n\treturn { rings, fillRule: 'nonzero' };\n}\n\n// ---------------------------------------------------------------------------\n// Overlay\n// ---------------------------------------------------------------------------\n\ninterface Vertex {\n\tid: number;\n\tx: number;\n\ty: number;\n}\n\n/** Merges points closer than `eps` (grid hash with neighbour lookup). */\nclass PointPool {\n\treadonly vertices: Vertex[] = [];\n\tprivate readonly cells = new Map<string, number[]>();\n\tconstructor(private readonly eps: number) {}\n\n\tget(x: number, y: number): Vertex {\n\t\tconst cx = Math.floor(x / this.eps);\n\t\tconst cy = Math.floor(y / this.eps);\n\t\tlet best: Vertex | null = null;\n\t\tlet bestDist = Infinity;\n\t\tfor (let dx = -1; dx <= 1; dx++) {\n\t\t\tfor (let dy = -1; dy <= 1; dy++) {\n\t\t\t\tconst ids = this.cells.get(`${cx + dx},${cy + dy}`);\n\t\t\t\tif (!ids) continue;\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst v = this.vertices[id];\n\t\t\t\t\tconst d = Math.hypot(v.x - x, v.y - y);\n\t\t\t\t\tif (d <= this.eps && d < bestDist) {\n\t\t\t\t\t\tbest = v;\n\t\t\t\t\t\tbestDist = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (best) return best;\n\t\tconst vertex: Vertex = { id: this.vertices.length, x, y };\n\t\tthis.vertices.push(vertex);\n\t\tconst key = `${cx},${cy}`;\n\t\tconst bucket = this.cells.get(key);\n\t\tif (bucket) bucket.push(vertex.id);\n\t\telse this.cells.set(key, [vertex.id]);\n\t\treturn vertex;\n\t}\n}\n\ninterface RawSegment {\n\ta: Vertex;\n\tb: Vertex;\n\toperand: number;\n}\n\ninterface Splits {\n\tsegment: RawSegment;\n\tpoints: Map<number, { t: number; v: Vertex }>;\n}\n\n/** Projection parameter of `p` on `a→b` and its distance to the segment line. */\nfunction project(a: Vertex, b: Vertex, p: Vertex): { t: number; dist: number } {\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tconst len2 = dx * dx + dy * dy;\n\tconst t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;\n\tconst cx = a.x + t * dx;\n\tconst cy = a.y + t * dy;\n\treturn { t, dist: Math.hypot(p.x - cx, p.y - cy) };\n}\n\nfunction addSplit(splits: Splits, t: number, v: Vertex) {\n\tconst { a, b } = splits.segment;\n\tif (v.id === a.id || v.id === b.id) return;\n\tif (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });\n}\n\n/** One pass: splits every segment at crossings / touches with other segments. */\nfunction splitPass(segments: RawSegment[], pool: PointPool, eps: number): { segments: RawSegment[]; changed: boolean } {\n\tconst order = segments\n\t\t.map((s, i) => ({\n\t\t\ti,\n\t\t\tminX: Math.min(s.a.x, s.b.x),\n\t\t\tmaxX: Math.max(s.a.x, s.b.x),\n\t\t\tminY: Math.min(s.a.y, s.b.y),\n\t\t\tmaxY: Math.max(s.a.y, s.b.y)\n\t\t}))\n\t\t.sort((p, q) => p.minX - q.minX);\n\tconst splits: Splits[] = segments.map((segment) => ({ segment, points: new Map() }));\n\n\tfor (let oi = 0; oi < order.length; oi++) {\n\t\tconst bi = order[oi];\n\t\tconst s1 = segments[bi.i];\n\t\tfor (let oj = oi + 1; oj < order.length; oj++) {\n\t\t\tconst bj = order[oj];\n\t\t\tif (bj.minX > bi.maxX + eps) break;\n\t\t\tif (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;\n\t\t\tconst s2 = segments[bj.i];\n\t\t\tconst sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;\n\t\t\tconst sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;\n\t\t\tif (sharedA && sharedB) continue; // identical (or reversed) segment\n\n\t\t\tlet touched = false;\n\t\t\t// Endpoints of one segment lying on the other: T-junctions and collinear overlaps.\n\t\t\tfor (const [seg, other, split] of [\n\t\t\t\t[s1, s2, splits[bi.i]],\n\t\t\t\t[s2, s1, splits[bj.i]]\n\t\t\t] as const) {\n\t\t\t\tfor (const p of [other.a, other.b]) {\n\t\t\t\t\tif (p.id === seg.a.id || p.id === seg.b.id) continue;\n\t\t\t\t\tconst { t, dist } = project(seg.a, seg.b, p);\n\t\t\t\t\tconst len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);\n\t\t\t\t\tif (dist <= eps && t * len > eps && (1 - t) * len > eps) {\n\t\t\t\t\t\taddSplit(split, t, p);\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (touched) continue;\n\n\t\t\t// Proper crossing.\n\t\t\tconst d1x = s1.b.x - s1.a.x;\n\t\t\tconst d1y = s1.b.y - s1.a.y;\n\t\t\tconst d2x = s2.b.x - s2.a.x;\n\t\t\tconst d2y = s2.b.y - s2.a.y;\n\t\t\tconst denom = d1x * d2y - d1y * d2x;\n\t\t\tconst len1 = Math.hypot(d1x, d1y);\n\t\t\tconst len2 = Math.hypot(d2x, d2y);\n\t\t\tif (Math.abs(denom) <= 1e-12 * len1 * len2) continue; // parallel\n\t\t\tconst ex = s2.a.x - s1.a.x;\n\t\t\tconst ey = s2.a.y - s1.a.y;\n\t\t\tconst t = (ex * d2y - ey * d2x) / denom;\n\t\t\tconst u = (ex * d1y - ey * d1x) / denom;\n\t\t\tconst tolT = eps / len1;\n\t\t\tconst tolU = eps / len2;\n\t\t\tif (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;\n\t\t\tif (sharedA || sharedB) continue; // they meet at the shared endpoint only\n\t\t\tconst v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);\n\t\t\taddSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);\n\t\t\taddSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);\n\t\t}\n\t}\n\n\tlet changed = false;\n\tconst out: RawSegment[] = [];\n\tfor (const { segment, points } of splits) {\n\t\tif (points.size === 0) {\n\t\t\tout.push(segment);\n\t\t\tcontinue;\n\t\t}\n\t\tchanged = true;\n\t\tconst ordered = [...points.values()].sort((p, q) => p.t - q.t);\n\t\tlet prev = segment.a;\n\t\tfor (const { v } of ordered) {\n\t\t\tif (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });\n\t\t\tprev = v;\n\t\t}\n\t\tif (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });\n\t}\n\treturn { segments: out, changed };\n}\n\ninterface Edge {\n\ta: Vertex;\n\tb: Vertex;\n\t/** Signed multiplicity per operand (+1 per edge running a→b, −1 per edge running b→a). */\n\tmult: number[];\n}\n\ninterface Overlay {\n\tedges: Edge[];\n\t/** Winding number per operand on the right of each edge. */\n\tright: number[][];\n\t/** Winding number per operand on the left of each edge. */\n\tleft: number[][];\n\tfillRules: PolygonFillRule[];\n\teps: number;\n}\n\nfunction defaultEpsilon(operands: NormalizedOperand[]): number {\n\tlet max = 0;\n\tfor (const op of operands) {\n\t\tfor (const ring of op.rings) {\n\t\t\tfor (const [x, y] of ring) {\n\t\t\t\tconst m = Math.max(Math.abs(x), Math.abs(y));\n\t\t\t\tif (m > max) max = m;\n\t\t\t}\n\t\t}\n\t}\n\treturn Math.max(1e-9, max * 1e-9);\n}\n\nfunction buildOverlay(inputs: PolygonInput[], options: PolygonBooleanOptions): Overlay {\n\tconst operands = inputs.map(normalizeInput);\n\tconst eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);\n\tconst pool = new PointPool(eps);\n\tlet segments: RawSegment[] = [];\n\toperands.forEach((op, operand) => {\n\t\tfor (const ring of op.rings) {\n\t\t\tconst verts = ring.map(([x, y]) => pool.get(x, y));\n\t\t\tfor (let i = 0; i < verts.length; i++) {\n\t\t\t\tconst a = verts[i];\n\t\t\t\tconst b = verts[(i + 1) % verts.length];\n\t\t\t\tif (a.id !== b.id) segments.push({ a, b, operand });\n\t\t\t}\n\t\t}\n\t});\n\n\tfor (let pass = 0; pass < 8; pass++) {\n\t\tconst result = splitPass(segments, pool, eps);\n\t\tsegments = result.segments;\n\t\tif (!result.changed) break;\n\t}\n\n\t// Merge coincident segments.\n\tconst count = operands.length;\n\tconst byKey = new Map<string, Edge>();\n\tfor (const s of segments) {\n\t\tconst forward = s.a.id < s.b.id;\n\t\tconst a = forward ? s.a : s.b;\n\t\tconst b = forward ? s.b : s.a;\n\t\tconst key = `${a.id},${b.id}`;\n\t\tlet edge = byKey.get(key);\n\t\tif (!edge) {\n\t\t\tedge = { a, b, mult: new Array(count).fill(0) };\n\t\t\tbyKey.set(key, edge);\n\t\t}\n\t\tedge.mult[s.operand] += forward ? 1 : -1;\n\t}\n\tconst edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));\n\n\tconst right: number[][] = [];\n\tconst left: number[][] = [];\n\tfor (let i = 0; i < edges.length; i++) {\n\t\tconst edge = edges[i];\n\t\t// Cast the ray across the edge rather than along it: rotating by −90° keeps windings\n\t\t// and turns a mostly horizontal edge into a mostly vertical one.\n\t\tconst horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);\n\t\tconst tx = (v: Vertex) => (horizontal ? v.y : v.x);\n\t\tconst ty = (v: Vertex) => (horizontal ? -v.x : v.y);\n\t\tconst ax = tx(edge.a);\n\t\tconst ay = ty(edge.a);\n\t\tconst bx = tx(edge.b);\n\t\tconst by = ty(edge.b);\n\t\tconst mx = (ax + bx) / 2;\n\t\tconst my = (ay + by) / 2;\n\t\tconst winding = new Array(count).fill(0);\n\t\tfor (let j = 0; j < edges.length; j++) {\n\t\t\tif (j === i) continue;\n\t\t\tconst other = edges[j];\n\t\t\tconst oax = tx(other.a);\n\t\t\tconst oay = ty(other.a);\n\t\t\tconst obx = tx(other.b);\n\t\t\tconst oby = ty(other.b);\n\t\t\tlet dir = 0;\n\t\t\tif (oay <= my && oby > my) dir = 1;\n\t\t\telse if (oby <= my && oay > my) dir = -1;\n\t\t\telse continue;\n\t\t\tconst x = oax + ((my - oay) / (oby - oay)) * (obx - oax);\n\t\t\tif (x <= mx) continue;\n\t\t\tfor (let k = 0; k < count; k++) {\n\t\t\t\tif (other.mult[k] !== 0) winding[k] += dir * other.mult[k];\n\t\t\t}\n\t\t}\n\t\t// The ray starts just past the midpoint on the +x side, which is the right of the\n\t\t// edge when it points up (dy > 0) and its left otherwise.\n\t\tconst sideIsRight = by > ay;\n\t\tconst r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);\n\t\tright.push(r);\n\t\tleft.push(r.map((w, k) => w + edge.mult[k]));\n\t}\n\treturn { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };\n}\n\nfunction insideFlags(windings: number[], fillRules: PolygonFillRule[]): boolean[] {\n\treturn windings.map((w, k) => (fillRules[k] === 'evenodd' ? (w & 1) === 1 : w !== 0));\n}\n\n// ---------------------------------------------------------------------------\n// Ring extraction\n// ---------------------------------------------------------------------------\n\ninterface DirectedEdge {\n\tfrom: Vertex;\n\tto: Vertex;\n\tangle: number;\n}\n\n/** Traces the kept directed edges (result on the left) into rings. */\nfunction traceRings(directed: DirectedEdge[]): Vertex[][] {\n\tconst outgoing = new Map<number, DirectedEdge[]>();\n\tfor (const e of directed) {\n\t\tconst list = outgoing.get(e.from.id);\n\t\tif (list) list.push(e);\n\t\telse outgoing.set(e.from.id, [e]);\n\t}\n\tconst TWO_PI = Math.PI * 2;\n\tconst next = (incoming: DirectedEdge): DirectedEdge | undefined => {\n\t\tconst candidates = outgoing.get(incoming.to.id);\n\t\tif (!candidates) return undefined;\n\t\tconst back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);\n\t\tlet best: DirectedEdge | undefined;\n\t\tlet bestTurn = Infinity;\n\t\tfor (const c of candidates) {\n\t\t\t// Clockwise sweep from the reversed incoming edge: the first hit is the sharpest\n\t\t\t// left turn, which keeps the result region on the left and splits rings at\n\t\t\t// vertices they merely touch.\n\t\t\tlet turn = back - c.angle;\n\t\t\tturn -= Math.floor(turn / TWO_PI) * TWO_PI;\n\t\t\tif (turn <= 1e-15) turn = TWO_PI;\n\t\t\tif (turn < bestTurn) {\n\t\t\t\tbestTurn = turn;\n\t\t\t\tbest = c;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst used = new Set<DirectedEdge>();\n\tconst rings: Vertex[][] = [];\n\tfor (const start of directed) {\n\t\tif (used.has(start)) continue;\n\t\tconst ring: Vertex[] = [];\n\t\tlet edge: DirectedEdge | undefined = start;\n\t\tlet guard = directed.length + 1;\n\t\twhile (edge && !used.has(edge) && guard-- > 0) {\n\t\t\tused.add(edge);\n\t\t\tring.push(edge.from);\n\t\t\tedge = next(edge);\n\t\t}\n\t\tif (ring.length >= 3) rings.push(ring);\n\t}\n\treturn rings;\n}\n\n/** Removes vertices that lie on the straight line between their neighbours. */\nfunction removeCollinear(ring: Point[], eps: number): Point[] {\n\tlet points = ring;\n\tlet changed = true;\n\twhile (changed && points.length > 3) {\n\t\tchanged = false;\n\t\tconst out: Point[] = [];\n\t\tconst n = points.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];\n\t\t\tconst cur = points[i];\n\t\t\tconst nxt = points[(i + 1) % n];\n\t\t\tconst dx = nxt[0] - prev[0];\n\t\t\tconst dy = nxt[1] - prev[1];\n\t\t\tconst len = Math.hypot(dx, dy);\n\t\t\tconst cross = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;\n\t\t\tconst dot = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;\n\t\t\tif (len > 0 && Math.abs(cross) / len <= eps && dot > 0 && dot < len * len) {\n\t\t\t\tchanged = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push(cur);\n\t\t}\n\t\tif (out.length < 3) return out;\n\t\tpoints = out;\n\t}\n\treturn points;\n}\n\nfunction assemble(rings: Vertex[][], eps: number): MultiPolygon {\n\tconst outers: { ring: Point[]; area: number }[] = [];\n\tconst holes: { ring: Point[]; area: number }[] = [];\n\tconst minArea = eps * eps;\n\tfor (const vertices of rings) {\n\t\tconst ring = removeCollinear(\n\t\t\tvertices.map((v) => [v.x, v.y] as Point),\n\t\t\teps\n\t\t);\n\t\tif (ring.length < 3) continue;\n\t\tconst area = ringSignedArea(ring);\n\t\tif (Math.abs(area) <= minArea) continue;\n\t\t(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });\n\t}\n\touters.sort((a, b) => b.area - a.area);\n\tconst polygons: PolygonWithHoles[] = outers.map((o) => [o.ring]);\n\tfor (const hole of holes) {\n\t\t// Sample the midpoint of a hole edge: overlay edges are unique, so it is not on an\n\t\t// outer ring's boundary.\n\t\tconst [p, q] = [hole.ring[0], hole.ring[1]];\n\t\tconst sample: Point = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];\n\t\tlet owner = -1;\n\t\tfor (let i = outers.length - 1; i >= 0; i--) {\n\t\t\t// Iterating smallest-first finds the tightest container.\n\t\t\tif (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], 'evenodd')) {\n\t\t\t\towner = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (owner >= 0) polygons[owner].push(hole.ring);\n\t}\n\treturn polygons;\n}\n\nfunction extract(overlay: Overlay, isInside: (flags: boolean[]) => boolean): MultiPolygon {\n\tconst directed: DirectedEdge[] = [];\n\toverlay.edges.forEach((edge, i) => {\n\t\tconst l = isInside(insideFlags(overlay.left[i], overlay.fillRules));\n\t\tconst r = isInside(insideFlags(overlay.right[i], overlay.fillRules));\n\t\tif (l === r) return;\n\t\tconst [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];\n\t\tdirected.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });\n\t});\n\treturn assemble(traceRings(directed), overlay.eps);\n}\n\nfunction predicate(operation: PolygonBooleanOperation): (flags: boolean[]) => boolean {\n\tswitch (operation) {\n\t\tcase 'union':\n\t\t\treturn (f) => f.some(Boolean);\n\t\tcase 'intersection':\n\t\t\treturn (f) => f.length > 0 && f.every(Boolean);\n\t\tcase 'difference':\n\t\t\treturn (f) => f[0] === true && !f.slice(1).some(Boolean);\n\t\tcase 'xor':\n\t\t\treturn (f) => f.filter(Boolean).length % 2 === 1;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\n/**\n * Applies a boolean operation to any number of operands.\n * - `union`: covered by any operand.\n * - `intersection`: covered by every operand.\n * - `difference`: the first operand minus all others.\n * - `xor`: covered by an odd number of operands (Exclude).\n *\n * Returns simple rings: outer rings have a positive signed area, holes a negative one.\n * Rings may touch at vertices but never cross.\n */\nexport function polygonBoolean(\n\toperation: PolygonBooleanOperation,\n\toperands: PolygonInput[],\n\toptions: PolygonBooleanOptions = {}\n): MultiPolygon {\n\tif (operands.length === 0) return [];\n\treturn extract(buildOverlay(operands, options), predicate(operation));\n}\n\nexport const unionPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('union', [a, b], options);\nexport const intersectPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('intersection', [a, b], options);\nexport const subtractPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('difference', [a, b], options);\nexport const xorPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('xor', [a, b], options);\n\n/**\n * Splits the overlay of the operands into its distinct regions (Freeform's Divide): every\n * connected area covered by the same set of operands becomes one piece. Pieces are ordered\n * by their operand set, then by size (largest first).\n */\nexport function dividePolygons(operands: PolygonInput[], options: PolygonBooleanOptions = {}): PolygonPiece[] {\n\tif (operands.length === 0) return [];\n\tconst overlay = buildOverlay(operands, options);\n\tconst signatures = new Map<string, number[]>();\n\tconst note = (windings: number[]) => {\n\t\tconst flags = insideFlags(windings, overlay.fillRules);\n\t\tif (!flags.some(Boolean)) return;\n\t\tconst key = flags.map((f) => (f ? '1' : '0')).join('');\n\t\tif (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => (f ? [k] : [])));\n\t};\n\toverlay.edges.forEach((_, i) => {\n\t\tnote(overlay.left[i]);\n\t\tnote(overlay.right[i]);\n\t});\n\tconst pieces: PolygonPiece[] = [];\n\tconst keys = [...signatures.keys()].sort();\n\tfor (const key of keys) {\n\t\tconst polygons = extract(overlay, (flags) => flags.map((f) => (f ? '1' : '0')).join('') === key);\n\t\tpolygons.sort((a, b) => polygonArea(b) - polygonArea(a));\n\t\tfor (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key)! });\n\t}\n\treturn pieces;\n}\n","/**\n * Least-squares cubic Bézier fitting of point sequences (Schneider-style: fit one cubic with\n * fixed end tangents, reparameterise with Newton steps, split at the worst point), with\n * corner preservation by a turning-angle test.\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/fit.rs and\n * vectorcraft-main/crates/pathops/src/edit.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from './types.js';\n\n/** Same as `formatNumber` in shapeGeometry (kept local to avoid an import cycle). */\nfunction formatNumber(value: number, precision: number): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\n/** One cubic Bézier segment. */\nexport interface CubicSegment {\n\tp0: Point;\n\tp1: Point;\n\tp2: Point;\n\tp3: Point;\n}\n\nexport interface FitCubicsOptions {\n\t/**\n\t * Vertices where the polyline turns by more than this many degrees stay sharp corners\n\t * (default 30). Use 180 to smooth through every vertex.\n\t */\n\tcornerAngleDeg?: number;\n\t/** Treat the points as a closed ring (the last point connects back to the first). */\n\tclosed?: boolean;\n}\n\n/** Maximum Newton reparameterisation rounds per fit. */\nconst MAX_REPARAM = 6;\n\ntype Vec = [number, number];\n\nconst sub = (a: Point, b: Point): Vec => [a[0] - b[0], a[1] - b[1]];\nconst add = (a: Point, v: Vec, s = 1): Vec => [a[0] + v[0] * s, a[1] + v[1] * s];\nconst dot = (a: Vec, b: Vec) => a[0] * b[0] + a[1] * b[1];\nconst dist = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]);\n\nfunction unit(v: Vec): Vec | null {\n\tconst l = Math.hypot(v[0], v[1]);\n\treturn l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;\n}\n\nfunction evalCubic(c: CubicSegment, t: number): Vec {\n\tconst s = 1 - t;\n\tconst b0 = s * s * s;\n\tconst b1 = 3 * s * s * t;\n\tconst b2 = 3 * s * t * t;\n\tconst b3 = t * t * t;\n\treturn [\n\t\tb0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],\n\t\tb0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]\n\t];\n}\n\n/** Evaluates a cubic segment at `t` ∈ [0, 1]. */\nexport function cubicPointAt(c: CubicSegment, t: number): Point {\n\treturn evalCubic(c, t);\n}\n\nfunction dedup(points: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of points) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tif (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);\n\t}\n\treturn out;\n}\n\n/** Chord-length parameters (0 to 1), one per point. */\nfunction chordParams(pts: Point[]): number[] {\n\tconst u = [0];\n\tlet acc = 0;\n\tfor (let i = 1; i < pts.length; i++) {\n\t\tacc += dist(pts[i - 1], pts[i]);\n\t\tu.push(acc);\n\t}\n\treturn acc > 0 ? u.map((x) => x / acc) : u;\n}\n\n/** Solves the 2×2 normal equations for the handle lengths along the fixed tangents. */\nfunction generate(pts: Point[], u: number[], t0: Vec, t1: Vec): CubicSegment {\n\tconst p0 = pts[0];\n\tconst p3 = pts[pts.length - 1];\n\tconst dirIn: Vec = [-t1[0], -t1[1]];\n\tlet c00 = 0;\n\tlet c01 = 0;\n\tlet c11 = 0;\n\tlet x0 = 0;\n\tlet x1 = 0;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst t = u[i];\n\t\tconst s = 1 - t;\n\t\tconst b0 = s * s * s;\n\t\tconst b1 = 3 * s * s * t;\n\t\tconst b2 = 3 * s * t * t;\n\t\tconst b3 = t * t * t;\n\t\tconst a0: Vec = [t0[0] * b1, t0[1] * b1];\n\t\tconst a1: Vec = [dirIn[0] * b2, dirIn[1] * b2];\n\t\tc00 += dot(a0, a0);\n\t\tc01 += dot(a0, a1);\n\t\tc11 += dot(a1, a1);\n\t\tconst tmp: Vec = [\n\t\t\tpts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),\n\t\t\tpts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))\n\t\t];\n\t\tx0 += dot(a0, tmp);\n\t\tx1 += dot(a1, tmp);\n\t}\n\tconst det = c00 * c11 - c01 * c01;\n\tconst seg = dist(p0, p3);\n\tconst eps = 1e-6 * seg;\n\tlet al = seg / 3;\n\tlet ar = seg / 3;\n\tif (Math.abs(det) > 1e-12) {\n\t\tal = (x0 * c11 - x1 * c01) / det;\n\t\tar = (c00 * x1 - c01 * x0) / det;\n\t}\n\tif (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {\n\t\tal = seg / 3;\n\t\tar = seg / 3;\n\t}\n\t// Guard against wild handles on nearly degenerate data.\n\tconst cap = seg * 4 + 1e-9;\n\tal = Math.min(al, cap);\n\tar = Math.min(ar, cap);\n\treturn { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };\n}\n\n/** Largest distance between a point and the curve at its parameter, and that point's index. */\nfunction maxError(pts: Point[], c: CubicSegment, u: number[]): [number, number] {\n\tlet e = 0;\n\tlet idx = pts.length >> 1;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst d = dist(evalCubic(c, u[i]), pts[i]);\n\t\tif (d > e) {\n\t\t\te = d;\n\t\t\tidx = i;\n\t\t}\n\t}\n\treturn [e, idx];\n}\n\n/** One Newton–Raphson step towards the parameter of the point on `c` nearest `p`. */\nfunction newton(c: CubicSegment, p: Point, t: number): number {\n\tconst s = 1 - t;\n\tconst q = evalCubic(c, t);\n\tconst d1: Vec = [\n\t\t3 * (s * s * (c.p1[0] - c.p0[0]) + 2 * s * t * (c.p2[0] - c.p1[0]) + t * t * (c.p3[0] - c.p2[0])),\n\t\t3 * (s * s * (c.p1[1] - c.p0[1]) + 2 * s * t * (c.p2[1] - c.p1[1]) + t * t * (c.p3[1] - c.p2[1]))\n\t];\n\tconst d2: Vec = [\n\t\t6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),\n\t\t6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))\n\t];\n\tconst diff: Vec = [q[0] - p[0], q[1] - p[1]];\n\tconst num = dot(diff, d1);\n\tconst den = dot(d1, d1) + dot(diff, d2);\n\tif (Math.abs(den) < 1e-12) return t;\n\tconst nt = t - num / den;\n\treturn Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;\n}\n\n/**\n * Best single cubic through `pts` with the given unit end tangents (`t1` points forward at\n * the end), starting from parameters `u`. Returns the curve, its max error and the index of\n * the worst point.\n */\nfunction fitSingleFrom(pts: Point[], u: number[], t0: Vec, t1: Vec): [CubicSegment, number, number] {\n\tlet best = generate(pts, u, t0, t1);\n\tlet [bestErr, bestSplit] = maxError(pts, best, u);\n\tconst params = u.slice();\n\tfor (let round = 0; round < MAX_REPARAM; round++) {\n\t\tfor (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);\n\t\tconst c = generate(pts, params, t0, t1);\n\t\tconst [e, s] = maxError(pts, c, params);\n\t\tif (e < bestErr) {\n\t\t\tbest = c;\n\t\t\tbestErr = e;\n\t\t\tbestSplit = s;\n\t\t} else break;\n\t}\n\treturn [best, bestErr, bestSplit];\n}\n\nfunction fitRec(pts: Point[], t0: Vec, t1: Vec, tol: number, out: CubicSegment[], depth: number) {\n\tconst n = pts.length;\n\tif (n === 2) {\n\t\tconst d = dist(pts[0], pts[1]) / 3;\n\t\tout.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });\n\t\treturn;\n\t}\n\tconst [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);\n\tif (err <= tol || depth > 40) {\n\t\tout.push(c);\n\t\treturn;\n\t}\n\t// Split at the worst point with a centre tangent estimated from its neighbours.\n\tconst split = Math.min(n - 2, Math.max(1, splitAt));\n\tconst tc = unit(sub(pts[split + 1], pts[split - 1])) ?? t0;\n\tfitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);\n\tfitRec(pts.slice(split), tc, t1, tol, out, depth + 1);\n}\n\n/**\n * Unit tangent at `a` estimated from the parabola through `a`, `b`, `c` (chord-length\n * parameters): second-order accurate, so polylines sampled from curves get their true end\n * direction rather than the first chord's.\n */\nfunction tangentFrom(a: Point, b: Point, c: Point | undefined): Vec | null {\n\tconst chord = unit(sub(b, a));\n\tif (!c) return chord;\n\tconst d1 = dist(a, b);\n\tconst d2 = dist(b, c);\n\tif (!(d1 > 0) || !(d2 > 0)) return chord;\n\tconst ka = -(2 * d1 + d2) / (d1 * (d1 + d2));\n\tconst kb = (d1 + d2) / (d1 * d2);\n\tconst kc = -d1 / (d2 * (d1 + d2));\n\tconst est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);\n\t// Fall back to the chord when the estimate swings too far (noisy or sharp data).\n\treturn est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;\n}\n\n/**\n * Fits a G1-continuous chain of cubics through `points` (an open run) with the unit\n * tangents `t0` (leaving the first point) and `t1` (arriving at the last point, forward);\n * both are estimated from the end points when omitted. Every point lies within `tolerance`\n * of the curve at its fitted parameter.\n */\nexport function fitCubicRun(points: readonly Point[], tolerance: number, t0?: Point, t1?: Point): CubicSegment[] {\n\tconst pts = dedup(points);\n\tconst out: CubicSegment[] = [];\n\tif (pts.length < 2) return out;\n\tconst n = pts.length;\n\tconst start = (t0 && unit([t0[0], t0[1]])) ?? tangentFrom(pts[0], pts[1], pts[2]) ?? [1, 0];\n\tconst back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);\n\tconst end = (t1 && unit([t1[0], t1[1]])) ?? (back ? ([-back[0], -back[1]] as Vec) : [1, 0]);\n\tfitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);\n\treturn out;\n}\n\n/** Turning angle in degrees between the directions `a`→`b` and `b`→`c`. */\nfunction turnDeg(a: Point, b: Point, c: Point): number {\n\tconst u = unit(sub(b, a));\n\tconst v = unit(sub(c, b));\n\tif (!u || !v) return 0;\n\treturn (Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180) / Math.PI;\n}\n\n/**\n * Corner vertices of a polyline. The turn at each vertex is measured to the nearest\n * neighbours at least `span` away on each side (so sample noise smaller than the fitting\n * tolerance does not read as corners), and only the sharpest vertex of a neighbourhood is\n * kept. End points of open polylines are not included.\n */\nfunction findCorners(pts: Point[], closed: boolean, cornerAngle: number, span: number): number[] {\n\tconst n = pts.length;\n\tconst at = (i: number) => pts[closed ? ((i % n) + n) % n : i];\n\tconst reach = (i: number, step: 1 | -1): number | null => {\n\t\tfor (let k = 1; k < (closed ? n : Infinity); k++) {\n\t\t\tconst j = i + step * k;\n\t\t\tif (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;\n\t\t\tif (dist(at(j), pts[i]) >= span) return j;\n\t\t}\n\t\treturn null;\n\t};\n\tconst turns = new Float64Array(n);\n\tconst windows: [number, number][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\twindows.push([i, i]);\n\t\tif (!closed && (i === 0 || i === n - 1)) continue;\n\t\tconst prev = reach(i, -1);\n\t\tconst next = reach(i, 1);\n\t\tif (prev === null || next === null) continue;\n\t\twindows[i] = [prev, next];\n\t\tturns[i] = turnDeg(at(prev), pts[i], at(next));\n\t}\n\tconst corners: number[] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tif (!(turns[i] > cornerAngle)) continue;\n\t\tconst [lo, hi] = windows[i];\n\t\tlet sharpest = true;\n\t\tfor (let j = lo; j <= hi && sharpest; j++) {\n\t\t\tconst k = closed ? ((j % n) + n) % n : j;\n\t\t\tif (k === i || dist(pts[k], pts[i]) >= span) continue;\n\t\t\tif (turns[k] > turns[i] || (turns[k] === turns[i] && k < i)) sharpest = false;\n\t\t}\n\t\tif (sharpest) corners.push(i);\n\t}\n\treturn corners;\n}\n\n/**\n * Points to fit for a run of polyline vertices: the vertices, plus extra samples along edges\n * much longer than the run's typical edge so straight stretches stay straight.\n */\nfunction densify(run: Point[]): Point[] {\n\tif (run.length < 3) return run;\n\tconst lengths: number[] = [];\n\tfor (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));\n\tconst sorted = [...lengths].sort((a, b) => a - b);\n\tconst median = sorted[sorted.length >> 1];\n\tif (!(median > 0)) return run;\n\tconst out: Point[] = [run[0]];\n\tfor (let i = 1; i < run.length; i++) {\n\t\tconst len = lengths[i - 1];\n\t\tif (len > 2 * median) {\n\t\t\tconst steps = Math.min(64, Math.ceil(len / median));\n\t\t\tconst a = run[i - 1];\n\t\t\tconst b = run[i];\n\t\t\tfor (let k = 1; k < steps; k++) {\n\t\t\t\tconst t = k / steps;\n\t\t\t\tout.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);\n\t\t\t}\n\t\t}\n\t\tout.push(run[i]);\n\t}\n\treturn out;\n}\n\n/**\n * Fits cubic Bézier segments to a polyline (`closed` for a ring), keeping vertices that turn\n * by more than `cornerAngleDeg` (default 30°) as sharp corners. Each stretch between corners\n * is fitted Schneider-style so that every input point is within `tolerance` of the result.\n *\n * Returns the segments in order (consecutive segments share end points; a closed result ends\n * where it starts). Fewer than two distinct points give an empty array.\n */\nexport function fitCubics(points: readonly Point[], tolerance: number, options: FitCubicsOptions = {}): CubicSegment[] {\n\tlet pts = dedup(points);\n\tconst closed = options.closed === true;\n\tif (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);\n\tif (pts.length < 2) return [];\n\tconst tol = Math.max(tolerance, 1e-6);\n\tconst cornerAngle = options.cornerAngleDeg ?? 30;\n\tconst n = pts.length;\n\tconst span = 4 * tol;\n\n\tif (!closed || n < 3) {\n\t\tconst corners = [0, ...(n > 2 ? findCorners(pts, false, cornerAngle, span) : []), n - 1];\n\t\tconst out: CubicSegment[] = [];\n\t\tfor (let k = 1; k < corners.length; k++) {\n\t\t\tconst run = pts.slice(corners[k - 1], corners[k] + 1);\n\t\t\tout.push(...fitCubicRun(densify(run), tol));\n\t\t}\n\t\tif (closed && n === 2) out.push(...fitCubicRun([pts[1], pts[0]], tol));\n\t\treturn out;\n\t}\n\n\tconst corners = findCorners(pts, true, cornerAngle, span);\n\tif (corners.length === 0) {\n\t\t// Smooth ring: start anywhere, with the same tangent leaving and arriving.\n\t\tconst ring = [...pts, pts[0]];\n\t\tconst t = unit(sub(pts[1], pts[n - 1])) ?? [1, 0];\n\t\t// (A central difference: already second-order accurate at the seam.)\n\t\treturn fitCubicRun(densify(ring), tol, t, t);\n\t}\n\tconst out: CubicSegment[] = [];\n\tfor (let k = 0; k < corners.length; k++) {\n\t\tconst from = corners[k];\n\t\tconst to = corners[(k + 1) % corners.length];\n\t\tconst run: Point[] = [];\n\t\tfor (let i = from; ; i = (i + 1) % n) {\n\t\t\trun.push(pts[i]);\n\t\t\tif (run.length > 1 && i === to) break;\n\t\t}\n\t\tout.push(...fitCubicRun(densify(run), tol));\n\t}\n\treturn out;\n}\n\n/** Whether a cubic is (within `tol`) the straight line from `p0` to `p3`. */\nexport function isStraightCubic(c: CubicSegment, tol: number): boolean {\n\tconst chord = sub(c.p3, c.p0);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tif (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;\n\tconst dir: Vec = [chord[0] / len, chord[1] / len];\n\treturn [c.p1, c.p2].every((h) => {\n\t\tconst v = sub(h, c.p0);\n\t\tconst along = dot(v, dir);\n\t\tconst across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);\n\t\treturn across <= tol && along >= -tol && along <= len + tol;\n\t});\n}\n\nexport interface FitPathDataOptions extends FitCubicsOptions {\n\t/** Fitting tolerance in path units (default 0.5). */\n\ttolerance?: number;\n\t/** Decimal places (default 3). */\n\tprecision?: number;\n\t/** Added to every point before formatting. */\n\toffset?: { x: number; y: number };\n\t/**\n\t * Keep the original polyline when fitting would need more segments than it has edges\n\t * (default `true`), so fitting never makes a path heavier.\n\t */\n\tnoWorse?: boolean;\n}\n\n/** SVG `d` for fitted segments (`L` for straight ones, `C` otherwise). */\nexport function cubicsToPathData(\n\tsegments: readonly CubicSegment[],\n\toptions: { precision?: number; offset?: { x: number; y: number }; closed?: boolean; straightTolerance?: number } = {}\n): string {\n\tif (segments.length === 0) return '';\n\tconst f = (n: number) => formatNumber(n, options.precision ?? 3);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst pt = (p: Point) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;\n\tconst straight = options.straightTolerance ?? 1e-3;\n\tlet d = `M${pt(segments[0].p0)}`;\n\tsegments.forEach((c, i) => {\n\t\tconst last = options.closed && i === segments.length - 1;\n\t\tif (isStraightCubic(c, straight)) {\n\t\t\tif (!last) d += `L${pt(c.p3)}`;\n\t\t} else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;\n\t});\n\treturn options.closed ? `${d}Z` : d;\n}\n\nfunction polylineToPathData(points: readonly Point[], closed: boolean, options: FitPathDataOptions): string {\n\tif (points.length < 2) return '';\n\tconst f = (n: number) => formatNumber(n, options.precision ?? 3);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tlet d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;\n\tfor (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;\n\treturn closed ? `${d}Z` : d;\n}\n\n/**\n * Fits smooth curves to a polygon ring (closed by default) and returns SVG path data with\n * `C` curves for smooth stretches, `L` lines for straight ones and sharp corners kept.\n * Use it for boolean results of curved shapes or to smooth freehand strokes\n * (`{ closed: false }`, see `fitFreehandPath`).\n */\nexport function fitPathData(ring: readonly Point[], options: FitPathDataOptions = {}): string {\n\tconst closed = options.closed ?? true;\n\tconst tolerance = options.tolerance ?? 0.5;\n\tconst pts = dedup(ring);\n\tconst segments = fitCubics(pts, tolerance, { ...options, closed });\n\tif (segments.length === 0) return '';\n\tconst edges = closed ? pts.length : pts.length - 1;\n\tif (options.noWorse !== false) {\n\t\tconst straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;\n\t\t// Fitting only pays off when it removes vertices.\n\t\tif (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);\n\t\tif (segments.length > edges) return polylineToPathData(pts, closed, options);\n\t}\n\treturn cubicsToPathData(segments, {\n\t\tprecision: options.precision,\n\t\toffset: options.offset,\n\t\tclosed,\n\t\tstraightTolerance: tolerance * 0.02\n\t});\n}\n\n/**\n * Smooths a freehand stroke (pointer samples) into an open SVG path of cubic curves.\n * `tolerance` is the largest distance (in the points' units) a sample may be from the\n * curve (default 1); sharp turns above `cornerAngleDeg` (default 30) stay corners.\n */\nexport function fitFreehandPath(\n\tpoints: readonly Point[],\n\toptions: Omit<FitPathDataOptions, 'closed'> = {}\n): string {\n\treturn fitPathData(points, { tolerance: 1, ...options, closed: false });\n}\n","/**\n * Shape geometry: shape descriptions (rect, rounded rect, ellipse, regular polygon, star and\n * SVG path data) to polygons, SVG path parsing / flattening, and polygons back to path data.\n */\nimport type { Point } from './types.js';\nimport type { MultiPolygon, PolygonFillRule, PolygonRing } from './polygonBoolean.js';\nimport { fitPathData } from './curveFit.js';\n\n// ---------------------------------------------------------------------------\n// Shape descriptions\n// ---------------------------------------------------------------------------\n\n/** Paint shared by every shape kind. Colours are any CSS colour. */\nexport interface ShapePaint {\n\tfill?: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\topacity?: number;\n}\n\ninterface ShapeBase extends ShapePaint {\n\t/** Clockwise rotation in degrees (on a y-down canvas) about the centre of the node box. */\n\trotation?: number;\n}\n\n/** Rectangle filling the node box, optionally with rounded corners. */\nexport interface RectShape extends ShapeBase {\n\tkind: 'rect';\n\t/** One radius, or `[topLeft, topRight, bottomRight, bottomLeft]`. Clamped to fit. */\n\tcornerRadius?: number | readonly [number, number, number, number];\n}\n\n/** Ellipse (circle when the node is square) inscribed in the node box. */\nexport interface EllipseShape extends ShapeBase {\n\tkind: 'ellipse';\n}\n\n/** Regular polygon, stretched so its bounding box fills the node box. */\nexport interface RegularPolygonShape extends ShapeBase {\n\tkind: 'polygon';\n\t/** Number of sides (at least 3, default 6). The first vertex points up. */\n\tsides?: number;\n}\n\n/** Star, stretched so its bounding box fills the node box. */\nexport interface StarShape extends ShapeBase {\n\tkind: 'star';\n\t/** Number of points (at least 3, default 5). The first point points up. */\n\tpoints?: number;\n\t/** Inner radius as a fraction of the outer radius (default 0.5). */\n\tinnerRadius?: number;\n}\n\n/** Arbitrary SVG path data. */\nexport interface PathShape extends ShapeBase {\n\tkind: 'path';\n\td: string;\n\t/**\n\t * `[minX, minY, width, height]` of the path's coordinate space, scaled to the node box.\n\t * Without it the path coordinates are node-local pixels.\n\t */\n\tviewBox?: readonly [number, number, number, number];\n\t/** Fill rule (default `nonzero`, as in SVG). */\n\tfillRule?: PolygonFillRule;\n}\n\n/**\n * Shape description stored on a node as `data.shape`. Geometry is relative to the node\n * box (`0,0` to `width,height`), so resizing the node resizes the shape.\n */\nexport type ShapeDescription = RectShape | EllipseShape | RegularPolygonShape | StarShape | PathShape;\n\nexport type ShapeKind = ShapeDescription['kind'];\n\nexport const SHAPE_KINDS: readonly ShapeKind[] = ['rect', 'ellipse', 'polygon', 'star', 'path'];\n\n/** Whether a value is a supported shape description. */\nexport function isShapeDescription(value: unknown): value is ShapeDescription {\n\tif (!value || typeof value !== 'object') return false;\n\tconst kind = (value as { kind?: unknown }).kind;\n\tif (!SHAPE_KINDS.includes(kind as ShapeKind)) return false;\n\treturn kind !== 'path' || typeof (value as PathShape).d === 'string';\n}\n\nexport interface ShapeSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface FlattenOptions {\n\t/** Maximum distance between a curve and its polyline, in output units (default 0.25). */\n\ttolerance?: number;\n\t/** Upper bound on the segments one curve is split into (default 512). */\n\tmaxSegments?: number;\n}\n\n// ---------------------------------------------------------------------------\n// SVG path parsing\n// ---------------------------------------------------------------------------\n\n/** Normalised absolute path command produced by `parseSvgPath`. */\nexport type PathCommand =\n\t| { type: 'M'; x: number; y: number }\n\t| { type: 'L'; x: number; y: number }\n\t| { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number }\n\t| { type: 'Q'; x1: number; y1: number; x: number; y: number }\n\t| {\n\t\t\ttype: 'A';\n\t\t\trx: number;\n\t\t\try: number;\n\t\t\trotation: number;\n\t\t\tlargeArc: boolean;\n\t\t\tsweep: boolean;\n\t\t\tx: number;\n\t\t\ty: number;\n\t }\n\t| { type: 'Z' };\n\nconst PARAM_COUNTS: Record<string, number> = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };\n\n/** Character scanner for path data (handles `1.5.5`, `1e-3`, `-1-2` and compact arc flags). */\nclass PathScanner {\n\tprivate pos = 0;\n\tconstructor(private readonly text: string) {}\n\n\tprivate skip() {\n\t\twhile (this.pos < this.text.length && /[\\s,]/.test(this.text[this.pos])) this.pos++;\n\t}\n\n\t/** The next command letter, or `null` when a number (or the end) follows. */\n\tcommand(): string | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {\n\t\t\tthis.pos++;\n\t\t\treturn ch;\n\t\t}\n\t\treturn null;\n\t}\n\n\tdone(): boolean {\n\t\tthis.skip();\n\t\treturn this.pos >= this.text.length;\n\t}\n\n\tnumber(): number | null {\n\t\tthis.skip();\n\t\tconst match = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));\n\t\tif (!match) return null;\n\t\tthis.pos += match[0].length;\n\t\tconst value = Number(match[0]);\n\t\treturn Number.isFinite(value) ? value : null;\n\t}\n\n\tflag(): number | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch === '0' || ch === '1') {\n\t\t\tthis.pos++;\n\t\t\treturn ch === '1' ? 1 : 0;\n\t\t}\n\t\treturn null;\n\t}\n}\n\n/**\n * Parses SVG path data into absolute `M`, `L`, `C`, `Q`, `A` and `Z` commands. Relative\n * commands, `H` / `V`, smooth curves (`S` / `T`) and implicit repeated commands are\n * normalised. Parsing stops at the first malformed command (as browsers do).\n */\nexport function parseSvgPath(d: string): PathCommand[] {\n\tconst scanner = new PathScanner(d ?? '');\n\tconst commands: PathCommand[] = [];\n\tlet cx = 0;\n\tlet cy = 0;\n\tlet startX = 0;\n\tlet startY = 0;\n\tlet lastControl: { x: number; y: number; type: 'C' | 'Q' } | null = null;\n\tlet command = '';\n\n\twhile (!scanner.done()) {\n\t\tconst letter = scanner.command();\n\t\tif (letter) {\n\t\t\tcommand = letter;\n\t\t\tif (command === 'z' || command === 'Z') {\n\t\t\t\tcommands.push({ type: 'Z' });\n\t\t\t\tcx = startX;\n\t\t\t\tcy = startY;\n\t\t\t\tlastControl = null;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if (!command || command === 'z' || command === 'Z') {\n\t\t\tbreak;\n\t\t}\n\t\tconst lower = command.toLowerCase();\n\t\tconst count = PARAM_COUNTS[lower];\n\t\tconst args: number[] = [];\n\t\tfor (let k = 0; k < count; k++) {\n\t\t\tconst value = lower === 'a' && (k === 3 || k === 4) ? scanner.flag() : scanner.number();\n\t\t\tif (value === null) return commands;\n\t\t\targs.push(value);\n\t\t}\n\t\tconst rel = command !== command.toUpperCase();\n\t\tconst ox = rel ? cx : 0;\n\t\tconst oy = rel ? cy : 0;\n\t\tswitch (lower) {\n\t\t\tcase 'm': {\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tstartX = cx;\n\t\t\t\tstartY = cy;\n\t\t\t\tcommands.push({ type: 'M', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\t// Extra coordinate pairs after a moveto are implicit linetos.\n\t\t\t\tcommand = rel ? 'l' : 'L';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'l':\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tcx = args[0] + (rel ? cx : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tcy = args[0] + (rel ? cy : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'c': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1: args[0] + ox,\n\t\t\t\t\ty1: args[1] + oy,\n\t\t\t\t\tx2: args[2] + ox,\n\t\t\t\t\ty2: args[3] + oy,\n\t\t\t\t\tx: args[4] + ox,\n\t\t\t\t\ty: args[5] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 's': {\n\t\t\t\tconst x1: number = lastControl?.type === 'C' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'C' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1,\n\t\t\t\t\ty1,\n\t\t\t\t\tx2: args[0] + ox,\n\t\t\t\t\ty2: args[1] + oy,\n\t\t\t\t\tx: args[2] + ox,\n\t\t\t\t\ty: args[3] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'q': {\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x1, y: c.y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 't': {\n\t\t\t\tconst x1: number = lastControl?.type === 'Q' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'Q' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1, y1, x: args[0] + ox, y: args[1] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: x1, y: y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'a': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'A',\n\t\t\t\t\trx: Math.abs(args[0]),\n\t\t\t\t\try: Math.abs(args[1]),\n\t\t\t\t\trotation: args[2],\n\t\t\t\t\tlargeArc: args[3] !== 0,\n\t\t\t\t\tsweep: args[4] !== 0,\n\t\t\t\t\tx: args[5] + ox,\n\t\t\t\t\ty: args[6] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = null;\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn commands;\n}\n\n// ---------------------------------------------------------------------------\n// Curve flattening\n// ---------------------------------------------------------------------------\n\nfunction segmentCount(curvature: number, tolerance: number, max: number): number {\n\tif (!(curvature > 0)) return 1;\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));\n}\n\nfunction flattenCubic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx2: number,\n\ty2: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\t// Uniform subdivision error <= max|B''| / (8 n²) with |B''| <= 6 * max second difference.\n\tconst dd = Math.max(\n\t\tMath.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),\n\t\tMath.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)\n\t);\n\tconst n = segmentCount((6 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tconst a = mt * mt * mt;\n\t\tconst b = 3 * mt * mt * t;\n\t\tconst c = 3 * mt * t * t;\n\t\tconst e = t * t * t;\n\t\tout.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);\n\t}\n}\n\nfunction flattenQuadratic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);\n\tconst n = segmentCount((2 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tout.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);\n\t}\n}\n\nfunction arcSteps(radius: number, sweep: number, tolerance: number, max: number): number {\n\tif (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));\n\tconst step = 2 * Math.acos(1 - tolerance / radius);\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));\n}\n\n/** Flattens an SVG elliptical arc (endpoint parameterisation, SVG 1.1 F.6.5). */\nfunction flattenArc(\n\tout: Point[],\n\tp0: Point,\n\tcmd: Extract<PathCommand, { type: 'A' }>,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst [x0, y0] = p0;\n\tlet { rx, ry } = cmd;\n\tconst { x, y } = cmd;\n\tif (x0 === x && y0 === y) return;\n\tif (rx === 0 || ry === 0) {\n\t\tout.push([x, y]);\n\t\treturn;\n\t}\n\tconst phi = (cmd.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst dx = (x0 - x) / 2;\n\tconst dy = (y0 - y) / 2;\n\tconst x1p = cos * dx + sin * dy;\n\tconst y1p = -sin * dx + cos * dy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\tconst s = Math.sqrt(lambda);\n\t\trx *= s;\n\t\try *= s;\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tlet coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));\n\tif (cmd.largeArc === cmd.sweep) coef = -coef;\n\tconst cxp = (coef * rx * y1p) / ry;\n\tconst cyp = (-coef * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x0 + x) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y0 + y) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => {\n\t\tconst a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\t\treturn a;\n\t};\n\tconst theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;\n\telse if (cmd.sweep && delta < 0) delta += 2 * Math.PI;\n\tconst n = arcSteps(Math.max(rx, ry), delta, tolerance, max);\n\tfor (let k = 1; k < n; k++) {\n\t\tconst t = theta1 + (delta * k) / n;\n\t\tconst ex = rx * Math.cos(t);\n\t\tconst ey = ry * Math.sin(t);\n\t\tout.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);\n\t}\n\tout.push([x, y]);\n}\n\n/**\n * Flattens SVG path data into closed rings (one per subpath; open subpaths are closed the\n * way SVG fills them). Curves are approximated within `tolerance`.\n */\nexport function flattenSvgPath(d: string | PathCommand[], options: FlattenOptions = {}): PolygonRing[] {\n\tconst tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;\n\tconst max = options.maxSegments ?? 512;\n\tconst commands = typeof d === 'string' ? parseSvgPath(d) : d;\n\tconst rings: PolygonRing[] = [];\n\tlet current: Point[] = [];\n\tlet start: Point = [0, 0];\n\tconst flush = () => {\n\t\tif (current.length >= 3) rings.push(current);\n\t\tcurrent = [];\n\t};\n\tconst last = (): Point => current[current.length - 1] ?? start;\n\tfor (const cmd of commands) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tflush();\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tcurrent = [start];\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tcurrent.push([cmd.x, cmd.y]);\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'A':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenArc(current, last(), cmd, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\tflush();\n\t\t\t\t// A command after Z without a moveto starts at the subpath start.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tflush();\n\treturn rings.map(dedupeRing).filter((r) => r.length >= 3);\n}\n\nfunction dedupeRing(ring: Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tconst prev = out[out.length - 1];\n\t\tif (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);\n\t}\n\twhile (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Shapes to polygons\n// ---------------------------------------------------------------------------\n\nfunction cornerRadii(shape: RectShape, width: number, height: number): [number, number, number, number] {\n\tconst raw = shape.cornerRadius ?? 0;\n\tlet radii = (typeof raw === 'number' ? [raw, raw, raw, raw] : [...raw]).map((r) =>\n\t\tNumber.isFinite(r) && r > 0 ? r : 0\n\t) as [number, number, number, number];\n\t// Scale all radii down together when adjacent corners would overlap (CSS rule).\n\tconst factor = Math.min(\n\t\t1,\n\t\twidth / Math.max(radii[0] + radii[1], 1e-12),\n\t\theight / Math.max(radii[1] + radii[2], 1e-12),\n\t\twidth / Math.max(radii[2] + radii[3], 1e-12),\n\t\theight / Math.max(radii[3] + radii[0], 1e-12)\n\t);\n\tif (factor < 1) radii = radii.map((r) => r * factor) as [number, number, number, number];\n\treturn radii;\n}\n\n/** Unit vertices of a regular polygon / star, stretched to fill the box. */\nfunction polygonVertices(count: number, inner: number | null, width: number, height: number): Point[] {\n\tconst raw: Point[] = [];\n\tconst total = inner === null ? count : count * 2;\n\tfor (let k = 0; k < total; k++) {\n\t\tconst angle = -Math.PI / 2 + (k * 2 * Math.PI) / total;\n\t\tconst r = inner !== null && k % 2 === 1 ? inner : 1;\n\t\traw.push([r * Math.cos(angle), r * Math.sin(angle)]);\n\t}\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of raw) {\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x);\n\t\tmaxY = Math.max(maxY, y);\n\t}\n\tconst sx = width / (maxX - minX || 1);\n\tconst sy = height / (maxY - minY || 1);\n\treturn raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);\n}\n\nfunction rotationTransform(shape: ShapeDescription, size: ShapeSize): ((p: Point) => Point) | null {\n\tconst deg = shape.rotation ?? 0;\n\tif (!deg || !Number.isFinite(deg)) return null;\n\tconst rad = (deg * Math.PI) / 180;\n\tconst cos = Math.cos(rad);\n\tconst sin = Math.sin(rad);\n\tconst cx = size.width / 2;\n\tconst cy = size.height / 2;\n\treturn ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];\n}\n\n/**\n * SVG path data for a shape in node-local pixels (exact curves: arcs for rounded corners\n * and ellipses), plus the fill rule and a `transform` for rotation / path view boxes.\n * Use it to render a shape; use `shapeToPolygon` for geometry.\n */\nexport function getShapeSvg(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { d: string; transform?: string; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tconst f = (n: number) => formatNumber(n, 3);\n\tconst transforms: string[] = [];\n\tif (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);\n\tlet d = '';\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tswitch (shape.kind) {\n\t\tcase 'rect': {\n\t\t\tconst [tl, tr, br, bl] = cornerRadii(shape, w, h);\n\t\t\tif (tl + tr + br + bl === 0) {\n\t\t\t\td = `M0 0H${f(w)}V${f(h)}H0Z`;\n\t\t\t} else {\n\t\t\t\tconst arc = (r: number, x: number, y: number) => (r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : '');\n\t\t\t\td =\n\t\t\t\t\t`M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}` +\n\t\t\t\t\t`H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'ellipse': {\n\t\t\tconst rx = w / 2;\n\t\t\tconst ry = h / 2;\n\t\t\td = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'polygon':\n\t\tcase 'star': {\n\t\t\tconst points =\n\t\t\t\tshape.kind === 'polygon'\n\t\t\t\t\t? polygonVertices(Math.max(3, Math.floor(shape.sides ?? 6)), null, w, h)\n\t\t\t\t\t: polygonVertices(Math.max(3, Math.floor(shape.points ?? 5)), clampRatio(shape.innerRadius), w, h);\n\t\t\td = ringsToPath([points], f);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'path': {\n\t\t\td = shape.d;\n\t\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\t\tconst vb = validViewBox(shape.viewBox);\n\t\t\tif (vb) {\n\t\t\t\ttransforms.push(\n\t\t\t\t\t`scale(${f(w / vb[2])} ${f(h / vb[3])})`,\n\t\t\t\t\t`translate(${f(-vb[0])} ${f(-vb[1])})`\n\t\t\t\t);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn transforms.length > 0 ? { d, transform: transforms.join(' '), fillRule } : { d, fillRule };\n}\n\n/** Everything a renderer needs to draw a shape as one SVG `<path>`. */\nexport interface ShapeRenderData {\n\t/** SVG `viewBox` (`0 0 width height`): node-local pixels. */\n\tviewBox: string;\n\twidth: number;\n\theight: number;\n\td: string;\n\ttransform?: string;\n\tfillRule: PolygonFillRule;\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: number;\n\topacity?: number;\n}\n\n/**\n * Resolves the SVG drawing of a shape for a node size. Without a size, a path's view box\n * size (or 100x100) is used. Paint defaults: `fill: currentColor`, `stroke: none`,\n * `strokeWidth: 1`.\n */\nexport function getShapeRenderData(shape: ShapeDescription, size: Partial<ShapeSize> = {}): ShapeRenderData {\n\tconst vb = shape.kind === 'path' ? validViewBox(shape.viewBox) : null;\n\tconst width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;\n\tconst height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;\n\tconst svg = getShapeSvg(shape, { width, height });\n\tconst data: ShapeRenderData = {\n\t\tviewBox: `0 0 ${formatNumber(width, 3)} ${formatNumber(height, 3)}`,\n\t\twidth,\n\t\theight,\n\t\td: svg.d,\n\t\tfillRule: svg.fillRule,\n\t\tfill: shape.fill ?? 'currentColor',\n\t\tstroke: shape.stroke ?? 'none',\n\t\tstrokeWidth: shape.strokeWidth ?? 1\n\t};\n\tif (svg.transform) data.transform = svg.transform;\n\tif (shape.opacity !== undefined) data.opacity = shape.opacity;\n\treturn data;\n}\n\nfunction clampRatio(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) return 0.5;\n\treturn Math.min(1, Math.max(0.01, value));\n}\n\nfunction validViewBox(vb: PathShape['viewBox']): readonly [number, number, number, number] | null {\n\tif (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;\n\treturn vb;\n}\n\n/**\n * Converts a shape to rings in node-local pixels (rotation applied), flattening curves at\n * `tolerance`. Returns the rings with the fill rule they must be read with.\n */\nexport function shapeToPolygon(\n\tshape: ShapeDescription,\n\tsize: ShapeSize,\n\toptions: FlattenOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { rings: [], fillRule: 'nonzero' };\n\tlet rings: PolygonRing[];\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tif (shape.kind === 'path') {\n\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\tconst vb = validViewBox(shape.viewBox);\n\t\tif (vb) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\t// Flatten in path units with the tolerance scaled to stay within `tolerance` px.\n\t\t\tconst tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);\n\t\t\trings = flattenSvgPath(shape.d, { ...options, tolerance }).map((ring) =>\n\t\t\t\tring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy] as Point)\n\t\t\t);\n\t\t} else {\n\t\t\trings = flattenSvgPath(shape.d, options);\n\t\t}\n\t} else {\n\t\tconst { d } = getShapeSvg({ ...shape, rotation: 0 }, size);\n\t\trings = flattenSvgPath(d, options);\n\t}\n\tconst rotate = rotationTransform(shape, size);\n\tif (rotate) rings = rings.map((ring) => ring.map(rotate));\n\treturn { rings, fillRule };\n}\n\n// ---------------------------------------------------------------------------\n// Polygons to path data\n// ---------------------------------------------------------------------------\n\n/** Formats a number with at most `precision` decimals, without trailing zeros. */\nexport function formatNumber(value: number, precision = 3): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\nfunction ringsToPath(rings: readonly (readonly Point[])[], f: (n: number) => string): string {\n\tlet d = '';\n\tfor (const ring of rings) {\n\t\tif (ring.length < 2) continue;\n\t\td += `M${f(ring[0][0])} ${f(ring[0][1])}`;\n\t\tfor (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;\n\t\td += 'Z';\n\t}\n\treturn d;\n}\n\n/** Perpendicular distance from `p` to the segment `a`–`b`. */\nfunction segmentDistance(p: Point, a: Point, b: Point): number {\n\tconst dx = b[0] - a[0];\n\tconst dy = b[1] - a[1];\n\tconst len2 = dx * dx + dy * dy;\n\tif (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);\n\tconst t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));\n\treturn Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));\n}\n\nfunction rdp(points: Point[], tolerance: number): Point[] {\n\tif (points.length <= 2) return points;\n\tconst keep = new Uint8Array(points.length);\n\tkeep[0] = 1;\n\tkeep[points.length - 1] = 1;\n\tconst stack: [number, number][] = [[0, points.length - 1]];\n\twhile (stack.length) {\n\t\tconst [s, e] = stack.pop()!;\n\t\tlet maxDist = -1;\n\t\tlet index = -1;\n\t\tfor (let i = s + 1; i < e; i++) {\n\t\t\tconst dist = segmentDistance(points[i], points[s], points[e]);\n\t\t\tif (dist > maxDist) {\n\t\t\t\tmaxDist = dist;\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t}\n\t\tif (index !== -1 && maxDist > tolerance) {\n\t\t\tkeep[index] = 1;\n\t\t\tstack.push([s, index], [index, e]);\n\t\t}\n\t}\n\treturn points.filter((_, i) => keep[i]);\n}\n\n/**\n * Simplifies a closed ring with Ramer–Douglas–Peucker at `tolerance`. Returns an empty\n * ring when fewer than three points survive.\n */\nexport function simplifyRing(ring: readonly Point[], tolerance: number): Point[] {\n\tif (ring.length <= 3 || !(tolerance > 0)) return [...ring];\n\t// Anchor on the point farthest from the first one so the closing seam is not lost.\n\tlet far = 0;\n\tlet farDist = -1;\n\tfor (let i = 1; i < ring.length; i++) {\n\t\tconst dist = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);\n\t\tif (dist > farDist) {\n\t\t\tfarDist = dist;\n\t\t\tfar = i;\n\t\t}\n\t}\n\tconst first = rdp(ring.slice(0, far + 1) as Point[], tolerance);\n\tconst second = rdp([...ring.slice(far), ring[0]] as Point[], tolerance);\n\tconst out = [...first.slice(0, -1), ...second.slice(0, -1)];\n\treturn out.length >= 3 ? out : [];\n}\n\nexport interface PolygonToPathOptions {\n\t/** Decimal places in the output (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker tolerance; `0` (default) keeps every vertex. */\n\tsimplify?: number;\n\t/** Added to every point before formatting (e.g. to make coordinates node-local). */\n\toffset?: { x: number; y: number };\n\t/**\n\t * `'lines'` (default) writes every ring as `M`/`L`/`Z`. `'fit'` fits cubic Béziers to each\n\t * ring (`fitPathData`), so flattened curves come back as smooth `C` curves while sharp\n\t * corners (turning more than `cornerAngleDeg`) and straight edges stay as they are.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in path units for `curves: 'fit'` (default 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n}\n\n/**\n * Converts a multi-polygon (or a list of rings) to SVG path data using `M`/`L`/`Z`. Holes\n * keep their opposite orientation, so the path fills correctly with either fill rule.\n */\nexport function polygonToSvgPath(input: MultiPolygon | PolygonRing[], options: PolygonToPathOptions = {}): string {\n\tconst precision = options.precision ?? 3;\n\tconst f = (n: number) => formatNumber(n, precision);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst rings: Point[][] = [];\n\tconst fit = options.curves === 'fit';\n\tlet fitted = '';\n\tfor (const item of input as any[]) {\n\t\tconst list: Point[][] =\n\t\t\titem.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];\n\t\tfor (const ring of list) {\n\t\t\tlet r = ring;\n\t\t\tif (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);\n\t\t\tif (r.length < 3) continue;\n\t\t\tif (fit) {\n\t\t\t\tfitted += fitPathData(r, {\n\t\t\t\t\ttolerance: options.curveTolerance ?? 0.25,\n\t\t\t\t\tcornerAngleDeg: options.cornerAngleDeg,\n\t\t\t\t\tprecision,\n\t\t\t\t\toffset: { x: dx, y: dy }\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\trings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy] as Point) : r);\n\t\t}\n\t}\n\treturn fit ? fitted : ringsToPath(rings, f);\n}\n","/**\n * Shape nodes and Freeform-style shape boolean operations (Unite / Intersect / Subtract /\n * Exclude / Divide) on canvas nodes.\n *\n * A shape node is any node whose `data.shape` is a `ShapeDescription`. Its geometry is\n * relative to the node box, so moving or resizing the node moves / resizes the shape.\n */\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\twithStyleSize,\n\tLOCKED_DATA_KEY,\n\tLOCKED_PREVIOUS_FLAGS_KEY,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tdividePolygons,\n\tmultiPolygonBounds,\n\tpolygonBoolean,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule,\n\ttype PolygonRing\n} from './polygonBoolean.js';\nimport {\n\tisShapeDescription,\n\tpolygonToSvgPath,\n\tshapeToPolygon,\n\ttype PathShape,\n\ttype ShapeDescription\n} from './shapeGeometry.js';\nimport type { CanvasNode, Point } from './types.js';\n\n/** Data key holding a node's shape description. */\nexport const SHAPE_DATA_KEY = 'shape';\n\n/** Node type suggested for shape nodes (register `ShapeNode` under it). */\nexport const SHAPE_NODE_TYPE = 'shape';\n\n/** Freeform's shape operations. `exclude` is xor; `divide` splits into every region. */\nexport type ShapeOperation = 'unite' | 'intersect' | 'subtract' | 'exclude' | 'divide';\n\nexport const SHAPE_OPERATIONS: readonly ShapeOperation[] = ['unite', 'intersect', 'subtract', 'exclude', 'divide'];\n\nconst BOOLEAN_FOR: Record<Exclude<ShapeOperation, 'divide'>, PolygonBooleanOperation> = {\n\tunite: 'union',\n\tintersect: 'intersection',\n\tsubtract: 'difference',\n\texclude: 'xor'\n};\n\n/** Whether a node carries a supported `data.shape`. */\nexport function isShapeNode(node: CanvasNode | undefined | null): boolean {\n\treturn !!node && isShapeDescription(node.data?.[SHAPE_DATA_KEY]);\n}\n\n/** The node's shape description, or `null`. */\nexport function getNodeShape(node: CanvasNode | undefined | null): ShapeDescription | null {\n\tconst shape = node?.data?.[SHAPE_DATA_KEY];\n\treturn isShapeDescription(shape) ? shape : null;\n}\n\nexport interface ShapeNodePolygonOptions extends NodeRectOptions {\n\t/** Curve flattening tolerance in flow units (default 0.25). */\n\ttolerance?: number;\n}\n\n/**\n * A shape node's outline in absolute flow coordinates (parent offsets, size and rotation\n * applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToPolygon(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: ShapeNodePolygonOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });\n\treturn {\n\t\trings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y] as Point)),\n\t\tfillRule\n\t};\n}\n\n/** Why `combineShapeNodes` refused. */\nexport type CombineShapeNodesRefusal =\n\t/** A selected shape is locked; locked shapes are never consumed. */\n\t| 'locked'\n\t/** Fewer than two shape nodes were selected. */\n\t| 'too-few'\n\t/** The operation leaves nothing (e.g. intersecting shapes that do not overlap). */\n\t| 'empty';\n\nexport interface CombineShapeNodesOptions extends ShapeNodePolygonOptions {\n\t/**\n\t * Which shape's node style and data the result inherits: `bottom` (default, like\n\t * Freeform: the back-most shape), `top`, `first` (first id passed), or a node id.\n\t * Divide pieces always take the style of the top-most shape covering them.\n\t */\n\tstyleFrom?: 'bottom' | 'top' | 'first' | { id: string };\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the style source's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker simplification tolerance for the result (default 0, off). */\n\tsimplify?: number;\n\t/**\n\t * How the result's outline is written: `'fit'` (default) fits smooth cubic curves to the\n\t * flattened result, so booleans of curved shapes stay curved (sharp corners and straight\n\t * edges are kept); `'lines'` writes the polygon as straight segments.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in flow units for `curves: 'fit'` (default: `tolerance`, 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n}\n\nexport type CombineShapeNodesResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapeOperation;\n\t\t\t/** Ids of the shape nodes consumed by the operation. */\n\t\t\tremoveIds: string[];\n\t\t\t/** New path shape node(s): one, or one per region for `divide`. */\n\t\t\tnodes: TNode[];\n\t }\n\t| { ok: false; operation: ShapeOperation; reason: CombineShapeNodesRefusal };\n\nlet idCounter = 0;\nfunction defaultId(index: number): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\tidCounter += 1;\n\treturn `shape-${Date.now().toString(36)}-${idCounter}-${index}`;\n}\n\n/** Back-to-front order: `zIndex` (missing = 0), then array order, as xyflow stacks nodes. */\nfunction stackOrder<TNode extends CanvasNode>(nodes: TNode[], ids: Set<string>): TNode[] {\n\treturn nodes\n\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t.filter((entry) => ids.has(entry.node.id))\n\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t.map((entry) => entry.node);\n}\n\n/**\n * Checks whether `combineShapeNodes` can run, without computing geometry (it cannot\n * predict an `empty` result). Use it to enable menu items.\n */\nexport function canCombineShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: Exclude<CombineShapeNodesRefusal, 'empty'> } {\n\tconst idSet = new Set(ids);\n\tconst shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length < 2) return { ok: false, reason: 'too-few' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/**\n * Freeform-style shape operations on canvas nodes.\n *\n * - Only shape nodes (`data.shape`) among `ids` take part; other selected nodes are ignored.\n * - Refuses (`ok: false`) when a selected shape is locked, when fewer than two shapes are\n * selected, or when the result is empty. Nothing should be changed on refusal.\n * - `subtract` removes every other shape from the back-most one; the other operations are\n * symmetric. `divide` returns one node per distinct region of the overlay.\n * - Each new node is a `path` shape: its position / size is the result's bounding box and\n * its path data is node-local with `viewBox = [0, 0, width, height]`, so it scales with\n * the node. Rotation is baked into the path. Node fields and `data` (other than the\n * shape geometry, lock state and size) come from the style source.\n * - When all shapes share a parent, the new nodes keep it; otherwise they are top-level.\n *\n * Pure: returns the change; apply it with `applyShapeCombination` (or the arrange\n * controller's `combineShapes`, which also records one undo step).\n */\nexport function combineShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toperation: ShapeOperation,\n\toptions: CombineShapeNodesOptions = {}\n): CombineShapeNodesResult<TNode> {\n\tconst idList = [...ids];\n\tconst check = canCombineShapeNodes(nodes, idList);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\n\tconst lookup = createNodeLookup(nodes);\n\tconst shapeIds = new Set(check.shapeIds);\n\tconst ordered = stackOrder(nodes, shapeIds);\n\tconst operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options)!);\n\n\tconst parentIds = new Set(ordered.map((n) => n.parentId ?? ''));\n\tconst sharedParent = parentIds.size === 1 ? ordered[0].parentId : undefined;\n\tconst parentOrigin =\n\t\tsharedParent && lookup.get(sharedParent)\n\t\t\t? getAbsolutePosition(lookup.get(sharedParent)!, lookup)\n\t\t\t: { x: 0, y: 0 };\n\n\tconst pickSource = (): TNode => {\n\t\tconst style = options.styleFrom ?? 'bottom';\n\t\tif (style === 'top') return ordered[ordered.length - 1];\n\t\tif (style === 'first') {\n\t\t\tconst first = idList.find((id) => shapeIds.has(id));\n\t\t\treturn (first && lookup.get(first)) || ordered[0];\n\t\t}\n\t\tif (typeof style === 'object') return (shapeIds.has(style.id) && lookup.get(style.id)) || ordered[0];\n\t\treturn ordered[0];\n\t};\n\n\tconst createId = options.createId ?? defaultId;\n\tconst build = (polygon: MultiPolygon, source: TNode, index: number): TNode | null => {\n\t\tconst bounds = multiPolygonBounds(polygon);\n\t\tif (!bounds) return null;\n\t\tconst width = bounds.maxX - bounds.minX;\n\t\tconst height = bounds.maxY - bounds.minY;\n\t\tif (!(width > 0) || !(height > 0)) return null;\n\t\tconst d = polygonToSvgPath(polygon, {\n\t\t\tprecision: options.precision,\n\t\t\tsimplify: options.simplify,\n\t\t\toffset: { x: -bounds.minX, y: -bounds.minY },\n\t\t\tcurves: options.curves ?? 'fit',\n\t\t\tcurveTolerance: options.curveTolerance ?? options.tolerance ?? 0.25,\n\t\t\tcornerAngleDeg: options.cornerAngleDeg\n\t\t});\n\t\tif (!d) return null;\n\t\tconst sourceShape = getNodeShape(source)!;\n\t\tconst shape: PathShape = {\n\t\t\tkind: 'path',\n\t\t\td,\n\t\t\tviewBox: [0, 0, roundSize(width), roundSize(height)],\n\t\t\tfillRule: 'nonzero'\n\t\t};\n\t\tfor (const key of ['fill', 'stroke', 'strokeWidth', 'opacity'] as const) {\n\t\t\tif (sourceShape[key] !== undefined) (shape as any)[key] = sourceShape[key];\n\t\t}\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,\n\t\t\t...data\n\t\t} = source.data ?? {};\n\t\tconst {\n\t\t\tmeasured: _measured,\n\t\t\tselected: _selected,\n\t\t\tdragging: _dragging,\n\t\t\tpositionAbsolute: _positionAbsolute,\n\t\t\tinternals: _internals,\n\t\t\tparentId: _parentId,\n\t\t\t...rest\n\t\t} = source as CanvasNode;\n\t\tconst node: CanvasNode = {\n\t\t\t...rest,\n\t\t\tid: createId(index),\n\t\t\tposition: { x: bounds.minX - parentOrigin.x, y: bounds.minY - parentOrigin.y },\n\t\t\twidth,\n\t\t\theight,\n\t\t\tdata: { ...data, [SHAPE_DATA_KEY]: shape }\n\t\t};\n\t\tif (sharedParent) node.parentId = sharedParent;\n\t\tif (options.nodeType !== undefined) node.type = options.nodeType;\n\t\tif (source.style) node.style = withStyleSize(source.style, { width, height });\n\t\tif (options.select !== false) node.selected = true;\n\t\treturn node as TNode;\n\t};\n\n\tconst created: TNode[] = [];\n\tif (operation === 'divide') {\n\t\tconst pieces = dividePolygons(operands, { epsilon: options.epsilon });\n\t\tpieces.forEach((piece) => {\n\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\tconst node = build([piece.polygon], source, created.length);\n\t\t\tif (node) created.push(node);\n\t\t});\n\t} else {\n\t\tconst result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });\n\t\tconst node = build(result, pickSource(), 0);\n\t\tif (node) created.push(node);\n\t}\n\tif (created.length === 0) return { ok: false, operation, reason: 'empty' };\n\treturn { ok: true, operation, removeIds: ordered.map((n) => n.id), nodes: created };\n}\n\nfunction roundSize(value: number): number {\n\treturn Math.round(value * 1000) / 1000;\n}\n\n/**\n * Applies a successful `combineShapeNodes` result: removes the consumed nodes and inserts\n * the new ones at the array index of the first consumed node.\n * With `deselect` (default `true`) other nodes are deselected when the result is selected.\n */\nexport function applyShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst remove = new Set(change.removeIds);\n\tconst insertAt = nodes.findIndex((n) => remove.has(n.id));\n\tconst deselect = options.deselect !== false && change.nodes.some((n) => n.selected);\n\tconst out: TNode[] = [];\n\tnodes.forEach((node, index) => {\n\t\tif (index === insertAt) out.push(...change.nodes);\n\t\tif (remove.has(node.id)) return;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t});\n\tif (insertAt === -1) out.push(...change.nodes);\n\treturn out;\n}\n\n/**\n * Reverses `applyShapeCombination`: removes the created nodes and puts the consumed nodes\n * back at their original array indices (`previous` is the node array before the change).\n */\nexport function revertShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\tprevious: TNode[]\n): TNode[] {\n\tconst created = new Set(change.nodes.map((n) => n.id));\n\tconst removed = new Set(change.removeIds);\n\tconst out = nodes.filter((n) => !created.has(n.id));\n\tprevious.forEach((node, index) => {\n\t\tif (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;\n\t\tout.splice(Math.min(index, out.length), 0, node);\n\t});\n\treturn out;\n}\n","/**\n * Resize snapping: the moving edges of a box being resized land on other shapes' edges and\n * centres (or on a grid) while the opposite edge / corner stays put.\n *\n * Portions ported from vectorcraft-main/crates/tools/src/guides.rs (`snap_scale`) and\n * vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\ttype CanvasNodeUpdate,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tcreateGuideIndex,\n\tcreateNodeGuideIndex,\n\tsnapToGrid,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions\n} from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** A resize handle, named like xyflow's `ControlPosition`. */\nexport type ResizeHandle =\n\t| 'top-left'\n\t| 'top'\n\t| 'top-right'\n\t| 'right'\n\t| 'bottom-right'\n\t| 'bottom'\n\t| 'bottom-left'\n\t| 'left';\n\nexport const RESIZE_HANDLES: readonly ResizeHandle[] = [\n\t'top-left',\n\t'top',\n\t'top-right',\n\t'right',\n\t'bottom-right',\n\t'bottom',\n\t'bottom-left',\n\t'left'\n];\n\n/** Which edge of each axis a handle moves: -1 the start (left / top), 1 the end, 0 none. */\nexport function resizeHandleAxes(handle: ResizeHandle): { x: -1 | 0 | 1; y: -1 | 0 | 1 } {\n\tconst x = handle.includes('left') ? -1 : handle.includes('right') ? 1 : 0;\n\tconst y = handle.includes('top') ? -1 : handle.includes('bottom') ? 1 : 0;\n\treturn { x, y };\n}\n\n/**\n * The handle a resize from `start` to `next` was made with, from the edges that moved, or\n * `null` when no edge moved.\n */\nexport function inferResizeHandle(start: Rect, next: Rect, eps = 1e-6): ResizeHandle | null {\n\tconst moved = (a: number, b: number) => Math.abs(a - b) > eps;\n\tconst left = moved(start.x, next.x);\n\tconst right = moved(start.x + start.width, next.x + next.width);\n\tconst top = moved(start.y, next.y);\n\tconst bottom = moved(start.y + start.height, next.y + next.height);\n\tconst h = left ? 'left' : right ? 'right' : '';\n\tconst v = top ? 'top' : bottom ? 'bottom' : '';\n\tif (h && v) return `${v}-${h}` as ResizeHandle;\n\treturn (h || v || null) as ResizeHandle | null;\n}\n\nexport interface SnapResizeOptions extends Omit<SnapGuidesOptions, 'spacing'> {\n\t/**\n\t * Keep the input box's aspect ratio. A corner snaps whichever axis is closer to a target\n\t * and the other follows; a side scales the other axis about its centre.\n\t */\n\tkeepAspectRatio?: boolean;\n\t/** Smallest width a snap may leave (default: anything above 0). */\n\tminWidth?: number;\n\t/** Smallest height a snap may leave (default: anything above 0). */\n\tminHeight?: number;\n}\n\nexport interface SnapResizeResult {\n\t/** The snapped box. */\n\trect: Rect;\n\t/** Guide segments for the shape snaps that were applied (grid snaps draw none). */\n\tguides: GuideLine[];\n\t/** Whether the moving x edge snapped to a shape. */\n\tsnappedX: boolean;\n\t/** Whether the moving y edge snapped to a shape. */\n\tsnappedY: boolean;\n\t/** Whether `rect` differs from the input. */\n\tchanged: boolean;\n}\n\ninterface Hit {\n\tvalue: number;\n\tkind: 'edge' | 'centre' | 'grid';\n}\n\ntype Axis = GuideIndex['edgeX'];\n\nfunction within(axis: Axis, v: number, tolerance: number): number[] {\n\tconst { values } = axis;\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v - tolerance) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\tconst out: number[] = [];\n\tfor (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);\n\treturn out;\n}\n\nfunction matching(axis: Axis, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);\n\treturn out;\n}\n\n/**\n * Snaps a resize of the axis-aligned box `rect` made with `handle` against `targets`. The\n * opposite edge (side handles) or corner (corner handles) stays fixed:\n *\n * - A **side** handle snaps its one moving edge to the nearest target edge or centre within\n * `threshold` screen pixels.\n * - A **corner** handle snaps x and y independently (unless `keepAspectRatio`).\n * - A snap that would collapse or flip the box (or go below `minWidth` / `minHeight`) is\n * rejected; the next nearest target is tried, else the edge stays where it was dragged.\n * - With `gridSize`, an axis without a shape snap snaps its moving edge to the grid.\n *\n * Returns the snapped box and the guides to draw (same shape as drag snapping).\n */\nexport function snapResize(\n\trect: Rect,\n\thandle: ResizeHandle,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapResizeOptions = {}\n): SnapResizeResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst eps = 0.5 / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst grid = options.gridSize ?? 0;\n\tconst axes = resizeHandleAxes(handle);\n\tconst unchanged: SnapResizeResult = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };\n\tif (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;\n\n\tconst x0 = rect.x;\n\tconst x1 = rect.x + rect.width;\n\tconst y0 = rect.y;\n\tconst y1 = rect.y + rect.height;\n\n\t/** Best valid target for one moving edge, or `null`. */\n\tconst bestHit = (horizontal: boolean): Hit | null => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (dir === 0) return null;\n\t\tconst moving = horizontal ? (dir < 0 ? x0 : x1) : dir < 0 ? y0 : y1;\n\t\tconst fixed = horizontal ? (dir < 0 ? x1 : x0) : dir < 0 ? y1 : y0;\n\t\tconst min = (horizontal ? options.minWidth : options.minHeight) ?? 0;\n\t\tconst valid = (t: number) => {\n\t\t\tconst size = dir < 0 ? fixed - t : t - fixed;\n\t\t\treturn Number.isFinite(size) && size > 1e-9 && size >= min;\n\t\t};\n\t\tlet best: Hit | null = null;\n\t\tconst consider = (values: number[], kind: Hit['kind']) => {\n\t\t\tfor (const t of values) {\n\t\t\t\tif (!valid(t)) continue;\n\t\t\t\tif (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };\n\t\t\t}\n\t\t};\n\t\tif (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), 'edge');\n\t\tif (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), 'centre');\n\t\tif (!best && grid > 0) {\n\t\t\tconst origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;\n\t\t\tconsider([snapToGrid(moving, grid, origin)], 'grid');\n\t\t}\n\t\treturn best;\n\t};\n\n\tlet nx0 = x0;\n\tlet nx1 = x1;\n\tlet ny0 = y0;\n\tlet ny1 = y1;\n\tconst setEdge = (horizontal: boolean, value: number) => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (horizontal) {\n\t\t\tif (dir < 0) nx0 = value;\n\t\t\telse nx1 = value;\n\t\t} else if (dir < 0) ny0 = value;\n\t\telse ny1 = value;\n\t};\n\n\tlet hitX = bestHit(true);\n\tlet hitY = bestHit(false);\n\tconst ratio = rect.height > 0 ? rect.width / rect.height : 0;\n\n\tif (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {\n\t\tif (axes.x !== 0 && axes.y !== 0) {\n\t\t\t// Corner: the closer snap wins; the other axis follows from the fixed corner.\n\t\t\tconst dist = (h: Hit | null, horizontal: boolean) =>\n\t\t\t\th ? Math.abs(h.value - (horizontal ? (axes.x < 0 ? x0 : x1) : axes.y < 0 ? y0 : y1)) : Infinity;\n\t\t\tconst useX = dist(hitX, true) <= dist(hitY, false);\n\t\t\tif (useX && hitX) {\n\t\t\t\tsetEdge(true, hitX.value);\n\t\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\t\tif (axes.y < 0) ny0 = ny1 - height;\n\t\t\t\telse ny1 = ny0 + height;\n\t\t\t\thitY = null;\n\t\t\t} else if (hitY) {\n\t\t\t\tsetEdge(false, hitY.value);\n\t\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\t\tif (axes.x < 0) nx0 = nx1 - width;\n\t\t\t\telse nx1 = nx0 + width;\n\t\t\t\thitX = null;\n\t\t\t}\n\t\t\tconst min = (v: number | undefined) => v ?? 0;\n\t\t\tif (nx1 - nx0 < min(options.minWidth) || ny1 - ny0 < min(options.minHeight) || !(nx1 > nx0 && ny1 > ny0)) {\n\t\t\t\treturn unchanged;\n\t\t\t}\n\t\t} else if (axes.x !== 0 && hitX) {\n\t\t\tsetEdge(true, hitX.value);\n\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\tconst cy = (y0 + y1) / 2;\n\t\t\tny0 = cy - height / 2;\n\t\t\tny1 = cy + height / 2;\n\t\t} else if (axes.y !== 0 && hitY) {\n\t\t\tsetEdge(false, hitY.value);\n\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\tconst cx = (x0 + x1) / 2;\n\t\t\tnx0 = cx - width / 2;\n\t\t\tnx1 = cx + width / 2;\n\t\t}\n\t} else {\n\t\tif (hitX) setEdge(true, hitX.value);\n\t\tif (hitY) setEdge(false, hitY.value);\n\t}\n\n\tconst out: Rect = { x: nx0, y: ny0, width: nx1 - nx0, height: ny1 - ny0 };\n\tconst guides: GuideLine[] = [];\n\tconst guideFor = (horizontal: boolean, hit: Hit | null) => {\n\t\tif (!hit || hit.kind === 'grid') return;\n\t\tconst axis =\n\t\t\thit.kind === 'edge' ? (horizontal ? index.edgeX : index.edgeY) : horizontal ? index.centreX : index.centreY;\n\t\tconst aligned = matching(axis, hit.value, eps);\n\t\tconst span = boundsOf([out, ...aligned])!;\n\t\tguides.push(\n\t\t\thorizontal\n\t\t\t\t? { orientation: 'vertical', kind: hit.kind, x1: hit.value, y1: span.y, x2: hit.value, y2: span.y + span.height }\n\t\t\t\t: { orientation: 'horizontal', kind: hit.kind, x1: span.x, y1: hit.value, x2: span.x + span.width, y2: hit.value }\n\t\t);\n\t};\n\tguideFor(true, hitX);\n\tguideFor(false, hitY);\n\tconst changed = out.x !== rect.x || out.y !== rect.y || out.width !== rect.width || out.height !== rect.height;\n\treturn {\n\t\trect: out,\n\t\tguides,\n\t\tsnappedX: !!hitX && hitX.kind !== 'grid',\n\t\tsnappedY: !!hitY && hitY.kind !== 'grid',\n\t\tchanged\n\t};\n}\n\nexport interface SnapNodeResizeOptions extends SnapResizeOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex(nodes, [id])`, reused across resize frames. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeResizeResult extends SnapResizeResult {\n\t/** Position / size update for the node (parent-relative position), or none. */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a node that is being resized. `rect` is the node's proposed absolute box (e.g. from\n * a resize event) and `handle` the handle being dragged. Locked nodes are never snapped.\n * The node itself and its descendants are not snap targets.\n */\nexport function snapNodeResize(\n\tnodes: CanvasNode[],\n\tid: string,\n\trect: Rect,\n\thandle: ResizeHandle,\n\toptions: SnapNodeResizeOptions = {}\n): SnapNodeResizeResult {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node || isLocked(node)) {\n\t\treturn { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };\n\t}\n\tconst index = options.index ?? createNodeGuideIndex(nodes, [id], options);\n\tconst result = snapResize(rect, handle, index, options);\n\tif (!result.changed) return { ...result, updates: [] };\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\tconst origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };\n\treturn {\n\t\t...result,\n\t\tupdates: [\n\t\t\t{\n\t\t\t\tid,\n\t\t\t\tposition: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },\n\t\t\t\twidth: result.rect.width,\n\t\t\t\theight: result.rect.height\n\t\t\t}\n\t\t]\n\t};\n}\n\n/**\n * Absolute box of a node while it is being resized: flow libraries write the new size to\n * `width` / `height` before the DOM is re-measured, so those win over `measured` here.\n */\nexport function getResizingNodeRect(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): Rect {\n\tconst { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;\n\tconst size = getNodeSize(node, options);\n\treturn { x, y, width: node.width ?? size.width, height: node.height ?? size.height };\n}\n","// Portions ported from figcraft/crates/layout/src/lib.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { createNodeLookup, getAbsolutePosition, getNodeSize, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/**\n * Figma-style auto layout for xyflow sub-flows.\n *\n * A *frame* is any node with `data.layout` (an {@link AutoLayout}); its children are the nodes\n * whose `parentId` is the frame, in node-array order. Children may carry `data.childLayout`\n * (an {@link AutoLayoutChild}) for their own sizing, min / max, absolute positioning,\n * constraints and grid cell. The functions here are pure: they return `CanvasNodeUpdate`s\n * (positions relative to the parent, sizes) for `applyNodeUpdates`.\n */\n\n/** Data key holding a frame's {@link AutoLayout}. */\nexport const AUTO_LAYOUT_DATA_KEY = 'layout';\n/** Data key holding a child's {@link AutoLayoutChild}. */\nexport const CHILD_LAYOUT_DATA_KEY = 'childLayout';\n\nexport type AutoLayoutMode = 'horizontal' | 'vertical' | 'grid';\n/** `fixed` keeps the size, `hug` fits the content (frames and text), `fill` shares the parent's free space. */\nexport type AutoLayoutSizing = 'fixed' | 'hug' | 'fill';\nexport type AutoLayoutAlignMain = 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';\nexport type AutoLayoutAlignCross = 'start' | 'center' | 'end' | 'stretch' | 'baseline';\n/** How wrapped lines share the free cross space: packed by `alignCross` (`auto`) or spread (`space-between`). */\nexport type AutoLayoutAlignContent = 'auto' | 'space-between';\n/** `number` (all sides), `[vertical, horizontal]`, `[top, right, bottom, left]` or per side. */\nexport type AutoLayoutPadding =\n\t| number\n\t| [number, number]\n\t| [number, number, number, number]\n\t| { top?: number; right?: number; bottom?: number; left?: number };\n\nexport interface AutoLayoutSize {\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface AutoLayoutSizingPair {\n\twidth?: AutoLayoutSizing;\n\theight?: AutoLayoutSizing;\n}\n\n/** A grid track: pixels, `'hug'` (fits its content), `'2fr'` or `{ fr: 2 }` (shares the rest like CSS `fr`). */\nexport type AutoLayoutGridTrack = number | 'hug' | `${number}fr` | { fr: number };\n\nexport interface AutoLayoutGrid {\n\t/** Column count (all `1fr`) or the column tracks (default 2 columns). */\n\tcolumns?: number | AutoLayoutGridTrack[];\n\t/** Minimum row count or the row tracks (default 1); rows are added to fit the children. Missing tracks are `1fr`. */\n\trows?: number | AutoLayoutGridTrack[];\n}\n\n/** A frame's auto layout (`node.data.layout`). */\nexport interface AutoLayout {\n\tmode: AutoLayoutMode;\n\t/** Main-axis gap (grid: column gap). Negative gaps overlap. Default 0. */\n\tgap?: number;\n\t/** Gap between wrapped lines (grid: row gap). Default: `gap`. */\n\trowGap?: number;\n\t/** Default 0. */\n\tpadding?: AutoLayoutPadding;\n\t/** Horizontal / vertical: wrap into lines when the main axis runs out (ignored while hugging it). */\n\twrap?: boolean;\n\t/** Default `start`. Grid: the cell alignment along x (`start`, `center`, `end`). */\n\talignMain?: AutoLayoutAlignMain;\n\t/** Default `start`. `baseline` applies to horizontal layouts. Grid: the cell alignment along y. */\n\talignCross?: AutoLayoutAlignCross;\n\talignContent?: AutoLayoutAlignContent;\n\t/** The frame's own sizing (`childLayout.sizing` wins when both are set). Default `fixed`. */\n\tsizing?: AutoLayoutSizingPair;\n\t/** The frame's own limits (`childLayout.min` / `max` win when set). */\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\tgrid?: AutoLayoutGrid;\n}\n\nexport type AutoLayoutHorizontalConstraint = 'left' | 'right' | 'leftRight' | 'center' | 'scale';\nexport type AutoLayoutVerticalConstraint = 'top' | 'bottom' | 'topBottom' | 'center' | 'scale';\n\nexport interface AutoLayoutConstraints {\n\thorizontal?: AutoLayoutHorizontalConstraint;\n\tvertical?: AutoLayoutVerticalConstraint;\n}\n\nexport type AutoLayoutCellAlign = 'start' | 'center' | 'end';\n\n/** A grid child's cell. Column / row are 0-based anchors; unanchored children flow in reading order. */\nexport interface AutoLayoutGridArea {\n\tcolumn?: number;\n\trow?: number;\n\tcolumnSpan?: number;\n\trowSpan?: number;\n\t/** The child's own alignment in its cell (wins over the frame's). */\n\talignX?: AutoLayoutCellAlign;\n\talignY?: AutoLayoutCellAlign;\n}\n\n/** A child's layout properties (`node.data.childLayout`). */\nexport interface AutoLayoutChild {\n\tsizing?: AutoLayoutSizingPair;\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\t/** Ignore auto layout: keep its place and follow `constraints` when the frame resizes. */\n\tabsolute?: boolean;\n\t/** How the child follows its parent's resizes (default left / top). */\n\tconstraints?: AutoLayoutConstraints;\n\tgridArea?: AutoLayoutGridArea;\n}\n\n/**\n * Measuring hooks. `size` defaults to `getNodeSize` (xyflow `measured` size, then `width` /\n * `height`, then style). Nodes for which `textSize` returns a size are *text-like*: they can\n * hug, and wrap (`textHeight`) when their width is set by the layout.\n */\nexport interface AutoLayoutMeasure {\n\tsize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Natural (unwrapped) size of a text-like node. */\n\ttextSize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Height of a text-like node wrapped at `width`. */\n\ttextHeight?: (node: CanvasNode, width: number) => number | undefined;\n\t/** Distance from the node's top to its first text baseline (for `alignCross: 'baseline'`). */\n\ttextBaseline?: (node: CanvasNode) => number | undefined;\n}\n\nexport interface AutoLayoutOptions {\n\tmeasure?: AutoLayoutMeasure;\n\t/** Size resolution for unmeasured nodes when `measure.size` is not given. */\n\trectOptions?: NodeRectOptions;\n}\n\ntype Axis = 'width' | 'height';\n\nconst MODES = new Set(['horizontal', 'vertical', 'grid']);\nconst EPS = 1e-6;\nconst MAX_TRACKS = 1000;\nconst MAX_GRID_CELLS = 1_000_000;\n\n/** Clamps a size to `[min, max]`; non-finite or negative sizes become 0 and min wins over a smaller max. */\nexport function clampLayoutSize(value: number, min?: number, max?: number): number {\n\tlet v = Number.isFinite(value) ? Math.max(value, 0) : 0;\n\tif (typeof max === 'number' && Number.isFinite(max)) v = Math.min(v, max);\n\tif (typeof min === 'number' && Number.isFinite(min)) v = Math.max(v, min);\n\treturn v;\n}\n\nconst finite = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0);\n\n/** The node's auto layout, or `undefined` when it is not an auto layout frame. */\nexport function getAutoLayout(node: CanvasNode | undefined | null): AutoLayout | undefined {\n\tconst layout = node?.data?.[AUTO_LAYOUT_DATA_KEY];\n\treturn layout && typeof layout === 'object' && MODES.has(layout.mode) ? (layout as AutoLayout) : undefined;\n}\n\n/** Whether the node is an auto layout frame. */\nexport function isAutoLayoutFrame(node: CanvasNode | undefined | null): boolean {\n\treturn getAutoLayout(node) !== undefined;\n}\n\n/** The node's child layout properties (empty when unset). */\nexport function getChildLayout(node: CanvasNode | undefined | null): AutoLayoutChild {\n\tconst child = node?.data?.[CHILD_LAYOUT_DATA_KEY];\n\treturn child && typeof child === 'object' ? (child as AutoLayoutChild) : {};\n}\n\n/** Effective sizing of a node on one axis: `childLayout.sizing`, then `layout.sizing`, then `fixed`. */\nexport function getLayoutSizing(node: CanvasNode, axis: Axis): AutoLayoutSizing {\n\tconst value = getChildLayout(node).sizing?.[axis] ?? getAutoLayout(node)?.sizing?.[axis];\n\treturn value === 'hug' || value === 'fill' ? value : 'fixed';\n}\n\nfunction limitsOf(node: CanvasNode) {\n\tconst child = getChildLayout(node);\n\tconst layout = getAutoLayout(node);\n\tconst min = child.min ?? layout?.min ?? {};\n\tconst max = child.max ?? layout?.max ?? {};\n\treturn { minW: min.width, maxW: max.width, minH: min.height, maxH: max.height };\n}\n\n/** Padding as `[top, right, bottom, left]`, sanitised (non-finite and negative values become 0). */\nexport function resolvePadding(padding: AutoLayoutPadding | undefined): [number, number, number, number] {\n\tlet sides: [unknown, unknown, unknown, unknown];\n\tif (typeof padding === 'number') sides = [padding, padding, padding, padding];\n\telse if (Array.isArray(padding)) {\n\t\tsides = padding.length === 2 ? [padding[0], padding[1], padding[0], padding[1]] : [padding[0], padding[1], padding[2], padding[3]];\n\t} else if (padding && typeof padding === 'object') sides = [padding.top, padding.right, padding.bottom, padding.left];\n\telse sides = [0, 0, 0, 0];\n\treturn sides.map((p) => Math.max(finite(p), 0)) as [number, number, number, number];\n}\n\ninterface Box {\n\tid: string;\n\tnode: CanvasNode;\n\tparent?: string;\n\tx: number;\n\ty: number;\n\tw: number;\n\th: number;\n\tkids: string[];\n}\n\n/** Working copy of the node geometry the layout passes mutate. */\nclass LayoutState {\n\treadonly boxes = new Map<string, Box>();\n\treadonly measure: AutoLayoutMeasure;\n\tprivate readonly visiting = new Set<string>();\n\tprivate readonly original = new Map<string, { x: number; y: number; w: number; h: number }>();\n\n\tconstructor(nodes: CanvasNode[], options: AutoLayoutOptions = {}) {\n\t\tthis.measure = options.measure ?? {};\n\t\tfor (const node of nodes) {\n\t\t\tconst size = this.measure.size?.(node) ?? getNodeSize(node, options.rectOptions);\n\t\t\tconst box: Box = {\n\t\t\t\tid: node.id,\n\t\t\t\tnode,\n\t\t\t\tparent: node.parentId,\n\t\t\t\tx: finite(node.position?.x),\n\t\t\t\ty: finite(node.position?.y),\n\t\t\t\tw: finite(size.width),\n\t\t\t\th: finite(size.height),\n\t\t\t\tkids: []\n\t\t\t};\n\t\t\tthis.boxes.set(node.id, box);\n\t\t\tthis.original.set(node.id, { x: box.x, y: box.y, w: box.w, h: box.h });\n\t\t}\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && node.parentId !== node.id) this.boxes.get(node.parentId)?.kids.push(node.id);\n\t\t}\n\t}\n\n\tupdates(): CanvasNodeUpdate[] {\n\t\tconst out: CanvasNodeUpdate[] = [];\n\t\tfor (const [id, box] of this.boxes) {\n\t\t\tconst o = this.original.get(id)!;\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (Math.abs(box.x - o.x) > EPS || Math.abs(box.y - o.y) > EPS) update.position = { x: box.x, y: box.y };\n\t\t\tif (Math.abs(box.w - o.w) > EPS) update.width = box.w;\n\t\t\tif (Math.abs(box.h - o.h) > EPS) update.height = box.h;\n\t\t\tif (update.position || update.width !== undefined || update.height !== undefined) out.push(update);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Runs `fn` for `id` unless it is already on the stack (guards `parentId` cycles). */\n\tguard(id: string, fn: () => void) {\n\t\tif (this.visiting.has(id)) return;\n\t\tthis.visiting.add(id);\n\t\ttry {\n\t\t\tfn();\n\t\t} finally {\n\t\t\tthis.visiting.delete(id);\n\t\t}\n\t}\n\n\tancestors(id: string): string[] {\n\t\tconst out: string[] = [];\n\t\tconst seen = new Set([id]);\n\t\tlet parent = this.boxes.get(id)?.parent;\n\t\twhile (parent && !seen.has(parent) && this.boxes.has(parent)) {\n\t\t\tout.push(parent);\n\t\t\tseen.add(parent);\n\t\t\tparent = this.boxes.get(parent)!.parent;\n\t\t}\n\t\treturn out;\n\t}\n}\n\nfunction setSize(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.w = Number.isFinite(w) ? Math.max(w, 0) : b.w;\n\tb.h = Number.isFinite(h) ? Math.max(h, 0) : b.h;\n}\n\nfunction place(s: LayoutState, id: string, x: number, y: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.x = x;\n\tb.y = y;\n}\n\nconst isText = (s: LayoutState, b: Box) => !!s.measure.textSize && s.measure.textSize(b.node) !== undefined;\n\n/** The children an auto layout frame places: visible and not absolute. */\nfunction flowChildren(s: LayoutState, frame: string): string[] {\n\tconst f = s.boxes.get(frame);\n\tif (!f) return [];\n\treturn f.kids.filter((k) => {\n\t\tconst n = s.boxes.get(k)!.node;\n\t\treturn n.hidden !== true && !getChildLayout(n).absolute;\n\t});\n}\n\n/** Text that hugs its width refreshes its natural size (wrapping at its max width); a hug-height text gets its wrapped height. */\nfunction fitHugText(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b || !isText(s, b)) return;\n\tconst natural = s.measure.textSize!(b.node)!;\n\tconst { minW, maxW } = limitsOf(b.node);\n\tif (getLayoutSizing(b.node, 'width') === 'hug') {\n\t\tconst w = clampLayoutSize(natural.width, minW, maxW);\n\t\tconst limited = Math.abs(w - natural.width) > EPS;\n\t\tconst h = limited ? s.measure.textHeight?.(b.node, w) ?? natural.height : natural.height;\n\t\tsetSize(s, id, w, h);\n\t} else if (getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, b.w, s.measure.textHeight?.(b.node, b.w) ?? b.h);\n\t}\n}\n\n/** Clamps an in-flow child's own size to its min / max. */\nfunction enforceLimits(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tconst { minW, maxW, minH, maxH } = limitsOf(b.node);\n\tif (minW === undefined && maxW === undefined && minH === undefined && maxH === undefined) return;\n\tconst nw = clampLayoutSize(b.w, minW, maxW);\n\tconst nh = clampLayoutSize(b.h, minH, maxH);\n\tif (Math.abs(nw - b.w) < 1e-9 && Math.abs(nh - b.h) < 1e-9) return;\n\tif (isText(s, b)) setSize(s, id, nw, nh);\n\telse resizeChild(s, id, nw, nh);\n}\n\nfunction itemBaseline(s: LayoutState, id: string, depth: number): number | undefined {\n\tconst b = s.boxes.get(id);\n\tif (!b) return undefined;\n\tif (isText(s, b)) {\n\t\tconst v = s.measure.textBaseline?.(b.node);\n\t\treturn typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\t}\n\tconst layout = getAutoLayout(b.node);\n\tif (!layout || layout.mode === 'grid' || depth >= 32) return undefined;\n\tfor (const k of flowChildren(s, id)) {\n\t\tconst base = itemBaseline(s, k, depth + 1);\n\t\tif (base !== undefined) return s.boxes.get(k)!.y + base;\n\t}\n\treturn undefined;\n}\n\n/** Resizes a child; text with hugging height re-wraps, frames apply constraints and re-run their layout. */\nfunction resizeChild(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tif (isText(s, b) && getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, w, s.measure.textHeight?.(b.node, w) ?? h);\n\t\treturn;\n\t}\n\tconst ow = b.w;\n\tconst oh = b.h;\n\tsetSize(s, id, w, h);\n\tif (b.kids.length > 0) constrain(s, id, ow, oh);\n\tif (getAutoLayout(b.node)) layout(s, id);\n}\n\ninterface Item {\n\tid: string;\n\tmain: number;\n\tcross: number;\n\tfillMain: boolean;\n\tfillCross: boolean;\n\tminMain?: number;\n\tmaxMain?: number;\n\tminCross?: number;\n\tmaxCross?: number;\n\tbaseline?: number;\n\tbaseOff: number;\n}\n\n/** Where the first child starts and the step between children along the main axis. */\nfunction mainOffsets(align: AutoLayoutAlignMain, inner: number, total: number, n: number, gap: number): [number, number] {\n\tconst gaps = gap * Math.max(n - 1, 0);\n\tconst free = inner - total;\n\tconst k = Math.max(n, 1);\n\tswitch (align) {\n\t\tcase 'center':\n\t\t\treturn [(free - gaps) / 2, gap];\n\t\tcase 'end':\n\t\t\treturn [free - gaps, gap];\n\t\tcase 'space-between':\n\t\t\treturn [0, n > 1 ? Math.max(free / (n - 1), 0) : 0];\n\t\tcase 'space-around': {\n\t\t\tconst g = Math.max(free / k, 0);\n\t\t\treturn [g / 2, g];\n\t\t}\n\t\tcase 'space-evenly': {\n\t\t\tconst g = Math.max(free / (k + 1), 0);\n\t\t\treturn [g, g];\n\t\t}\n\t\tdefault:\n\t\t\treturn [0, gap];\n\t}\n}\n\n/**\n * Shares `free` main-axis space among the fill items like CSS flexbox: equal shares within\n * min / max; when clamping adds more than it removes, the items held at their min keep it\n * (otherwise those held at their max do) and the rest share what is left again.\n */\nfunction distribute(items: Item[], open: number[], free: number) {\n\tfree = Math.max(free, 0);\n\twhile (open.length > 0) {\n\t\tconst share = free / open.length;\n\t\tconst sized = open.map((i) => [i, clampLayoutSize(share, items[i].minMain, items[i].maxMain)] as const);\n\t\tconst violation = sized.reduce((sum, [, v]) => sum + (v - share), 0);\n\t\tconst frozen = (v: number) =>\n\t\t\tMath.abs(violation) < 1e-9 || (violation > 0 && v > share + 1e-9) || (violation < 0 && v < share - 1e-9);\n\t\tfor (const [i, v] of sized) {\n\t\t\tif (frozen(v)) {\n\t\t\t\titems[i].main = v;\n\t\t\t\tfree -= v;\n\t\t\t}\n\t\t}\n\t\tfree = Math.max(free, 0);\n\t\topen = sized.filter(([, v]) => !frozen(v)).map(([i]) => i);\n\t}\n}\n\nfunction layout(s: LayoutState, frame: string) {\n\ts.guard(frame, () => layoutFrameInner(s, frame));\n}\n\nfunction layoutFrameInner(s: LayoutState, frame: string) {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al) return;\n\tconst horizontal = al.mode !== 'vertical';\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ow = f.w;\n\tconst oh = f.h;\n\tconst fl = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst fw = Math.max(clampLayoutSize(ow, fl.minW, fl.maxW), pl + pr);\n\tconst fh = Math.max(clampLayoutSize(oh, fl.minH, fl.maxH), pt + pb);\n\tconst kids = flowChildren(s, frame);\n\n\tfor (const c of kids) {\n\t\tfitHugText(s, c);\n\t\tenforceLimits(s, c);\n\t}\n\n\tif (al.mode === 'grid') {\n\t\tlayoutGrid(s, frame, al);\n\t\treturn;\n\t}\n\n\tconst innerMainFixed = horizontal ? fw - pl - pr : fh - pt - pb;\n\tconst innerCrossFixed = horizontal ? fh - pt - pb : fw - pl - pr;\n\tconst hugMain = horizontal ? hugW : hugH;\n\tconst hugCross = horizontal ? hugH : hugW;\n\tconst alignCross = al.alignCross ?? 'start';\n\tconst baseline = horizontal && alignCross === 'baseline';\n\tconst stretch = alignCross === 'stretch';\n\n\tconst items: Item[] = [];\n\tfor (const c of kids) {\n\t\tconst b = s.boxes.get(c)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tconst sm = getLayoutSizing(b.node, horizontal ? 'width' : 'height');\n\t\tconst sc = getLayoutSizing(b.node, horizontal ? 'height' : 'width');\n\t\titems.push({\n\t\t\tid: c,\n\t\t\tmain: horizontal ? b.w : b.h,\n\t\t\tcross: horizontal ? b.h : b.w,\n\t\t\t// Fill needs room to fill: on an axis the frame hugs it reads as fixed.\n\t\t\tfillMain: sm === 'fill' && !hugMain,\n\t\t\tfillCross: (sc === 'fill' || (stretch && sc !== 'hug')) && !hugCross,\n\t\t\tminMain: horizontal ? lim.minW : lim.minH,\n\t\t\tmaxMain: horizontal ? lim.maxW : lim.maxH,\n\t\t\tminCross: horizontal ? lim.minH : lim.minW,\n\t\t\tmaxCross: horizontal ? lim.maxH : lim.maxW,\n\t\t\tbaseOff: 0\n\t\t});\n\t}\n\n\tconst gap = finite(al.gap);\n\tconst alignMain = al.alignMain ?? 'start';\n\tconst autoGap = alignMain === 'space-between' || alignMain === 'space-around' || alignMain === 'space-evenly';\n\tconst wraps = !!al.wrap && !hugMain;\n\tconst sizeOf = (id: string) => {\n\t\tconst b = s.boxes.get(id)!;\n\t\treturn horizontal ? [b.w, b.h] : [b.h, b.w];\n\t};\n\tconst resize = (it: Item, main: number, cross: number) =>\n\t\tresizeChild(s, it.id, horizontal ? main : cross, horizontal ? cross : main);\n\n\t// Fill across a single line: resize those children first so their main size is final.\n\tif (!wraps) {\n\t\tfor (const it of items) {\n\t\t\tif (!it.fillCross || it.fillMain) continue;\n\t\t\tresize(it, it.main, clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross));\n\t\t\tit.main = sizeOf(it.id)[0];\n\t\t}\n\t}\n\n\t// Split into lines along the main axis.\n\tconst lines: number[][] = [];\n\tif (wraps) {\n\t\tlet cur: number[] = [];\n\t\tlet used = 0;\n\t\titems.forEach((it, i) => {\n\t\t\tconst sz = it.fillMain ? it.minMain ?? 0 : it.main;\n\t\t\tconst add = cur.length === 0 ? sz : Math.max(gap, 0) + sz;\n\t\t\tif (cur.length > 0 && used + add > innerMainFixed + 1e-6) {\n\t\t\t\tlines.push(cur);\n\t\t\t\tcur = [];\n\t\t\t\tused = sz;\n\t\t\t} else {\n\t\t\t\tused += add;\n\t\t\t}\n\t\t\tcur.push(i);\n\t\t});\n\t\tif (cur.length > 0) lines.push(cur);\n\t} else {\n\t\tlines.push(items.map((_, i) => i));\n\t}\n\n\t// Share each line's free space among its fill children.\n\tfor (const line of lines) {\n\t\tconst fixed = line.filter((i) => !items[i].fillMain).reduce((sum, i) => sum + items[i].main, 0);\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst fills = line.filter((i) => items[i].fillMain);\n\t\tif (fills.length > 0) distribute(items, fills, innerMainFixed - fixed - (autoGap ? 0 : gaps));\n\t}\n\tfor (const it of items) {\n\t\tif (!it.fillMain) continue;\n\t\tconst cross = it.fillCross && !wraps ? clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross) : it.cross;\n\t\tresize(it, it.main, cross);\n\t\tit.cross = sizeOf(it.id)[1];\n\t}\n\tif (baseline) {\n\t\tfor (const it of items) if (!it.fillCross) it.baseline = itemBaseline(s, it.id, 0);\n\t}\n\n\t// Line extents: [main used, cross size].\n\tconst extents: [number, number][] = [];\n\tfor (const line of lines) {\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst lineBase = line.reduce((m, i) => Math.max(m, items[i].baseline ?? 0), 0);\n\t\tfor (const i of line) items[i].baseOff = items[i].baseline !== undefined ? lineBase - items[i].baseline! : 0;\n\t\tconst inLine = line.map((i) => items[i]);\n\t\tconst mainUsed = inLine.reduce((sum, it) => sum + it.main, 0) + gaps;\n\t\t// A line of nothing but fill-across children keeps their own size.\n\t\tconst allFill = inLine.every((it) => it.fillCross);\n\t\tconst cross = inLine.filter((it) => allFill || !it.fillCross).reduce((m, it) => Math.max(m, it.baseOff + it.cross), 0);\n\t\textents.push([mainUsed, cross]);\n\t}\n\n\t// Container size.\n\tconst contentMain = extents.reduce((m, l) => Math.max(m, l[0]), 0);\n\tlet crossGap = al.rowGap !== undefined ? finite(al.rowGap) : gap;\n\tconst contentCross = extents.reduce((sum, l) => sum + l[1], 0) + crossGap * Math.max(lines.length - 1, 0);\n\tlet nw = fw;\n\tlet nh = fh;\n\tif (hugMain) {\n\t\tconst v = contentMain + (horizontal ? pl + pr : pt + pb);\n\t\tif (horizontal) nw = v;\n\t\telse nh = v;\n\t}\n\tif (hugCross) {\n\t\tconst v = contentCross + (horizontal ? pt + pb : pl + pr);\n\t\tif (horizontal) nh = v;\n\t\telse nw = v;\n\t}\n\tnw = Math.max(clampLayoutSize(nw, fl.minW, fl.maxW), pl + pr);\n\tnh = Math.max(clampLayoutSize(nh, fl.minH, fl.maxH), pt + pb);\n\tsetSize(s, frame, nw, nh);\n\tconstrain(s, frame, ow, oh);\n\tconst innerMain = horizontal ? nw - pl - pr : nh - pt - pb;\n\tconst innerCross = horizontal ? nh - pt - pb : nw - pl - pr;\n\n\t// Position. Several lines share the free space across the flow.\n\tlet crossPos = horizontal ? pt : pl;\n\tconst single = lines.length === 1;\n\tif (lines.length > 1) {\n\t\tconst linesCross = extents.reduce((sum, l) => sum + l[1], 0);\n\t\tconst between = lines.length - 1;\n\t\tconst spread = (innerCross - linesCross) / between;\n\t\tconst spaceBetween = al.alignContent === 'space-between';\n\t\tif (spaceBetween && spread > 0) {\n\t\t\tcrossGap = spread;\n\t\t} else {\n\t\t\tif (spaceBetween) crossGap = 0;\n\t\t\tconst free = innerCross - linesCross - crossGap * between;\n\t\t\tif (free > 0 && items.every((it) => it.fillCross)) {\n\t\t\t\tconst extra = free / lines.length;\n\t\t\t\tfor (const l of extents) l[1] += extra;\n\t\t\t} else if (Number.isFinite(free)) {\n\t\t\t\tcrossPos += alignCross === 'center' ? free / 2 : alignCross === 'end' ? free : 0;\n\t\t\t}\n\t\t}\n\t}\n\tlines.forEach((line, li) => {\n\t\tconst [used, ownCross] = extents[li];\n\t\tconst lineCross = single ? innerCross : ownCross;\n\t\tconst total = used - gap * Math.max(line.length - 1, 0);\n\t\tlet [pos, step] = mainOffsets(alignMain, innerMain, total, line.length, gap);\n\t\tpos += horizontal ? pl : pt;\n\t\tfor (const i of line) {\n\t\t\tconst it = items[i];\n\t\t\tconst crossSize = it.fillCross ? clampLayoutSize(lineCross, it.minCross, it.maxCross) : it.cross;\n\t\t\tlet off = 0;\n\t\t\tif (alignCross === 'baseline') off = it.baseOff;\n\t\t\telse if (alignCross === 'center') off = (lineCross - crossSize) / 2;\n\t\t\telse if (alignCross === 'end') off = lineCross - crossSize;\n\t\t\t// Fill across a wrapped line: its size is known only now.\n\t\t\tif (it.fillCross && wraps) resize(it, it.main, crossSize);\n\t\t\tconst x = horizontal ? pos : crossPos + off;\n\t\t\tconst y = horizontal ? crossPos + off : pos;\n\t\t\tplace(s, it.id, finite(x), finite(y));\n\t\t\tpos += it.main + step;\n\t\t}\n\t\tcrossPos += lineCross + crossGap;\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Grid\n\ninterface TrackSpec {\n\tsizing: AutoLayoutSizing;\n\tvalue: number;\n}\n\nfunction parseTrack(track: AutoLayoutGridTrack | undefined): TrackSpec {\n\tif (typeof track === 'number') return { sizing: 'fixed', value: track };\n\tif (track === 'hug') return { sizing: 'hug', value: 0 };\n\tif (typeof track === 'string') {\n\t\tconst match = /^\\s*(-?\\d*\\.?\\d+)\\s*fr\\s*$/i.exec(track);\n\t\tif (match) return { sizing: 'fill', value: Number(match[1]) };\n\t}\n\tif (track && typeof track === 'object' && 'fr' in track) return { sizing: 'fill', value: track.fr };\n\treturn { sizing: 'fill', value: 1 };\n}\n\nfunction trackList(spec: number | AutoLayoutGridTrack[] | undefined, fallback: number): { count: number; tracks: TrackSpec[] } {\n\tif (Array.isArray(spec)) return { count: Math.min(Math.max(spec.length, 1), MAX_TRACKS), tracks: spec.map(parseTrack) };\n\tconst n = typeof spec === 'number' && Number.isFinite(spec) ? Math.floor(spec) : fallback;\n\treturn { count: Math.min(Math.max(n, 1), MAX_TRACKS), tracks: [] };\n}\n\nfunction trackSpec(tracks: TrackSpec[], i: number): TrackSpec {\n\tconst t = tracks[i] ?? { sizing: 'fill', value: 1 };\n\tconst value = Number.isFinite(t.value) ? Math.min(Math.max(t.value, 0), 1e6) : 0;\n\treturn { sizing: t.sizing, value };\n}\n\n/** A grid child's cell (0-based column / row and spans). */\nexport interface AutoLayoutGridCell {\n\tid: string;\n\tcolumn: number;\n\trow: number;\n\tcolumnSpan: number;\n\trowSpan: number;\n}\n\n/** A grid frame's resolved geometry (frame-local): column x ranges, row y ranges, cells and the size it wants. */\nexport interface AutoLayoutGridGeometry {\n\tcolumns: [number, number][];\n\trows: [number, number][];\n\tcells: AutoLayoutGridCell[];\n\twidth: number;\n\theight: number;\n}\n\nfunction cellRect(g: AutoLayoutGridGeometry, c: AutoLayoutGridCell) {\n\tconst x0 = g.columns[c.column]?.[0] ?? 0;\n\tconst x1 = g.columns[c.column + c.columnSpan - 1]?.[1] ?? x0;\n\tconst y0 = g.rows[c.row]?.[0] ?? 0;\n\tconst y1 = g.rows[c.row + c.rowSpan - 1]?.[1] ?? y0;\n\treturn { x: x0, y: y0, width: Math.max(x1, x0) - x0, height: Math.max(y1, y0) - y0 };\n}\n\nconst toIndex = (v: unknown): number | undefined =>\n\ttypeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.floor(v) : undefined;\n\n/** Places grid children: anchored ones first, then the rest in reading order into the next free cells. */\nfunction gridPlace(s: LayoutState, al: AutoLayout, kids: string[]): { cells: AutoLayoutGridCell[]; rows: number } {\n\tconst cols = trackList(al.grid?.columns, 2).count;\n\tconst minRows = trackList(al.grid?.rows, 1).count;\n\tconst maxRows = Math.min(Math.max(Math.floor(MAX_GRID_CELLS / cols), 1), MAX_TRACKS);\n\tconst occ: boolean[][] = [];\n\tconst free = (c: number, r: number, cs: number, rs: number) => {\n\t\tfor (let ri = r; ri < r + rs; ri++) {\n\t\t\tconst row = occ[ri];\n\t\t\tif (!row) continue;\n\t\t\tfor (let ci = c; ci < c + cs; ci++) if (row[ci]) return false;\n\t\t}\n\t\treturn true;\n\t};\n\tconst mark = (c: number, r: number, cs: number, rs: number) => {\n\t\twhile (occ.length < r + rs) occ.push(new Array(cols).fill(false));\n\t\tfor (let ri = r; ri < r + rs; ri++) for (let ci = c; ci < c + cs; ci++) occ[ri][ci] = true;\n\t};\n\tconst spans = (id: string) => {\n\t\tconst area = getChildLayout(s.boxes.get(id)!.node).gridArea ?? {};\n\t\tconst cs = Math.min(Math.max(toIndex(area.columnSpan) ?? 1, 1), cols);\n\t\tconst rs = Math.min(Math.max(toIndex(area.rowSpan) ?? 1, 1), maxRows);\n\t\tconst col = toIndex(area.column);\n\t\tconst row = toIndex(area.row);\n\t\treturn {\n\t\t\tcol: col === undefined ? undefined : Math.min(col, cols - cs),\n\t\t\trow: row === undefined ? undefined : Math.min(row, maxRows - rs),\n\t\t\tcs,\n\t\t\trs\n\t\t};\n\t};\n\tconst placed: (AutoLayoutGridCell | undefined)[] = new Array(kids.length).fill(undefined);\n\tkids.forEach((id, i) => {\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tif (col !== undefined && row !== undefined) {\n\t\t\tmark(col, row, cs, rs);\n\t\t\tplaced[i] = { id, column: col, row, columnSpan: cs, rowSpan: rs };\n\t\t}\n\t});\n\tlet cursor: [number, number] = [0, 0]; // [row, column]\n\tkids.forEach((id, i) => {\n\t\tif (placed[i]) return;\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tconst lastRow = maxRows - rs;\n\t\tlet c = 0;\n\t\tlet r = 0;\n\t\tif (col !== undefined) {\n\t\t\tr = lastRow;\n\t\t\tfor (let ri = 0; ri <= lastRow; ri++) if (free(col, ri, cs, rs)) { r = ri; break; }\n\t\t\tc = col;\n\t\t} else if (row !== undefined) {\n\t\t\tr = row;\n\t\t\tfor (let ci = 0; ci <= cols - cs; ci++) if (free(ci, row, cs, rs)) { c = ci; break; }\n\t\t} else {\n\t\t\tlet [rr, cc] = cursor;\n\t\t\tlet found: [number, number] | undefined;\n\t\t\twhile (rr <= lastRow) {\n\t\t\t\tif (cc + cs <= cols && free(cc, rr, cs, rs)) {\n\t\t\t\t\tfound = [cc, rr];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcc += 1;\n\t\t\t\tif (cc + cs > cols) {\n\t\t\t\t\tcc = 0;\n\t\t\t\t\trr += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t[c, r] = found ?? [0, lastRow];\n\t\t\tcursor = [r, c + cs];\n\t\t}\n\t\tmark(c, r, cs, rs);\n\t\tplaced[i] = { id, column: c, row: r, columnSpan: cs, rowSpan: rs };\n\t});\n\tconst cells = placed.filter((c): c is AutoLayoutGridCell => !!c);\n\tconst rows = Math.max(cells.reduce((m, c) => Math.max(m, c.row + c.rowSpan), 0), minRows);\n\treturn { cells, rows: Math.min(rows, MAX_TRACKS) };\n}\n\n/**\n * Resolves track sizes: fixed tracks keep their size, hug tracks fit their content, fill tracks\n * share what is left by their `fr` like CSS (a track never gets less than its content).\n * `avail` is undefined when the frame hugs that axis.\n */\nfunction resolveTracks(tracks: TrackSpec[], content: number[], avail: number | undefined): number[] {\n\tconst out = content.map((c, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : c;\n\t});\n\tif (avail === undefined) return out;\n\tconst fr = (i: number) => trackSpec(tracks, i).value;\n\tlet flex = out.map((_, i) => i).filter((i) => trackSpec(tracks, i).sizing === 'fill');\n\tlet used = out.reduce((sum, v, i) => (trackSpec(tracks, i).sizing === 'fill' ? sum : sum + v), 0);\n\twhile (flex.length > 0) {\n\t\tconst total = flex.reduce((sum, i) => sum + fr(i), 0);\n\t\tconst unit = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;\n\t\tconst open = flex.filter((i) => unit * fr(i) >= out[i]);\n\t\tconst frozen = flex.filter((i) => unit * fr(i) < out[i]);\n\t\tif (frozen.length === 0) {\n\t\t\tfor (const i of open) out[i] = unit * fr(i);\n\t\t\tbreak;\n\t\t}\n\t\tused += frozen.reduce((sum, i) => sum + out[i], 0);\n\t\tflex = open;\n\t}\n\treturn out;\n}\n\n/** Content size per track: the largest single-span child; spanning children grow the content-sized tracks they span. */\nfunction trackContent(\n\tcells: AutoLayoutGridCell[],\n\tsizes: number[],\n\ttracks: TrackSpec[],\n\tn: number,\n\tgap: number,\n\tcolumns: boolean,\n\thug: boolean\n): number[] {\n\tconst byContent = (i: number) => trackSpec(tracks, i).sizing !== 'fixed';\n\tconst content = Array.from({ length: n }, (_, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : 0;\n\t});\n\tconst span = (c: AutoLayoutGridCell): [number, number] => (columns ? [c.column, c.columnSpan] : [c.row, c.rowSpan]);\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len === 1 && byContent(start) && start < n) content[start] = Math.max(content[start], sizes[k]);\n\t});\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len < 2) return;\n\t\tconst range = Array.from({ length: len }, (_, j) => start + j).filter((i) => i < n);\n\t\tif (!hug && range.some((i) => trackSpec(tracks, i).sizing === 'fill')) return;\n\t\tconst have = range.reduce((sum, i) => sum + content[i], 0) + gap * (len - 1);\n\t\tconst grow = range.filter(byContent);\n\t\tif (sizes[k] > have && grow.length > 0) {\n\t\t\tconst extra = (sizes[k] - have) / grow.length;\n\t\t\tfor (const i of grow) content[i] += extra;\n\t\t}\n\t});\n\treturn content;\n}\n\nfunction trackRanges(start: number, sizes: number[], gap: number): [number, number][] {\n\tlet x = start;\n\treturn sizes.map((size) => {\n\t\tconst r: [number, number] = [x, x + size];\n\t\tx += size + gap;\n\t\treturn r;\n\t});\n}\n\nfunction gridGeometryOf(s: LayoutState, frame: string): AutoLayoutGridGeometry | undefined {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al || al.mode !== 'grid') return undefined;\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst kids = flowChildren(s, frame);\n\tconst { cells, rows } = gridPlace(s, al, kids);\n\tconst colSpec = trackList(al.grid?.columns, 2);\n\tconst rowSpec = trackList(al.grid?.rows, 1);\n\tconst cols = colSpec.count;\n\tconst gx = Math.max(finite(al.gap), 0);\n\tconst gy = Math.max(al.rowGap !== undefined ? finite(al.rowGap) : gx, 0);\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ws: number[] = [];\n\tconst hs: number[] = [];\n\tfor (const c of cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tws.push(getLayoutSizing(b.node, 'width') === 'fill' && !hugW ? lim.minW ?? 0 : b.w);\n\t\ths.push(getLayoutSizing(b.node, 'height') === 'fill' && !hugH ? lim.minH ?? 0 : b.h);\n\t}\n\tconst colContent = trackContent(cells, ws, colSpec.tracks, cols, gx, true, hugW);\n\tconst rowContent = trackContent(cells, hs, rowSpec.tracks, rows, gy, false, hugH);\n\tconst gapsW = gx * Math.max(cols - 1, 0);\n\tconst gapsH = gy * Math.max(rows - 1, 0);\n\tconst colW = resolveTracks(colSpec.tracks, colContent, hugW ? undefined : finite(f.w) - pl - pr - gapsW);\n\tconst rowH = resolveTracks(rowSpec.tracks, rowContent, hugH ? undefined : finite(f.h) - pt - pb - gapsH);\n\tconst lim = limitsOf(f.node);\n\tconst width = hugW ? colW.reduce((a, b) => a + b, 0) + gapsW + pl + pr : finite(f.w);\n\tconst height = hugH ? rowH.reduce((a, b) => a + b, 0) + gapsH + pt + pb : finite(f.h);\n\treturn {\n\t\tcolumns: trackRanges(pl, colW, gx),\n\t\trows: trackRanges(pt, rowH, gy),\n\t\tcells,\n\t\twidth: Math.max(clampLayoutSize(width, lim.minW, lim.maxW), pl + pr),\n\t\theight: Math.max(clampLayoutSize(height, lim.minH, lim.maxH), pt + pb)\n\t};\n}\n\nfunction layoutGrid(s: LayoutState, frame: string, al: AutoLayout) {\n\tconst f = s.boxes.get(frame)!;\n\tconst fw = f.w;\n\tconst fh = f.h;\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst lim = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tsetSize(\n\t\ts,\n\t\tframe,\n\t\thugW ? fw : Math.max(clampLayoutSize(fw, lim.minW, lim.maxW), pl + pr),\n\t\thugH ? fh : Math.max(clampLayoutSize(fh, lim.minH, lim.maxH), pt + pb)\n\t);\n\tlet g = gridGeometryOf(s, frame);\n\tif (!g) return;\n\t// Fill-width children take their column width first, so wrapping text sizes its row.\n\tlet widened = false;\n\tfor (const c of g.cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tif (getLayoutSizing(b.node, 'width') === 'fill' && !hugW) {\n\t\t\tconst l = limitsOf(b.node);\n\t\t\tresizeChild(s, c.id, clampLayoutSize(cellRect(g, c).width, l.minW, l.maxW), b.h);\n\t\t\twidened = true;\n\t\t}\n\t}\n\tif (widened) g = gridGeometryOf(s, frame) ?? g;\n\tsetSize(s, frame, g.width, g.height);\n\tconstrain(s, frame, fw, fh);\n\tconst frameX: AutoLayoutCellAlign = al.alignMain === 'center' ? 'center' : al.alignMain === 'end' ? 'end' : 'start';\n\tconst frameY: AutoLayoutCellAlign = al.alignCross === 'center' ? 'center' : al.alignCross === 'end' ? 'end' : 'start';\n\tconst offset = (a: AutoLayoutCellAlign, free: number) => (a === 'center' ? free / 2 : a === 'end' ? free : 0);\n\tfor (const c of g.cells) {\n\t\tconst cell = cellRect(g, c);\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst l = limitsOf(b.node);\n\t\tconst area = getChildLayout(b.node).gridArea ?? {};\n\t\tconst fillW = getLayoutSizing(b.node, 'width') === 'fill' && !hugW;\n\t\tconst fillH = getLayoutSizing(b.node, 'height') === 'fill' && !hugH;\n\t\tif (fillW || fillH) {\n\t\t\tresizeChild(\n\t\t\t\ts,\n\t\t\t\tc.id,\n\t\t\t\tfillW ? clampLayoutSize(cell.width, l.minW, l.maxW) : b.w,\n\t\t\t\tfillH ? clampLayoutSize(cell.height, l.minH, l.maxH) : b.h\n\t\t\t);\n\t\t}\n\t\tconst ox = offset(area.alignX ?? frameX, cell.width - b.w);\n\t\tconst oy = offset(area.alignY ?? frameY, cell.height - b.h);\n\t\tplace(s, c.id, finite(cell.x + ox), finite(cell.y + oy));\n\t}\n}\n\n// ---------------------------------------------------------------------------------------------\n// Constraints\n\n/**\n * After `id` changed size from `ow × oh`, moves / resizes its children by their constraints\n * (recursively). In an auto layout frame only absolute children follow constraints.\n */\nfunction constrain(s: LayoutState, id: string, ow: number, oh: number) {\n\tconst f = s.boxes.get(id);\n\tif (!f) return;\n\tconst nw = f.w;\n\tconst nh = f.h;\n\tif (Math.abs(nw - ow) < 1e-9 && Math.abs(nh - oh) < 1e-9) return;\n\tconst absoluteOnly = !!getAutoLayout(f.node);\n\tconst sx = ow > 1e-9 ? nw / ow : 1;\n\tconst sy = oh > 1e-9 ? nh / oh : 1;\n\ts.guard(`constrain:${id}`, () => {\n\t\tfor (const k of f.kids) {\n\t\t\tconst c = s.boxes.get(k)!;\n\t\t\tconst child = getChildLayout(c.node);\n\t\t\tif (absoluteOnly && !child.absolute) continue;\n\t\t\tlet x0 = c.x;\n\t\t\tlet x1 = c.x + c.w;\n\t\t\tlet y0 = c.y;\n\t\t\tlet y1 = c.y + c.h;\n\t\t\tconst dx = nw - ow;\n\t\t\tconst dy = nh - oh;\n\t\t\tswitch (child.constraints?.horizontal ?? 'left') {\n\t\t\t\tcase 'right':\n\t\t\t\t\tx0 += dx;\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'leftRight':\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\tx0 += dx / 2;\n\t\t\t\t\tx1 += dx / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\tx0 *= sx;\n\t\t\t\t\tx1 *= sx;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tswitch (child.constraints?.vertical ?? 'top') {\n\t\t\t\tcase 'bottom':\n\t\t\t\t\ty0 += dy;\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'topBottom':\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\ty0 += dy / 2;\n\t\t\t\t\ty1 += dy / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\ty0 *= sy;\n\t\t\t\t\ty1 *= sy;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst cw = c.w;\n\t\t\tconst ch = c.h;\n\t\t\tconst resized = Math.abs(x1 - x0 - cw) > 1e-9 || Math.abs(y1 - y0 - ch) > 1e-9;\n\t\t\tplace(s, k, finite(x0), finite(y0));\n\t\t\tif (resized) {\n\t\t\t\tsetSize(s, k, x1 - x0, y1 - y0);\n\t\t\t\tif (c.kids.length > 0) constrain(s, k, cw, ch);\n\t\t\t\tif (getAutoLayout(c.node)) layout(s, k);\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Public API\n\n/**\n * Lays out one auto layout frame's children and sizes the frame when it hugs (nested frames\n * that are resized by the layout are laid out again). Returns the position / size updates.\n */\nexport function layoutFrame(nodes: CanvasNode[], frameId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tlayout(s, frameId);\n\treturn s.updates();\n}\n\nfunction postOrder(s: LayoutState, nodes: CanvasNode[]): string[] {\n\tconst out: string[] = [];\n\tconst seen = new Set<string>();\n\tconst visit = (id: string) => {\n\t\tif (seen.has(id)) return;\n\t\tseen.add(id);\n\t\tfor (const k of s.boxes.get(id)?.kids ?? []) visit(k);\n\t\tout.push(id);\n\t};\n\tconst roots = nodes.filter((n) => !n.parentId || !s.boxes.has(n.parentId));\n\tfor (const r of roots) visit(r.id);\n\tfor (const n of nodes) visit(n.id); // nodes in parent cycles\n\treturn out;\n}\n\n/** Lays out every auto layout frame, children before parents, so nested hugging settles in one pass. */\nexport function layoutAll(nodes: CanvasNode[], options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tfor (const id of postOrder(s, nodes)) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Lays out `changedId` (when it is a frame) and then each of its auto layout ancestors,\n * innermost first. Use it after a node inside a frame was added, removed, resized or moved.\n */\nexport function layoutUp(nodes: CanvasNode[], changedId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, [changedId], options);\n}\n\n/** {@link layoutUp} for several changed nodes at once (each chain once, innermost frames first). */\nexport function layoutUpMany(nodes: CanvasNode[], changedIds: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconst chains = [...new Set(changedIds)].filter((id) => s.boxes.has(id)).map((id) => [id, ...s.ancestors(id)]);\n\t// Deeper frames first so a parent sees its children's final sizes.\n\tconst depth = new Map<string, number>();\n\tfor (const chain of chains) chain.forEach((id, i) => depth.set(id, Math.max(depth.get(id) ?? 0, chain.length - 1 - i)));\n\tconst order = [...depth.keys()].sort((a, b) => depth.get(b)! - depth.get(a)!);\n\tfor (const id of order) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Children of `parentId` follow their constraints after the parent was resized from\n * `previousSize` to its current size. In an auto layout frame only absolute children move\n * (the layout places the others). Returns the updates.\n */\nexport function applyConstraints(\n\tnodes: CanvasNode[],\n\tparentId: string,\n\tpreviousSize: { width: number; height: number },\n\toptions: AutoLayoutOptions = {}\n): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconstrain(s, parentId, previousSize.width, previousSize.height);\n\treturn s.updates();\n}\n\n/** The resolved grid of a grid frame (frame-local), from its children's current sizes. */\nexport function getAutoLayoutGrid(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\toptions: AutoLayoutOptions = {}\n): AutoLayoutGridGeometry | undefined {\n\treturn gridGeometryOf(new LayoutState(nodes, options), frameId);\n}\n\n/** Ids of the children a frame places (visible, not absolute), in order. */\nexport function getFlowChildren(nodes: CanvasNode[], frameId: string): string[] {\n\treturn nodes\n\t\t.filter((n) => n.parentId === frameId && n.id !== frameId && n.hidden !== true && !getChildLayout(n).absolute)\n\t\t.map((n) => n.id);\n}\n\n/** The nearest auto layout frame that places `id` (its parent, when `id` is a flow child). */\nexport function getLayoutParent(nodes: CanvasNode[], id: string): CanvasNode | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node?.parentId || getChildLayout(node).absolute) return undefined;\n\tconst parent = lookup.get(node.parentId);\n\treturn isAutoLayoutFrame(parent) ? parent : undefined;\n}\n\n/**\n * Makes a node's sizing coherent with where it sits, and that of its children: `fill` needs an\n * auto layout parent that does not hug that axis (and a flow child), `hug` needs an auto layout\n * frame or a text-like node (see `measure.textSize`); otherwise the axis becomes `fixed`.\n * Writes `data.childLayout.sizing`. Returns a new array (unchanged nodes keep their identity).\n */\nexport function makeSizingCoherent<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tid: string,\n\toptions: AutoLayoutOptions = {}\n): TNode[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targets = new Set([id, ...nodes.filter((n) => n.parentId === id).map((n) => n.id)]);\n\tif (!lookup.has(id)) return nodes;\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\tconst flowParent = parent && isAutoLayoutFrame(parent) && !getChildLayout(node).absolute ? parent : undefined;\n\t\tconst canHug = isAutoLayoutFrame(node) || options.measure?.textSize?.(node) !== undefined;\n\t\tconst coherent = (axis: Axis): AutoLayoutSizing => {\n\t\t\tconst sizing = getLayoutSizing(node, axis);\n\t\t\tif (sizing === 'fill' && (!flowParent || getLayoutSizing(flowParent, axis) === 'hug')) return 'fixed';\n\t\t\tif (sizing === 'hug' && !canHug) return 'fixed';\n\t\t\treturn sizing;\n\t\t};\n\t\tconst width = coherent('width');\n\t\tconst height = coherent('height');\n\t\tif (width === getLayoutSizing(node, 'width') && height === getLayoutSizing(node, 'height')) return node;\n\t\tchanged = true;\n\t\tconst child = getChildLayout(node);\n\t\treturn {\n\t\t\t...node,\n\t\t\tdata: { ...node.data, [CHILD_LAYOUT_DATA_KEY]: { ...child, sizing: { ...child.sizing, width, height } } }\n\t\t};\n\t});\n\treturn changed ? next : nodes;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Reordering\n\nexport interface AutoLayoutInsertTarget {\n\tframeId: string;\n\t/** Index among the frame's flow children, not counting the dragged node. */\n\tindex: number;\n\t/** Insertion marker (absolute flow coordinates) for `<CanvasGuidesLayer>`. */\n\tguide: GuideLine;\n}\n\n/**\n * Where a node dropped at `point` (absolute flow coordinates) goes among the flow children of\n * auto layout frame `frameId`: the reading-order index of the slot under the point. `excludeId`\n * (the dragged node) is left out of the siblings.\n */\nexport function getAutoLayoutInsertTarget(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\tpoint: XYPosition,\n\toptions: AutoLayoutOptions & { excludeId?: string } = {}\n): AutoLayoutInsertTarget | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst frame = lookup.get(frameId);\n\tconst al = getAutoLayout(frame);\n\tif (!frame || !al) return undefined;\n\tconst s = new LayoutState(nodes, options);\n\tconst origin = getAbsolutePosition(frame, lookup);\n\tconst px = point.x - origin.x;\n\tconst py = point.y - origin.y;\n\tconst siblings = flowChildren(s, frameId)\n\t\t.filter((id) => id !== options.excludeId)\n\t\t.map((id) => s.boxes.get(id)!);\n\t// Grid and wrapped layouts read in rows (vertical wrap: columns); plain stacks along their axis.\n\tconst horizontal = al.mode !== 'vertical';\n\tconst main = (b: { x: number; y: number }) => (horizontal ? b.x : b.y);\n\tconst mainSize = (b: Box) => (horizontal ? b.w : b.h);\n\tconst cross = (b: { x: number; y: number }) => (horizontal ? b.y : b.x);\n\tconst crossSize = (b: Box) => (horizontal ? b.h : b.w);\n\tconst p = { x: px, y: py };\n\tconst multiLine = al.mode === 'grid' || !!al.wrap;\n\tlet index = 0;\n\tfor (const b of siblings) {\n\t\tconst before = multiLine\n\t\t\t? cross(b) + crossSize(b) <= cross(p) ||\n\t\t\t\t(cross(b) <= cross(p) && main(b) + mainSize(b) / 2 < main(p))\n\t\t\t: main(b) + mainSize(b) / 2 < main(p);\n\t\tif (before) index += 1;\n\t}\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst prev = siblings[index - 1];\n\tconst next = siblings[index];\n\tconst f = s.boxes.get(frameId)!;\n\tlet guide: GuideLine;\n\tif (horizontal) {\n\t\tconst x = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.x + prev.w + next.x) / 2\n\t\t\t: prev\n\t\t\t\t? prev.x + prev.w\n\t\t\t\t: next\n\t\t\t\t\t? next.x\n\t\t\t\t\t: pl;\n\t\tconst ref = next ?? prev;\n\t\tconst y1 = ref ? ref.y : pt;\n\t\tconst y2 = ref ? ref.y + ref.h : f.h - pb;\n\t\tguide = { orientation: 'vertical', kind: 'edge', x1: origin.x + x, y1: origin.y + y1, x2: origin.x + x, y2: origin.y + y2 };\n\t} else {\n\t\tconst y = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.y + prev.h + next.y) / 2\n\t\t\t: prev\n\t\t\t\t? prev.y + prev.h\n\t\t\t\t: next\n\t\t\t\t\t? next.y\n\t\t\t\t\t: pt;\n\t\tconst ref = next ?? prev;\n\t\tconst x1 = ref ? ref.x : pl;\n\t\tconst x2 = ref ? ref.x + ref.w : f.w - pr;\n\t\tguide = { orientation: 'horizontal', kind: 'edge', x1: origin.x + x1, y1: origin.y + y, x2: origin.x + x2, y2: origin.y + y };\n\t}\n\treturn { frameId, index, guide };\n}\n\n/**\n * Moves child `childId` of an auto layout frame to flow index `index` (among its flow siblings)\n * by reordering the node array; parents still precede their children. Returns a new array\n * (the same array when nothing moves).\n */\nexport function moveLayoutChild<TNode extends CanvasNode>(nodes: TNode[], childId: string, index: number): TNode[] {\n\tconst child = nodes.find((n) => n.id === childId);\n\tif (!child?.parentId) return nodes;\n\tconst siblings = nodes.filter((n) => n.parentId === child.parentId && n.id !== child.parentId);\n\tconst flow = siblings.filter((n) => n.hidden !== true && !getChildLayout(n).absolute && n.id !== childId);\n\tconst clamped = Math.min(Math.max(Math.floor(Number.isFinite(index) ? index : 0), 0), flow.length);\n\t// Insert before the flow sibling now at `clamped` (or after the last one).\n\tconst order = siblings.filter((n) => n.id !== childId);\n\tconst anchor = flow[clamped];\n\tconst at = anchor ? order.indexOf(anchor) : flow.length > 0 ? order.indexOf(flow[flow.length - 1]) + 1 : order.length;\n\torder.splice(at, 0, child);\n\tif (order.every((n, i) => n === siblings[i])) return nodes;\n\t// Put the siblings back into the slots they occupied, in their new order.\n\tlet k = 0;\n\tconst parentId = child.parentId;\n\tconst placed = nodes.map((n) => (n.parentId === parentId && n.id !== parentId ? order[k++] : n));\n\treturn parentsFirst(placed);\n}\n\n/** Stable reorder so every node comes after its parent (xyflow requires it). */\nfunction parentsFirst<TNode extends CanvasNode>(nodes: TNode[]): TNode[] {\n\tconst ids = new Set(nodes.map((n) => n.id));\n\tconst pending = new Map<string, TNode[]>();\n\tconst done = new Set<string>();\n\tconst out: TNode[] = [];\n\tconst emit = (n: TNode) => {\n\t\tout.push(n);\n\t\tdone.add(n.id);\n\t\tconst waiting = pending.get(n.id);\n\t\tif (waiting) {\n\t\t\tpending.delete(n.id);\n\t\t\tfor (const w of waiting) emit(w);\n\t\t}\n\t};\n\tfor (const n of nodes) {\n\t\tif (n.parentId && ids.has(n.parentId) && !done.has(n.parentId) && n.parentId !== n.id) {\n\t\t\tif (!pending.has(n.parentId)) pending.set(n.parentId, []);\n\t\t\tpending.get(n.parentId)!.push(n);\n\t\t} else emit(n);\n\t}\n\tfor (const waiting of pending.values()) for (const n of waiting) if (!done.has(n.id)) emit(n); // cycles\n\treturn out;\n}\n","import type { Command } from '../history/types.js';\nimport {\n\talignNodes,\n\tapplyNodeUpdates,\n\tdistributeNodes,\n\tinvertNodeUpdates,\n\tisLocked,\n\tlockNodes,\n\tmatchSize,\n\treorderNodes,\n\tunlockNodes,\n\ttype AlignEdge,\n\ttype AlignNodesOptions,\n\ttype CanvasNodeUpdate,\n\ttype DistributeAxis,\n\ttype DistributeMode,\n\ttype DistributeNodesOptions,\n\ttype MatchSizeOptions,\n\ttype NodeRectOptions,\n\ttype SizeDimension,\n\ttype ZOrderOperation\n} from './arrange.js';\nimport type { CanvasArrangeAction } from './arrangeShortcuts.js';\nimport {\n\tapplyShapeCombination,\n\tcanCombineShapeNodes,\n\tcombineShapeNodes,\n\trevertShapeCombination,\n\ttype CombineShapeNodesOptions,\n\ttype CombineShapeNodesResult,\n\ttype ShapeOperation\n} from './shapeNodes.js';\nimport {\n\tcreateNodeGuideIndex,\n\tsnapNodeDrag,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions,\n\ttype SnapNodeDragResult\n} from './guides.js';\nimport {\n\tgetResizingNodeRect,\n\tinferResizeHandle,\n\tsnapNodeResize,\n\ttype ResizeHandle,\n\ttype SnapNodeResizeResult\n} from './resizeSnap.js';\nimport { createNodeLookup } from './arrange.js';\nimport { layoutUpMany, type AutoLayoutOptions } from './autoLayout.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/** Minimal history surface (`HistoryManager` satisfies it). */\nexport interface ArrangeHistoryTarget {\n\texecute(command: Command): void;\n}\n\nexport interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** Ids acted on when a method is called without ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** When set, every arrange action is recorded as one undoable command. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Current zoom, used to convert the guide threshold from screen pixels (default 1). */\n\tgetZoom?: () => number;\n\t/** Smart-guide options (threshold in screen px, edges / centres / spacing toggles). */\n\tguides?: Omit<SnapGuidesOptions, 'zoom'>;\n\t/** Turns drag snapping off while keeping the arrange actions (default `true`). */\n\tsnapping?: boolean;\n\t/** Turns resize snapping off (default: follows `snapping`). */\n\tresizeSnapping?: boolean;\n\t/** Resize options passed to `snapResize` (`keepAspectRatio`, `minWidth`, `minHeight`). */\n\tresize?: { keepAspectRatio?: boolean; minWidth?: number; minHeight?: number };\n\t/** Size resolution for unmeasured nodes. */\n\trectOptions?: NodeRectOptions;\n\t/** Called whenever the guide segments change (set while dragging, cleared on drop). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n\t/**\n\t * Re-run auto layout (`data.layout` frames) around the nodes an arrange action moves or\n\t * resizes, in the same undoable step. `true` uses the default measuring.\n\t */\n\tautoLayout?: boolean | AutoLayoutOptions;\n}\n\n/**\n * A React Flow / SvelteFlow style node change; only `position` changes are inspected.\n * `id` is optional because a batch can contain changes without one (React Flow's\n * `add` change carries an `item` instead), so `NodeChange[]` must be accepted as is.\n */\nexport interface PositionChangeLike {\n\ttype: string;\n\tid?: string;\n\tposition?: XYPosition;\n\tdragging?: boolean;\n\t[key: string]: any;\n}\n\n/**\n * Framework-agnostic arrange controller shared by the Svelte rune and the React hook:\n * align / distribute / match size / lock / z-order actions on the selection (optionally\n * undoable through a `HistoryManager`) and smart-guide snapping during drags.\n */\nexport function createCanvasArrangeController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasArrangeControllerOptions<TNode>\n) {\n\tlet guides: GuideLine[] = [];\n\tlet dragIndex: GuideIndex | null = null;\n\tlet dragKey = '';\n\t// Snapped positions from the last dragging batch, re-applied to the drag-stop change.\n\tlet lastSnapped = new Map<string, XYPosition>();\n\t// Resize snapping: the node, its box when the resize started, cached targets and the\n\t// last snapped box (absolute) re-applied when the flow writes the raw end size.\n\tlet resizeId: string | null = null;\n\tlet resizeStart: Rect | null = null;\n\tlet resizeIndex: GuideIndex | null = null;\n\tlet lastResized: SnapNodeResizeResult | null = null;\n\n\tconst selectedIds = (ids?: Iterable<string>) =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/** Applies updates as one (optionally undoable) step. Returns the updates. */\n\tfunction apply(name: string, updates: CanvasNodeUpdate[]): CanvasNodeUpdate[] {\n\t\tif (updates.length === 0) return updates;\n\t\tif (options.autoLayout) {\n\t\t\tconst layoutOptions = options.autoLayout === true ? { rectOptions: options.rectOptions } : options.autoLayout;\n\t\t\tconst moved = applyNodeUpdates(options.getNodes(), updates);\n\t\t\tconst layout = layoutUpMany(moved, updates.map((u) => u.id), layoutOptions);\n\t\t\tif (layout.length > 0) {\n\t\t\t\tconst byId = new Map<string, CanvasNodeUpdate>();\n\t\t\t\tfor (const u of [...updates, ...layout]) byId.set(u.id, { ...byId.get(u.id), ...u });\n\t\t\t\tupdates = [...byId.values()];\n\t\t\t}\n\t\t}\n\t\tconst inverse = invertNodeUpdates(options.getNodes(), updates);\n\t\tconst run = (u: CanvasNodeUpdate[]) => options.setNodes(applyNodeUpdates(options.getNodes(), u));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });\n\t\t} else {\n\t\t\trun(updates);\n\t\t}\n\t\treturn updates;\n\t}\n\n\tfunction setLocked(name: string, ids: string[], locked: boolean): string[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst set = new Set(ids);\n\t\tconst changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);\n\t\tif (changed.length === 0) return changed;\n\t\tconst lock = () => options.setNodes(lockNodes(options.getNodes(), changed));\n\t\tconst unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });\n\t\t} else if (locked) lock();\n\t\telse unlock();\n\t\treturn changed;\n\t}\n\n\tconst rect = () => options.rectOptions ?? {};\n\n\tconst controller = {\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\t\tapply,\n\t\talign(edge: AlignEdge, ids?: Iterable<string>, alignOptions: AlignNodesOptions = {}) {\n\t\t\treturn apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));\n\t\t},\n\t\tdistribute(\n\t\t\taxis: DistributeAxis,\n\t\t\tmode: DistributeMode = 'gaps',\n\t\t\tids?: Iterable<string>,\n\t\t\tdistributeOptions: DistributeNodesOptions = {}\n\t\t) {\n\t\t\treturn apply(\n\t\t\t\t`Distribute ${axis}`,\n\t\t\t\tdistributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })\n\t\t\t);\n\t\t},\n\t\tmatchSize(dimension: SizeDimension, ids?: Iterable<string>, sizeOptions: MatchSizeOptions = {}) {\n\t\t\treturn apply(\n\t\t\t\t'Make Same Size',\n\t\t\t\tmatchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })\n\t\t\t);\n\t\t},\n\t\treorder(operation: ZOrderOperation, ids?: Iterable<string>) {\n\t\t\treturn apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));\n\t\t},\n\t\tbringForward: (ids?: Iterable<string>) => controller.reorder('bringForward', ids),\n\t\tsendBackward: (ids?: Iterable<string>) => controller.reorder('sendBackward', ids),\n\t\tbringToFront: (ids?: Iterable<string>) => controller.reorder('bringToFront', ids),\n\t\tsendToBack: (ids?: Iterable<string>) => controller.reorder('sendToBack', ids),\n\t\tlock: (ids?: Iterable<string>) => setLocked('Lock', selectedIds(ids), true),\n\t\tunlock: (ids?: Iterable<string>) => setLocked('Unlock', selectedIds(ids), false),\n\t\ttoggleLock(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst set = new Set(list);\n\t\t\tconst anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));\n\t\t\treturn anyUnlocked ? controller.lock(list) : controller.unlock(list);\n\t\t},\n\t\tisLocked: (id: string) => isLocked(options.getNodes().find((n) => n.id === id)),\n\n\t\t/**\n\t\t * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the\n\t\t * shape nodes among `ids` (default: the selection). Replaces them with the result as\n\t\t * one undoable step. Refuses without changing anything when a selected shape is\n\t\t * locked, fewer than two shapes are selected, or the result is empty.\n\t\t */\n\t\tcombineShapes(\n\t\t\toperation: ShapeOperation,\n\t\t\tids?: Iterable<string>,\n\t\t\tcombineOptions: CombineShapeNodesOptions = {}\n\t\t): CombineShapeNodesResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = combineShapeNodes(before, selectedIds(ids), operation, { ...rect(), ...combineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst name = `${operation[0].toUpperCase()}${operation.slice(1)} Shapes`;\n\t\t\tconst run = () => options.setNodes(applyShapeCombination(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name, execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\tunite: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('unite', ids, o),\n\t\tintersect: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('intersect', ids, o),\n\t\tsubtract: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('subtract', ids, o),\n\t\texclude: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('exclude', ids, o),\n\t\tdivide: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('divide', ids, o),\n\t\t/** Whether the shape operations can run on `ids` (default: the selection). */\n\t\tcanCombineShapes: (ids?: Iterable<string>) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Runs a shortcut action. Scene actions are not handled here (see the scenes store);\n\t\t * returns `false` for them and for unknown actions.\n\t\t */\n\t\trunAction(action: CanvasArrangeAction): boolean {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'alignLeft': controller.align('left'); return true;\n\t\t\t\tcase 'alignCenter': controller.align('center'); return true;\n\t\t\t\tcase 'alignRight': controller.align('right'); return true;\n\t\t\t\tcase 'alignTop': controller.align('top'); return true;\n\t\t\t\tcase 'alignMiddle': controller.align('middle'); return true;\n\t\t\t\tcase 'alignBottom': controller.align('bottom'); return true;\n\t\t\t\tcase 'distributeHorizontal': controller.distribute('horizontal'); return true;\n\t\t\t\tcase 'distributeVertical': controller.distribute('vertical'); return true;\n\t\t\t\tcase 'lock': controller.lock(); return true;\n\t\t\t\tcase 'unlock': controller.unlock(); return true;\n\t\t\t\tcase 'bringForward': controller.bringForward(); return true;\n\t\t\t\tcase 'sendBackward': controller.sendBackward(); return true;\n\t\t\t\tcase 'bringToFront': controller.bringToFront(); return true;\n\t\t\t\tcase 'sendToBack': controller.sendToBack(); return true;\n\t\t\t\tdefault: return false;\n\t\t\t}\n\t\t},\n\n\t\t/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */\n\t\tbeginDrag(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tdragKey = list.slice().sort().join('\\u0000');\n\t\t\tdragIndex = createNodeGuideIndex(options.getNodes(), list, rect());\n\t\t},\n\n\t\t/**\n\t\t * Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other\n\t\t * nodes, writes the snapped positions back through `setNodes` (not recorded in\n\t\t * history) and updates `guides`.\n\t\t */\n\t\tdrag(ids?: Iterable<string>): SnapNodeDragResult | null {\n\t\t\tif (options.snapping === false) return null;\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst key = list.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(list);\n\t\t\tconst result = snapNodeDrag(options.getNodes(), list, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tlastSnapped = new Map();\n\t\t\tif (result.updates.length > 0) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(options.getNodes(), result.updates));\n\t\t\t\tfor (const u of result.updates) if (u.position) lastSnapped.set(u.id, u.position);\n\t\t\t}\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so\n\t\t * the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.\n\t\t * A batch without a dragging position change ends the drag and clears the guides.\n\t\t */\n\t\tsnapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dragging = changes.filter(\n\t\t\t\t(c): c is TChange & { id: string; position: XYPosition } =>\n\t\t\t\t\tc.type === 'position' && typeof c.id === 'string' && !!c.position && !!c.dragging\n\t\t\t);\n\t\t\tif (dragging.length === 0) {\n\t\t\t\tif (!changes.some((c) => c.type === 'position' && c.dragging === false)) return changes;\n\t\t\t\t// React Flow's drag-stop change carries the raw pointer position, which would\n\t\t\t\t// undo the snap of the last frame; keep the snapped position instead.\n\t\t\t\tconst snapped = lastSnapped;\n\t\t\t\tlastSnapped = new Map(); // applied to the changes below, not by endDrag\n\t\t\t\tcontroller.endDrag();\n\t\t\t\tconst keep = (c: TChange) =>\n\t\t\t\t\tc.type === 'position' && c.dragging === false && !!c.position && c.id !== undefined && snapped.has(c.id);\n\t\t\t\tif (!changes.some(keep)) return changes;\n\t\t\t\treturn changes.map((c) => (keep(c) ? { ...c, position: snapped.get(c.id!)! } : c));\n\t\t\t}\n\t\t\tif (options.snapping === false) return changes;\n\t\t\tconst ids = dragging.map((c) => c.id);\n\t\t\tconst key = ids.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(ids);\n\t\t\tconst proposed = new Map(dragging.map((c) => [c.id, c.position]));\n\t\t\tconst nodes = options.getNodes().map((n) => (proposed.has(n.id) ? { ...n, position: proposed.get(n.id)! } : n));\n\t\t\tconst result = snapNodeDrag(nodes, ids, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst { x: dx, y: dy } = result.offset;\n\t\t\tlastSnapped = new Map();\n\t\t\tif (dx === 0 && dy === 0) return changes;\n\t\t\t// Children of a dragged parent move with it; only shift top-level dragged nodes.\n\t\t\tconst parents = new Map(nodes.map((n) => [n.id, n.parentId]));\n\t\t\tconst shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? '')));\n\t\t\treturn changes.map((c) => {\n\t\t\t\tif (!(c.type === 'position' && c.position && c.dragging && c.id !== undefined && shift.has(c.id))) return c;\n\t\t\t\tconst position = { x: c.position.x + dx, y: c.position.y + dy };\n\t\t\t\tlastSnapped.set(c.id, position);\n\t\t\t\treturn { ...c, position };\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Starts resize snapping for node `id`: records its box (to tell which handle is\n\t\t * dragged) and caches the snap targets. Optional: `resize` starts lazily.\n\t\t */\n\t\tbeginResize(id: string) {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tresizeId = id;\n\t\t\tresizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;\n\t\t\tresizeIndex = createNodeGuideIndex(nodes, [id], rect());\n\t\t\tlastResized = null;\n\t\t},\n\n\t\t/**\n\t\t * Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the\n\t\t * new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and\n\t\t * writes the snapped box back through `setNodes` (not recorded in history). `handle`\n\t\t * defaults to the one inferred from the edges that moved since `beginResize`. A locked\n\t\t * node is restored to its starting box.\n\t\t */\n\t\tresize(id: string, handle?: ResizeHandle): SnapNodeResizeResult | null {\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tif (!node || !resizeStart) return null;\n\t\t\tconst proposed = getResizingNodeRect(node, lookup, rect());\n\t\t\tif (isLocked(node)) {\n\t\t\t\tconst start = resizeStart;\n\t\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\t\tif (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {\n\t\t\t\t\toptions.setNodes(\n\t\t\t\t\t\tapplyNodeUpdates(nodes, [\n\t\t\t\t\t\t\t{ id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif ((options.resizeSnapping ?? options.snapping) === false) return null;\n\t\t\tconst h = handle ?? inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!h) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, h, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tlastResized = result.updates.length > 0 ? result : null;\n\t\t\tif (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * Ends resize snapping: clears guides and re-applies the last snapped box if the flow\n\t\t * wrote the raw pointer size afterwards.\n\t\t */\n\t\tendResize() {\n\t\t\tconst last = lastResized;\n\t\t\tresizeId = null;\n\t\t\tresizeStart = null;\n\t\t\tresizeIndex = null;\n\t\t\tlastResized = null;\n\t\t\tsetGuides([]);\n\t\t\tconst update = last?.updates[0];\n\t\t\tif (!update) return;\n\t\t\tconst node = options.getNodes().find((n) => n.id === update.id);\n\t\t\tif (!node) return;\n\t\t\tconst drift =\n\t\t\t\tnode.position.x !== update.position!.x ||\n\t\t\t\tnode.position.y !== update.position!.y ||\n\t\t\t\tnode.width !== update.width ||\n\t\t\t\tnode.height !== update.height;\n\t\t\tif (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: snaps the `dimensions` (and matching `position`) changes a\n\t\t * `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end\n\t\t * change (`resizing: false`) carries the raw pointer size, so the last snapped size is\n\t\t * kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.\n\t\t */\n\t\tsnapResizeChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dims = changes.find(\n\t\t\t\t(c) => c.type === 'dimensions' && typeof c.id === 'string' && !!c.dimensions && typeof c.resizing === 'boolean'\n\t\t\t);\n\t\t\tif (!dims) return changes;\n\t\t\tconst id = dims.id as string;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tconst isResizeChange = (c: TChange) => c.id === id && (c.type === 'dimensions' || c.type === 'position');\n\t\t\tif (node && isLocked(node)) {\n\t\t\t\tif (dims.resizing === false) controller.endResize();\n\t\t\t\treturn changes.filter((c) => !isResizeChange(c));\n\t\t\t}\n\t\t\tif (dims.resizing === false) {\n\t\t\t\tconst last = lastResized?.updates[0];\n\t\t\t\tlastResized = null; // applied to the change below, not by endResize\n\t\t\t\tcontroller.endResize();\n\t\t\t\tif (!last || last.id !== id) return changes;\n\t\t\t\treturn changes.map((c) =>\n\t\t\t\t\tc === dims ? { ...c, dimensions: { width: last.width!, height: last.height! } } : c\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!node || (options.resizeSnapping ?? options.snapping) === false) return changes;\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tif (!resizeStart) return changes;\n\t\t\tconst posChange = changes.find((c) => c.id === id && c.type === 'position' && !!c.position);\n\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\tconst position = posChange?.position ?? node.position;\n\t\t\tconst proposed: Rect = {\n\t\t\t\tx: position.x + origin.x,\n\t\t\t\ty: position.y + origin.y,\n\t\t\t\twidth: dims.dimensions.width,\n\t\t\t\theight: dims.dimensions.height\n\t\t\t};\n\t\t\tconst handle = inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!handle) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn changes;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, handle, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst update = result.updates[0];\n\t\t\tlastResized = update ? result : null;\n\t\t\tif (!update) return changes;\n\t\t\tconst next = changes.map((c) => {\n\t\t\t\tif (c === dims) return { ...c, dimensions: { width: update.width!, height: update.height! } };\n\t\t\t\tif (c === posChange) return { ...c, position: update.position! };\n\t\t\t\treturn c;\n\t\t\t});\n\t\t\tif (!posChange && (update.position!.x !== node.position.x || update.position!.y !== node.position.y)) {\n\t\t\t\tnext.push({ id, type: 'position', position: update.position! } as unknown as TChange);\n\t\t\t}\n\t\t\treturn next;\n\t\t},\n\n\t\t/**\n\t\t * Clears guides and the cached snap targets. Call it from the flow's drag-stop\n\t\t * handler: flow libraries write the raw pointer position when a drag ends, so the\n\t\t * last snapped positions from `drag` are written back here.\n\t\t */\n\t\tendDrag() {\n\t\t\tconst snapped = lastSnapped;\n\t\t\tdragIndex = null;\n\t\t\tdragKey = '';\n\t\t\tlastSnapped = new Map();\n\t\t\tsetGuides([]);\n\t\t\tif (snapped.size === 0) return;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst drift = nodes.some((n) => {\n\t\t\t\tconst p = snapped.get(n.id);\n\t\t\t\treturn !!p && (n.position.x !== p.x || n.position.y !== p.y);\n\t\t\t});\n\t\t\tif (drift) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(nodes, [...snapped].map(([id, position]) => ({ id, position }))));\n\t\t\t}\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasArrangeController<TNode>\n>;\n","/**\n * Distance measuring between bounding boxes (Alt-hover red lines with value labels).\n *\n * Portions ported from figcraft-main/crates/engine/src/distance.rs (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** Lines shorter than this (flow units) are not drawn. */\nconst EPS = 1e-6;\n\n/**\n * A measuring line in flow coordinates. `measure` lines carry a `label` (the distance);\n * `extension` lines are dashed, unlabelled lines that carry a gap over to the measured box\n * when the gap line passes beside it.\n */\nexport interface MeasureGuide extends GuideLine {\n\tkind: 'measure' | 'extension';\n\t/** The distance as shown on the label (`measure` lines only). */\n\tlabel?: string;\n}\n\n/** A distance as shown on a label: absolute value, up to two decimals, no trailing zeros. */\nexport function measureLabel(distance: number): string {\n\tconst v = Math.round(Math.abs(distance) * 100) / 100;\n\treturn Object.is(v, -0) ? '0' : String(v);\n}\n\ninterface Box {\n\tx0: number;\n\ty0: number;\n\tx1: number;\n\ty1: number;\n}\n\nconst toBox = (r: Rect): Box => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });\nconst finiteBox = (b: Box) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);\n\nfunction line(kind: 'measure' | 'extension', x1: number, y1: number, x2: number, y2: number): MeasureGuide {\n\treturn { orientation: x1 === x2 ? 'vertical' : 'horizontal', kind, x1, y1, x2, y2 };\n}\n\n/** A labelled distance line, unless it has no length. */\nfunction measured(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tconst d = Math.hypot(x2 - x1, y2 - y1);\n\tif (d > EPS && Number.isFinite(d)) out.push({ ...line('measure', x1, y1, x2, y2), label: measureLabel(d) });\n}\n\n/** A dashed extension line (no label), unless it has no length. */\nfunction extension(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tif (Math.hypot(x2 - x1, y2 - y1) > EPS) out.push(line('extension', x1, y1, x2, y2));\n}\n\nfunction contains(outer: Box, inner: Box): boolean {\n\treturn outer.x0 <= inner.x0 + EPS && outer.y0 <= inner.y0 + EPS && outer.x1 >= inner.x1 - EPS && outer.y1 >= inner.y1 - EPS;\n}\n\n/** The middle of the overlap of two spans, or `null` when they do not overlap. */\nfunction overlapMid(a0: number, a1: number, b0: number, b1: number): number | null {\n\tconst lo = Math.max(a0, b0);\n\tconst hi = Math.min(a1, b1);\n\treturn lo <= hi ? (lo + hi) / 2 : null;\n}\n\n/** From `inner` to each edge of `outer`, along `inner`'s centre lines. */\nfunction inside(out: MeasureGuide[], inner: Box, outer: Box) {\n\tconst cx = (inner.x0 + inner.x1) / 2;\n\tconst cy = (inner.y0 + inner.y1) / 2;\n\tmeasured(out, outer.x0, cy, inner.x0, cy);\n\tmeasured(out, inner.x1, cy, outer.x1, cy);\n\tmeasured(out, cx, outer.y0, cx, inner.y0);\n\tmeasured(out, cx, inner.y1, cx, outer.y1);\n}\n\n/**\n * Distance lines from box `a` (the selection) to box `b` (the hovered shape):\n *\n * - **One inside the other**: from the inner box to each edge of the outer one, along the\n * inner box's centre lines (the same lines either way round).\n * - **Overlapping**: between the matching edges (left to left, right to right, ...).\n * - **Apart**: the horizontal and/or vertical gap, across the shared rows / columns, or from\n * the middle of `a` with a dashed `extension` line to `b` when the gap line passes beside it.\n * Touching boxes have no gap to draw.\n */\nexport function measureBetween(a: Rect, b: Rect): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tconst B = toBox(b);\n\tif (!finiteBox(A) || !finiteBox(B)) return out;\n\tif (contains(B, A)) {\n\t\tinside(out, A, B);\n\t\treturn out;\n\t}\n\tif (contains(A, B)) {\n\t\tinside(out, B, A);\n\t\treturn out;\n\t}\n\tconst ox = overlapMid(A.x0, A.x1, B.x0, B.x1);\n\tconst oy = overlapMid(A.y0, A.y1, B.y0, B.y1);\n\tconst overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;\n\tif (overlapping && ox !== null && oy !== null) {\n\t\tmeasured(out, A.x0, oy, B.x0, oy);\n\t\tmeasured(out, A.x1, oy, B.x1, oy);\n\t\tmeasured(out, ox, A.y0, ox, B.y0);\n\t\tmeasured(out, ox, A.y1, ox, B.y1);\n\t\treturn out;\n\t}\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;\n\tif (gapX) {\n\t\tconst [from, to] = gapX;\n\t\tconst y = oy ?? cy;\n\t\tmeasured(out, from, y, to, y);\n\t\tif (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);\n\t}\n\tconst gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;\n\tif (gapY) {\n\t\tconst [from, to] = gapY;\n\t\tconst x = ox ?? cx;\n\t\tmeasured(out, x, from, x, to);\n\t\tif (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);\n\t}\n\treturn out;\n}\n\n/**\n * Distance lines from box `a` to an infinite guide line (`vertical` at x = `at`, otherwise\n * horizontal at y = `at`): to the nearer side, or to both sides when the line crosses the box.\n */\nexport function measureToLine(a: Rect, orientation: 'vertical' | 'horizontal', at: number): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tif (!finiteBox(A) || !Number.isFinite(at)) return out;\n\tconst vertical = orientation === 'vertical';\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];\n\tconst seg = (from: number, to: number) =>\n\t\tvertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);\n\tif (at >= hi) seg(hi, at);\n\telse if (at <= lo) seg(at, lo);\n\telse {\n\t\tseg(lo, at);\n\t\tseg(at, hi);\n\t}\n\treturn out;\n}\n\nexport interface MeasureSelectionResult {\n\t/** Measuring lines in flow coordinates. */\n\tguides: MeasureGuide[];\n\t/** The box measured to (to outline), or `null` when nothing is measured. */\n\ttarget: Rect | null;\n\t/** The selection's bounding box, or `null` without a selection. */\n\tselection: Rect | null;\n}\n\nexport interface MeasureSelectionOptions extends NodeRectOptions {}\n\n/**\n * Alt-hover measuring on canvas nodes: from the bounding box of `selectedIds` (absolute,\n * union of several nodes) to the hovered node. Hovering the selection itself, or nothing\n * (`hoveredId` null), measures to the selection's parent (`parentId`) when it has one;\n * a top-level selection then measures nothing. Hidden nodes are ignored.\n */\nexport function measureSelection(\n\tnodes: CanvasNode[],\n\tselectedIds: Iterable<string>,\n\thoveredId: string | null | undefined,\n\toptions: MeasureSelectionOptions = {}\n): MeasureSelectionResult {\n\tconst none = (selection: Rect | null = null): MeasureSelectionResult => ({ guides: [], target: null, selection });\n\tconst lookup = createNodeLookup(nodes);\n\tconst selected = [...new Set(selectedIds)]\n\t\t.map((id) => lookup.get(id))\n\t\t.filter((n): n is CanvasNode => !!n && n.hidden !== true);\n\tif (selected.length === 0) return none();\n\tconst selection = boundsOf(selected.map((n) => getNodeRect(n, lookup, options)));\n\tif (!selection) return none();\n\tconst selectedSet = new Set(selected.map((n) => n.id));\n\n\tlet targetId = hoveredId ?? null;\n\tif (targetId === null || selectedSet.has(targetId)) {\n\t\tconst parentId = selected[0].parentId;\n\t\tconst sameParent = selected.every((n) => n.parentId === parentId);\n\t\ttargetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;\n\t}\n\tconst target = targetId !== null ? lookup.get(targetId) : undefined;\n\tif (!target || target.hidden === true) return none(selection);\n\tconst rect = getNodeRect(target, lookup, options);\n\treturn { guides: measureBetween(selection, rect), target: rect, selection };\n}\n\n/**\n * Whether the measuring modifier (Alt / Option) is held after a keyboard or pointer event.\n * `keyup` of Alt releases it; `keydown` of Alt or any event with `altKey` holds it.\n */\nexport function isMeasureModifierHeld(\n\tevent: { type?: string; key?: string; altKey?: boolean } | null | undefined\n): boolean {\n\tif (!event) return false;\n\tif (event.type === 'keyup' && event.key === 'Alt') return false;\n\treturn event.altKey === true || (event.type === 'keydown' && event.key === 'Alt');\n}\n","import { applyNodeUpdates, createNodeLookup, getNodeRect, getNodeSize, invertNodeUpdates, type CanvasNodeUpdate } from './arrange.js';\nimport type { ArrangeHistoryTarget, PositionChangeLike } from './arrangeController.js';\nimport {\n\tapplyConstraints,\n\tgetAutoLayoutInsertTarget,\n\tgetLayoutParent,\n\tisAutoLayoutFrame,\n\tlayoutAll,\n\tlayoutUpMany,\n\tmoveLayoutChild,\n\ttype AutoLayoutInsertTarget,\n\ttype AutoLayoutOptions\n} from './autoLayout.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\nexport interface CanvasAutoLayoutControllerOptions<TNode extends CanvasNode = CanvasNode> extends AutoLayoutOptions {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, controller actions (add / remove / resize / reorder / apply) are one undoable step each. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Turns automatic relayout off (explicit `applyAutoLayout` still works). Default `true`. */\n\tenabled?: boolean;\n\t/** Reorder flow children by dragging them inside their frame (default `true`). */\n\tdragReorder?: boolean;\n\t/** Called when the drop marker of a reorder drag changes (empty when none). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n}\n\n/**\n * Merges the updates of several layout passes (later ones win per field).\n */\nexport function mergeNodeUpdates(...lists: CanvasNodeUpdate[][]): CanvasNodeUpdate[] {\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const list of lists) for (const u of list) byId.set(u.id, { ...byId.get(u.id), ...u });\n\treturn [...byId.values()];\n}\n\n/**\n * Updates that re-run auto layout around `ids` after a change: each id's own layout (if a\n * frame) and its auto layout ancestors, plus the frames that lost removed children\n * (`parentIds`). Pure helper used by the controllers.\n */\nexport function relayoutUpdates(nodes: CanvasNode[], ids: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, ids, options);\n}\n\n/**\n * Framework-agnostic auto layout controller shared by the React hook and the Svelte rune.\n * Applies auto layout to frames (`data.layout`) after nodes inside them are added, removed,\n * resized or reordered, either through its own (undoable) actions or by watching the flow's\n * node changes (`noteChanges` + `flush`), and reorders flow children dragged within a frame.\n */\nexport function createCanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasAutoLayoutControllerOptions<TNode>\n) {\n\tconst layoutOptions = (): AutoLayoutOptions => ({ measure: options.measure, rectOptions: options.rectOptions });\n\tconst enabled = () => options.enabled !== false;\n\n\t// Changes noted from the flow, applied by `flush`.\n\tconst pendingIds = new Set<string>();\n\tconst pendingResizes = new Map<string, { width: number; height: number }>();\n\tconst resizeStarts = new Map<string, { width: number; height: number }>();\n\tlet pendingAll = false;\n\t// Reorder drags: start position of each dragged flow child, and drops to commit.\n\tconst dragStarts = new Map<string, XYPosition>();\n\tconst pendingDrops = new Set<string>();\n\tlet guides: GuideLine[] = [];\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/**\n\t * Runs a structural change plus the relayout it causes as one (optionally undoable) step.\n\t * `forward` / `backward` change the array; `ids` are relayout around after `forward`.\n\t */\n\tfunction run(\n\t\tname: string,\n\t\tforward: (nodes: TNode[]) => TNode[],\n\t\tbackward: (nodes: TNode[]) => TNode[],\n\t\tids: (after: TNode[]) => Iterable<string>,\n\t\textra: CanvasNodeUpdate[] = [],\n\t\trestore?: { id: string; position: XYPosition }\n\t) {\n\t\tconst before = options.getNodes();\n\t\tconst structural = forward(before);\n\t\tconst withExtra = applyNodeUpdates(structural, extra);\n\t\tconst layout = enabled() || name === 'Auto Layout' ? relayoutUpdates(withExtra, ids(withExtra), layoutOptions()) : [];\n\t\tconst updates = mergeNodeUpdates(extra, layout);\n\t\tif (structural === before && updates.length === 0) return updates;\n\t\tlet inverse = invertNodeUpdates(structural, updates);\n\t\tif (restore) {\n\t\t\t// Undo puts a dragged node back where the drag began (the flow wrote the drag outside history).\n\t\t\tinverse = mergeNodeUpdates(inverse, [{ id: restore.id, position: { ...restore.position } }]);\n\t\t}\n\t\tconst execute = () => options.setNodes(applyNodeUpdates(forward(options.getNodes()), updates));\n\t\tconst undo = () => options.setNodes(backward(applyNodeUpdates(options.getNodes(), inverse)));\n\t\tif (options.history) options.history.execute({ name, execute, undo });\n\t\telse execute();\n\t\treturn updates;\n\t}\n\n\tconst identity = (nodes: TNode[]) => nodes;\n\n\tconst controller = {\n\t\t/** Drop marker of the active reorder drag (flow coordinates); empty otherwise. */\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\n\t\t/**\n\t\t * Lays out frame `frameId` and its auto layout ancestors (default: every frame) as one\n\t\t * (optionally undoable) step. Returns the updates.\n\t\t */\n\t\tapplyAutoLayout(frameId?: string): CanvasNodeUpdate[] {\n\t\t\tif (frameId === undefined) {\n\t\t\t\tconst updates = layoutAll(options.getNodes(), layoutOptions());\n\t\t\t\treturn run('Auto Layout', identity, identity, () => [], updates);\n\t\t\t}\n\t\t\treturn run('Auto Layout', identity, identity, () => [frameId]);\n\t\t},\n\n\t\t/** Updates that relayout around `ids` on the current nodes, without applying them. */\n\t\tlayoutUpdates(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\treturn relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t},\n\n\t\t/**\n\t\t * Applies the relayout around `ids` (frames, or nodes inside frames) directly, not\n\t\t * recorded in history: auto layout is derived state, re-run after any change.\n\t\t */\n\t\trelayout(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst updates = relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn updates;\n\t\t},\n\n\t\t/**\n\t\t * Adds `node` (into the frame named by its `parentId`, at flow `index` when given) and\n\t\t * lays out its frame, as one step.\n\t\t */\n\t\taddNode(node: TNode, addOptions: { index?: number } = {}): CanvasNodeUpdate[] {\n\t\t\tconst forward = (nodes: TNode[]) => {\n\t\t\t\tif (nodes.some((n) => n.id === node.id)) return nodes;\n\t\t\t\tlet next = [...nodes, node];\n\t\t\t\tif (addOptions.index !== undefined && node.parentId) next = moveLayoutChild(next, node.id, addOptions.index);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\tconst backward = (nodes: TNode[]) => nodes.filter((n) => n.id !== node.id);\n\t\t\treturn run('Add Node', forward, backward, () => [node.id]);\n\t\t},\n\n\t\t/** Removes nodes (and their descendants) and lays out the frames they leave, as one step. */\n\t\tremoveNodes(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst remove = new Set(ids);\n\t\t\t// Descendants go too (xyflow cannot render orphans).\n\t\t\tlet grew = true;\n\t\t\twhile (grew) {\n\t\t\t\tgrew = false;\n\t\t\t\tfor (const n of before) {\n\t\t\t\t\tif (n.parentId && remove.has(n.parentId) && !remove.has(n.id)) {\n\t\t\t\t\t\tremove.add(n.id);\n\t\t\t\t\t\tgrew = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst removed = before.map((n, i) => [i, n] as const).filter(([, n]) => remove.has(n.id));\n\t\t\tif (removed.length === 0) return [];\n\t\t\tconst parents = [...new Set(removed.map(([, n]) => n.parentId).filter((p): p is string => !!p && !remove.has(p)))];\n\t\t\tconst forward = (nodes: TNode[]) => nodes.filter((n) => !remove.has(n.id));\n\t\t\tconst backward = (nodes: TNode[]) => {\n\t\t\t\tconst next = [...nodes];\n\t\t\t\tfor (const [i, n] of removed) next.splice(Math.min(i, next.length), 0, n);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\treturn run('Delete', forward, backward, () => parents);\n\t\t},\n\n\t\t/**\n\t\t * Resizes node `id` (its children follow their constraints) and lays out around it,\n\t\t * as one step.\n\t\t */\n\t\tresizeNode(id: string, size: { width?: number; height?: number }): CanvasNodeUpdate[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst node = nodes.find((n) => n.id === id);\n\t\t\tif (!node) return [];\n\t\t\tconst old = getNodeSize(node, options.rectOptions);\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (size.width !== undefined) update.width = size.width;\n\t\t\tif (size.height !== undefined) update.height = size.height;\n\t\t\tconst resized = applyNodeUpdates(nodes, [update]);\n\t\t\tconst constrained = mergeNodeUpdates([update], applyConstraints(resized, id, old, layoutOptions()));\n\t\t\treturn run('Resize', identity, identity, () => [id], constrained);\n\t\t},\n\n\t\t/** Moves flow child `childId` to flow index `index` in its frame and lays it out, as one step. */\n\t\tmoveChild(childId: string, index: number): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, childId, index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\tif (forward(before) === before) return [];\n\t\t\treturn run('Reorder', forward, backward, () => [childId]);\n\t\t},\n\n\t\t/** Where flow child `id` would drop if released at `point` (default: its centre). */\n\t\tgetInsertTarget(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst frame = getLayoutParent(nodes, id);\n\t\t\tif (!frame) return undefined;\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst rect = getNodeRect(lookup.get(id)!, lookup, options.rectOptions);\n\t\t\tconst at = point ?? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };\n\t\t\treturn getAutoLayoutInsertTarget(nodes, frame.id, at, { ...layoutOptions(), excludeId: id });\n\t\t},\n\n\t\t/** Records the start position of a reorder drag. Optional: `dragReorder` records it lazily. */\n\t\tbeginReorderDrag(id: string) {\n\t\t\tconst node = options.getNodes().find((n) => n.id === id);\n\t\t\tif (node && getLayoutParent(options.getNodes(), id)) dragStarts.set(id, { ...node.position });\n\t\t},\n\n\t\t/**\n\t\t * While flow child `id` is dragged (as currently positioned in `getNodes()`): updates the\n\t\t * drop marker. Returns the insert target.\n\t\t */\n\t\tdragReorder(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tif (options.dragReorder === false) return undefined;\n\t\t\tif (!dragStarts.has(id)) controller.beginReorderDrag(id);\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tsetGuides(target ? [target.guide] : []);\n\t\t\treturn target;\n\t\t},\n\n\t\t/**\n\t\t * Ends a reorder drag: moves `id` to the slot under `point` (default: its centre) and lays\n\t\t * the frame out, as one undoable step whose undo puts the node back where the drag began.\n\t\t */\n\t\tendReorderDrag(id: string, point?: XYPosition): CanvasNodeUpdate[] {\n\t\t\tsetGuides([]);\n\t\t\tconst start = dragStarts.get(id);\n\t\t\tdragStarts.delete(id);\n\t\t\tif (options.dragReorder === false || !getLayoutParent(options.getNodes(), id)) return [];\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tif (!target) return [];\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, id, target.index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\treturn run('Reorder', forward, backward, () => [id], [], start && { id, position: start });\n\t\t},\n\n\t\t/**\n\t\t * Notes a batch of React Flow / SvelteFlow style node changes (before or after they are\n\t\t * applied) so `flush` can relayout: dimension changes (measured sizes, resize ends),\n\t\t * added and removed nodes, and position changes of flow children (reorder drags).\n\t\t * Returns the changes unchanged.\n\t\t */\n\t\tnoteChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tfor (const c of changes) {\n\t\t\t\tconst id = c.id ?? c.item?.id;\n\t\t\t\tif (c.type === 'add' && c.item) {\n\t\t\t\t\tpendingIds.add(c.item.id);\n\t\t\t\t} else if (c.type === 'remove' && id) {\n\t\t\t\t\tconst parent = lookup.get(id)?.parentId;\n\t\t\t\t\tif (parent) pendingIds.add(parent);\n\t\t\t\t\telse pendingAll = true;\n\t\t\t\t} else if (c.type === 'replace' && id) {\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'dimensions' && id) {\n\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\tif (c.resizing === true) {\n\t\t\t\t\t\tif (node && !resizeStarts.has(id)) resizeStarts.set(id, getNodeSize(node, options.rectOptions));\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst start = resizeStarts.get(id);\n\t\t\t\t\tresizeStarts.delete(id);\n\t\t\t\t\tif (start) pendingResizes.set(id, start);\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'position' && id && options.dragReorder !== false) {\n\t\t\t\t\tif (!getLayoutParent(nodes, id)) continue;\n\t\t\t\t\tif (c.dragging === true) {\n\t\t\t\t\t\tif (!dragStarts.has(id)) {\n\t\t\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\t\t\tif (node) dragStarts.set(id, { ...node.position });\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c.dragging === false || dragStarts.has(id)) {\n\t\t\t\t\t\tpendingDrops.add(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changes;\n\t\t},\n\n\t\t/**\n\t\t * Notes nodes that were already removed (e.g. SvelteFlow's `ondelete`), so `flush`\n\t\t * relayouts the frames they left. The nodes must still carry their `parentId`.\n\t\t */\n\t\tnoteRemoved(removed: Iterable<{ id: string; parentId?: string }>) {\n\t\t\tconst gone = new Set<string>();\n\t\t\tconst list = [...removed];\n\t\t\tfor (const n of list) gone.add(n.id);\n\t\t\tfor (const n of list) if (n.parentId && !gone.has(n.parentId)) pendingIds.add(n.parentId);\n\t\t},\n\n\t\t/** Whether `noteChanges` left work for `flush`. */\n\t\tget hasPending() {\n\t\t\treturn pendingAll || pendingIds.size > 0 || pendingResizes.size > 0 || pendingDrops.size > 0 || dragStarts.size > 0;\n\t\t},\n\n\t\t/**\n\t\t * Applies the work noted by `noteChanges` to the current nodes: drop markers while\n\t\t * dragging, committed reorders on drop (undoable), and a relayout (not recorded) for\n\t\t * size, add and remove changes. Call it once the changes are applied.\n\t\t */\n\t\tflush(): CanvasNodeUpdate[] {\n\t\t\tlet applied: CanvasNodeUpdate[] = [];\n\t\t\tfor (const id of pendingDrops) applied = mergeNodeUpdates(applied, controller.endReorderDrag(id));\n\t\t\tpendingDrops.clear();\n\t\t\tfor (const id of dragStarts.keys()) controller.dragReorder(id);\n\t\t\tif (!enabled()) {\n\t\t\t\tpendingIds.clear();\n\t\t\t\tpendingResizes.clear();\n\t\t\t\tpendingAll = false;\n\t\t\t\treturn applied;\n\t\t\t}\n\t\t\tlet nodes = options.getNodes();\n\t\t\tlet updates: CanvasNodeUpdate[] = [];\n\t\t\tfor (const [id, previous] of pendingResizes) {\n\t\t\t\tif (!nodes.some((n) => n.id === id)) continue;\n\t\t\t\tconst constrained = applyConstraints(nodes, id, previous, layoutOptions());\n\t\t\t\tnodes = applyNodeUpdates(nodes, constrained);\n\t\t\t\tupdates = mergeNodeUpdates(updates, constrained);\n\t\t\t}\n\t\t\tconst layout = pendingAll\n\t\t\t\t? layoutAll(nodes, layoutOptions())\n\t\t\t\t: relayoutUpdates(nodes, [...pendingIds].filter((id) => nodes.some((n) => n.id === id)), layoutOptions());\n\t\t\tupdates = mergeNodeUpdates(updates, layout);\n\t\t\tpendingIds.clear();\n\t\t\tpendingResizes.clear();\n\t\t\tpendingAll = false;\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn mergeNodeUpdates(applied, updates);\n\t\t},\n\n\t\t/** Whether `id` is an auto layout frame. */\n\t\tisFrame: (id: string) => isAutoLayoutFrame(options.getNodes().find((n) => n.id === id))\n\t};\n\n\treturn controller;\n}\n\n/** Reorders `nodes` to follow `order` (ids); ids not in `order` keep their relative place at the end. */\nfunction restoreOrder<TNode extends CanvasNode>(nodes: TNode[], order: string[]): TNode[] {\n\tconst rank = new Map(order.map((id, i) => [id, i]));\n\tconst indexed = nodes.map((n, i) => ({ n, r: rank.get(n.id) ?? order.length + i }));\n\tindexed.sort((a, b) => a.r - b.r);\n\treturn indexed.every((e, i) => e.n === nodes[i]) ? nodes : indexed.map((e) => e.n);\n}\n\nexport type CanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasAutoLayoutController<TNode>\n>;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","../../src/canvas/clock.ts","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts","../../src/canvas/arrange.ts","../../src/canvas/guides.ts","../../src/canvas/connectors.ts","../../src/canvas/scenes.ts","../../src/canvas/polygonBoolean.ts","../../src/canvas/curveFit.ts","../../src/canvas/shapeGeometry.ts","../../src/canvas/pathops/bezier.ts","../../src/canvas/pathops/boolean.ts","../../src/canvas/pathops/stroke.ts","../../src/canvas/shapeNodes.ts","../../src/canvas/resizeSnap.ts","../../src/canvas/autoLayout.ts","../../src/canvas/arrangeController.ts","../../src/canvas/measure.ts","../../src/canvas/autoLayoutController.ts","../../src/canvas/tokenBindings.ts"],"names":["scale","next","best","inside","toIndex","px","TWO_PI","cross","dot","directed","corners","out","formatNumber","dist","unit","a","b","c","sub","m","addSplit","splitPass","insideFlags","traceRings","ringsToPath","predicate","left","right","defaultId","layout","add","lim","EPS"],"mappings":"AAAA;AACE;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACA;ACFO,IAAM,WAAA,EAAa,CAAA,EAAA,GACzB,OAAO,SAAA,IAAa,YAAA,GAAe,QAAA,CAAS,MAAA;AAGtC,IAAM,UAAA,EAAmB;AAAA,EAC/B,IAAA,EAAM,IAAA;AAAA,EACN,GAAA,EAAK,CAAA,EAAA,GAAO,OAAO,YAAA,IAAgB,YAAA,EAAc,WAAA,CAAY,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA;AAAA,EAC9E,KAAA,EAAO,CAAA,EAAA,GACN,IAAI,OAAA,CAAc,CAAC,OAAA,EAAA,GAAY;AAC9B,IAAA,GAAA,CAAI,OAAO,sBAAA,IAA0B,WAAA,GAAc,CAAC,UAAA,CAAW,CAAA,EAAG;AACjE,MAAA,qBAAA,CAAsB,CAAA,EAAA,GAAM,OAAA,CAAQ,CAAC,CAAA;AAAA,IACtC,EAAA,KAAO;AACN,MAAA,UAAA,CAAW,OAAA,EAAS,EAAE,CAAA;AAAA,IACvB;AAAA,EACD,CAAC,CAAA;AAAA,EACF,KAAA,EAAO,CAAC,EAAA,EAAA,GAAe,IAAI,OAAA,CAAc,CAAC,OAAA,EAAA,GAAY,UAAA,CAAW,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,EAAE,CAAC,CAAC;AAC3F,CAAA;AAEA,IAAM,OAAA,EAAS,CAAA,EAAA,GAAM,IAAI,OAAA,CAAc,CAAC,OAAA,EAAA,GAAY,UAAA,CAAW,OAAA,EAAS,CAAC,CAAC,CAAA;AAMnE,IAAM,YAAA,YAAN,MAAmC;AAAA,iBAChC,KAAA,EAAO,MAAA;AAAA,EACR;AAAA,kBACA,OAAA,EAAyB,CAAC,EAAA;AAAA,kBAC1B,SAAA,EAA+D,CAAC,EAAA;AAAA,kBAChE,IAAA,EAAM,EAAA;AAAA,EAEd,WAAA,CAAY,MAAA,EAAQ,CAAA,EAAG;AACtB,IAAA,IAAA,CAAK,KAAA,EAAO,KAAA;AAAA,EACb;AAAA,EAEO,GAAA,CAAA,EAAc;AACpB,IAAA,OAAO,IAAA,CAAK,IAAA;AAAA,EACb;AAAA,EAEO,KAAA,CAAA,EAAuB;AAC7B,IAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAA,GAAY,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,OAAO,CAAC,CAAA;AAAA,EAChE;AAAA,EAEO,KAAA,CAAM,EAAA,EAA2B;AACvC,IAAA,OAAO,IAAI,OAAA;AAAA,MAAc,CAAC,OAAA,EAAA,GACzB,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK;AAAA,QAClB,EAAA,EAAI,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,EAAE,CAAA;AAAA,QAC9B,GAAA,EAAK,IAAA,CAAK,GAAA,EAAA;AAAA,QACV;AAAA,MACD,CAAC;AAAA,IACF,CAAA;AAAA,EACD;AAAA;AAAA,EAGA,IAAW,OAAA,CAAA,EAAmB;AAC7B,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,EAAS,EAAA,GAAK,IAAA,CAAK,QAAA,CAAS,OAAA,EAAS,CAAA;AAAA,EACzD;AAAA;AAAA,EAGA,MAAa,OAAA,CAAQ,EAAA,EAA2B;AAC/C,IAAA,MAAM,IAAA,EAAM,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,EAAE,CAAA;AACtC,IAAA,MAAM,MAAA,CAAO,CAAA;AAEb,IAAA,MAAA,CAAO,IAAA,EAAM;AACZ,MAAA,IAAI,IAAA;AACJ,MAAA,IAAA,CAAA,MAAW,EAAA,GAAK,IAAA,CAAK,QAAA,EAAU;AAC9B,QAAA,GAAA,CAAI,CAAA,CAAE,GAAA,GAAM,IAAA,GAAA,CAAQ,CAAC,KAAA,GAAQ,CAAA,CAAE,GAAA,EAAK,IAAA,CAAK,GAAA,GAAO,CAAA,CAAE,GAAA,IAAO,IAAA,CAAK,GAAA,GAAM,CAAA,CAAE,IAAA,EAAM,IAAA,CAAK,GAAA,CAAA,EAAO;AACvF,UAAA,KAAA,EAAO,CAAA;AAAA,QACR;AAAA,MACD;AACA,MAAA,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA;AAEX,MAAA,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,IAAI,CAAA,EAAG,CAAC,CAAA;AACnD,MAAA,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,EAAE,CAAA;AACvC,MAAA,IAAA,CAAK,OAAA,CAAQ,CAAA;AACb,MAAA,MAAM,MAAA,CAAO,CAAA;AAAA,IACd;AAEA,IAAA,IAAA,CAAK,KAAA,EAAO,GAAA;AACZ,IAAA,MAAM,UAAA,EAAY,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAC1D,IAAA,IAAA,CAAA,MAAW,aAAA,GAAgB,SAAA,EAAW;AACrC,MAAA,YAAA,CAAa,CAAA;AAAA,IACd;AACA,IAAA,MAAM,MAAA,CAAO,CAAA;AAAA,EACd;AAAA;AAAA,EAGA,MAAa,IAAA,CAAK,IAAA,EAAM,EAAA,EAAmB;AAC1C,IAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,EAAO,GAAG,CAAC,CAAA;AAAA,EAC1C;AACD,UAAA;ADhBA;AACA;AEvFO,IAAM,cAAA,EAAgB,CAAC,CAAA,EAAA,GAAsB,EAAE,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA;AAK3D,IAAM,iBAAA,EAAuC,CACnD,MAAA,EACA,MAAA,EACA,QAAA,EAAA,GACqB;AACrB,EAAA,OAAO;AAAA,IACN,QAAA,EAAU;AAAA,MACT,CAAA,EAAG,MAAA,CAAO,EAAA,EAAA,CAAK,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,CAAA,EAAA,EAAK,QAAA;AAAA,MACtC,CAAA,EAAG,MAAA,CAAO,EAAA,EAAA,CAAK,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,CAAA,EAAA,EAAK;AAAA,IACvC,CAAA;AAAA,IACA,KAAA,EAAO,QAAA;AAAA,IACP,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAA,EAAW,GAAG;AAAA,EACpC,CAAA;AACD,CAAA;AAKO,IAAM,yBAAA,EAA+C,CAC3D,MAAA,EACA,MAAA,EACA,QAAA,EAAA,GACqB;AACrB,EAAA,OAAO;AAAA,IACN,QAAA,EAAU;AAAA,MACT,CAAA,EAAG,MAAA,CAAO,EAAA,EAAA,CAAK,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,CAAA,EAAA,EAAK,QAAA;AAAA,MACtC,CAAA,EAAG,MAAA,CAAO,EAAA,EAAA,CAAK,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,CAAA,EAAA,EAAK;AAAA,IACvC;AAAA,EACD,CAAA;AACD,CAAA;AAKO,SAAS,sBAAA,CAAuB,QAAA,EAAmC,CAAC,CAAA,EAAuB;AACjG,EAAA,MAAM;AAAA,IACL,UAAA,EAAY,GAAA;AAAA,IACZ,WAAA,EAAa,CAAA;AAAA,IACb,SAAA,EAAW,CAAA;AAAA,IACX,aAAA,EAAe,CAAA;AAAA,IACf,WAAA,EAAa;AAAA,EACd,EAAA,EAAI,OAAA;AAEJ,EAAA,OAAO,CACN,MAAA,EACA,MAAA,EACA,QAAA,EACA,KAAA,EACA,KAAA,EAAA,GACqB;AACrB,IAAA,MAAM,GAAA,EAAK,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,CAAA;AAC7B,IAAA,MAAM,GAAA,EAAK,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,CAAA;AAG7B,IAAA,MAAM,UAAA,EAAY,MAAA,EAAQ,EAAA,EAAA,CAAK,MAAA,EAAA,CAAS,MAAA,EAAQ,CAAA,EAAA,EAAK,CAAA,EAAA,EAAA,CAAA,CAAO,MAAA,EAAQ,CAAA,EAAA,EAAK,CAAA,EAAA,EAAK,CAAA;AAG9E,IAAA,MAAM,KAAA,EAAO,MAAA,CAAO,EAAA,EAAI,GAAA,EAAK,GAAA;AAC7B,IAAA,MAAM,KAAA,EAAO,MAAA,CAAO,EAAA,EAAI,GAAA,EAAK,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,GAAA,CAAI,EAAE,EAAA,EAAI,KAAA,EAAO,SAAA;AAGrE,IAAA,MAAM,EAAA,EAAI,QAAA;AACV,IAAA,MAAM,UAAA,EAAY,EAAA,EAAI,CAAA;AAEtB,IAAA,MAAM,EAAA,EAAI,UAAA,EAAY,UAAA,EAAY,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,UAAA,EAAY,EAAA,EAAI,KAAA,EAAO,EAAA,EAAI,EAAA,EAAI,MAAA,CAAO,CAAA;AACvF,IAAA,MAAM,EAAA,EAAI,UAAA,EAAY,UAAA,EAAY,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,UAAA,EAAY,EAAA,EAAI,KAAA,EAAO,EAAA,EAAI,EAAA,EAAI,MAAA,CAAO,CAAA;AAEvF,IAAA,MAAMA,OAAAA,EAAQ,WAAA,EAAA,CAAc,SAAA,EAAW,UAAA,EAAA,EAAc,QAAA;AACrD,IAAA,MAAM,QAAA,EAAU,aAAA,EAAA,CAAgB,WAAA,EAAa,YAAA,EAAA,EAAgB,QAAA;AAE7D,IAAA,OAAO;AAAA,MACN,QAAA,EAAU,EAAE,CAAA,EAAG,EAAE,CAAA;AAAA,MACjB,KAAA,EAAAA,MAAAA;AAAA,MACA;AAAA,IACD,CAAA;AAAA,EACD,CAAA;AACD;AAKO,SAAS,sBAAA,CACf,eAAA,EACqB;AACrB,EAAA,OAAO,CAAC,MAAA,EAAoB,MAAA,EAAoB,QAAA,EAAA,GAAsC;AACrF,IAAA,MAAM,GAAA,EAAK,eAAA,CAAgB,MAAA,EAAQ,MAAM,CAAA;AACzC,IAAA,MAAM,EAAA,EAAI,QAAA;AACV,IAAA,MAAM,UAAA,EAAY,EAAA,EAAI,CAAA;AAEtB,IAAA,MAAM,EAAA,EAAI,UAAA,EAAY,UAAA,EAAY,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,UAAA,EAAY,EAAA,EAAI,EAAA,CAAG,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,MAAA,CAAO,CAAA;AACvF,IAAA,MAAM,EAAA,EAAI,UAAA,EAAY,UAAA,EAAY,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,UAAA,EAAY,EAAA,EAAI,EAAA,CAAG,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,MAAA,CAAO,CAAA;AAEvF,IAAA,OAAO;AAAA,MACN,QAAA,EAAU,EAAE,CAAA,EAAG,EAAE,CAAA;AAAA,MACjB,KAAA,EAAO,QAAA;AAAA,MACP,OAAA,EAAS;AAAA,IACV,CAAA;AAAA,EACD,CAAA;AACD;AAYO,SAAS,gBAAA,CACf,KAAA,EACA,OAAA,EACA,YAAA,EAAmC,QAAA,EACjB;AAClB,EAAA,GAAA,CAAI,MAAA,IAAU,KAAA,GAAQ,OAAO,MAAA,IAAU,QAAA,EAAU;AAChD,IAAA,OAAO,EAAE,GAAG,KAAA,EAAO,QAAQ,CAAA;AAAA,EAC5B;AACA,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,SAAA,GAAY,KAAA,CAAM,IAAA,CAAK,EAAA,IAAM,EAAA,EAAI;AACrD,IAAA,MAAM,KAAA,EAAO,KAAA,CACX,OAAA,CAAQ,iCAAA,EAAmC,IAAI,CAAA,CAC/C,IAAA,CAAK,CAAA,CACL,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA,CACpB,IAAA,CAAK,CAAA;AACP,IAAA,OAAO,CAAA,EAAA;AACR,EAAA;AACO,EAAA;AACR;AA4BgB;AAIX,EAAA;AACK,oBAAA;AACD,IAAA;AAAO,IAAA;AACf,EAAA;AAEM,EAAA;AACL,IAAA;AACA,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEA,EAAA;AACE,EAAA;AAEA,EAAA;AACF,IAAA;AACI,IAAA;AACA,IAAA;AACP,IAAA;AACQ,IAAA;AACD,IAAA;AACP,IAAA;AACC,EAAA;AAGI,EAAA;AAEG,EAAA;AACJ,IAAA;AAEE,IAAA;AACF,IAAA;AAEJ,IAAA;AACO,MAAA;AACA,MAAA;AAEF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AAGM,MAAA;AACA,MAAA;AAEA,MAAA;AAEA,MAAA;AAEF,MAAA;AACH,QAAA;AACI,QAAA;AACH,UAAA;AACD,QAAA;AACI,QAAA;AACH,UAAA;AACC,UAAA;AACF,QAAA;AACC,QAAA;AACF,MAAA;AAEA,MAAA;AAEI,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AAEA,oBAAA;AAEK,IAAA;AACE,MAAA;AACA,IAAA;AACN,sBAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AAEC,EAAA;AACN,IAAA;AACD,EAAA;AACD;AFvBY;AACA;AGjPL;AAaS;AAKL,EAAA;AACF,IAAA;AACR,EAAA;AAEI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEJ,EAAA;AACO,IAAA;AACA,IAAA;AAEC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AAEO,EAAA;AACH,IAAA;AACA,IAAA;AACI,IAAA;AACC,IAAA;AACT,EAAA;AACD;AAMgB;AAOT,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEE,EAAA;AACL,IAAA;AACM,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEE,EAAA;AACF,EAAA;AACI,IAAA;AACR,EAAA;AAEM,EAAA;AAIA,EAAA;AACA,EAAA;AAGA,EAAA;AAMA,EAAA;AACA,EAAA;AACL,IAAA;AACA,IAAA;AACD,EAAA;AAGM,EAAA;AAEN,EAAA;AACK,IAAA;AAEE,IAAA;AAGE,IAAA;AACF,MAAA;AACA,QAAA;AACH,UAAA;AACC,YAAA;AACA,YAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACA,QAAA;AACH,UAAA;AACC,YAAA;AACA,YAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACA,QAAA;AACH,UAAA;AACC,YAAA;AACA,YAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACA,QAAA;AACH,UAAA;AACC,YAAA;AACA,YAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACA,QAAA;AAEA,QAAA;AACH,UAAA;AACC,YAAA;AACA,YAAA;AACA,UAAA;AACF,QAAA;AACC,UAAA;AACC,YAAA;AACA,YAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAGI,EAAA;AACH,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACD,IAAA;AACD,EAAA;AAGI,EAAA;AACH,IAAA;AACK,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAEO,EAAA;AACR;AAKgB;AAWT,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEE,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEE,EAAA;AACD,EAAA;AAEK,EAAA;AACR,EAAA;AACA,EAAA;AAEI,EAAA;AAEN,EAAA;AACO,IAAA;AACA,IAAA;AACJ,IAAA;AACF,EAAA;AAED,EAAA;AACK,IAAA;AACD,MAAA;AACH,IAAA;AACA,EAAA;AAEK,EAAA;AAEA,EAAA;AACD,EAAA;AAEC,EAAA;AACA,EAAA;AAEA,EAAA;AACF,IAAA;AACA,IAAA;AACJ,EAAA;AAEM,EAAA;AACA,EAAA;AAEA,EAAA;AAEN,EAAA;AACK,IAAA;AAEE,IAAA;AACD,IAAA;AAEC,IAAA;AACA,IAAA;AAEA,IAAA;AACA,IAAA;AAGL,IAAA;AAGA,MAAA;AACD,IAAA;AACA,EAAA;AAEM,EAAA;AACR;AHwJY;AACA;AIraI;AACT,EAAA;AACD,EAAA;AAEJ,EAAA;AAOO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAMgB;AACX,EAAA;AACE,EAAA;AACF,EAAA;AACE,EAAA;AAEL,EAAA;AAOF;AAKgB;AACX,EAAA;AACE,EAAA;AAEL,EAAA;AAIF;AAKgB;AACX,EAAA;AACG,IAAA;AACC,IAAA;AACA,EAAA;AACA,IAAA;AACR,EAAA;AACD;AAKgB;AAIT,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAKgB;AAIX,EAAA;AACG,IAAA;AAEL,IAAA;AAOM,MAAA;AAGN,MAAA;AACD,IAAA;AACO,EAAA;AAER,EAAA;AACO,EAAA;AACR;AAWgB;AAST,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AAEA,EAAA;AACN,EAAA;AACO,IAAA;AACP,EAAA;AAEM,EAAA;AACC,IAAA;AACF,MAAA;AACC,MAAA;AACJ,MAAA;AACI,QAAA;AACA,QAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AAEI,IAAA;AACJ,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AAEE,MAAA;AACN,IAAA;AAEO,IAAA;AACP,EAAA;AAEK,EAAA;AAGD,IAAA;AACC,IAAA;AACI,IAAA;AACA,IAAA;AACR,IAAA;AACC,EAAA;AAEM,EAAA;AACV;AAKgB;AAIL,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEC,EAAA;AACH,IAAA;AACH,IAAA;AACM,MAAA;AACA,MAAA;AACN,IAAA;AACC,EAAA;AACH;AJ4UY;AACA;AK5gBH;AACA,EAAA;AACD,EAAA;AACR;AAKgB;AAGL,EAAA;AAEN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEJ,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAEC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AAEI,EAAA;AACK,EAAA;AACV;AAKgB;AAIR,EAAA;AACR;AAEM;AACA;AAMU;AAQT,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAMgB;AAKN,EAAA;AAEH,EAAA;AAEI,EAAA;AACH,IAAA;AACD,IAAA;AAEE,IAAA;AACH,MAAA;AACH,MAAA;AACI,QAAA;AACA,QAAA;AACJ,MAAA;AACA,MAAA;AAIA,MAAA;AAID,IAAA;AACA,EAAA;AACF;AAUgB;AAOL,EAAA;AAEJ,EAAA;AACN,EAAA;AACC,IAAA;AACA,IAAA;AACD,EAAA;AAEM,EAAA;AAEC,EAAA;AACR;AL2cY;AACA;AM3jBC;AAIJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEA,kBAAA;AACA,kBAAA;AACA,kBAAA;AACA,kBAAA;AACA,kBAAA;AAER,EAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AAAA;AAAA;AAAA;AAAA;AAMO,EAAA;AAKF,IAAA;AAEE,MAAA;AACL,MAAA;AACD,IAAA;AAGK,IAAA;AACA,IAAA;AACN,EAAA;AAEQ,EAAA;AACF,IAAA;AACC,MAAA;AACC,MAAA;AACD,MAAA;AACD,MAAA;AAEH,QAAA;AACA,QAAA;AACD,MAAA;AACO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAMC,EAAA;AAIK,IAAA;AACA,IAAA;AACN,EAAA;AAAA;AAAA;AAAA;AAKO,EAAA;AACC,IAAA;AACR,EAAA;AAEQ,EAAA;AACH,IAAA;AACH,MAAA;AACK,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AAEQ,EAAA;AAKD,IAAA;AACF,IAAA;AAIE,IAAA;AACF,IAAA;AACH,MAAA;AACC,QAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACN,IAAA;AAIM,IAAA;AAEA,IAAA;AACA,IAAA;AAEN,IAAA;AACO,MAAA;AACN,MAAA;AAEM,MAAA;AACD,MAAA;AAEC,MAAA;AACF,MAAA;AACH,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACD,IAAA;AAEK,IAAA;AAED,IAAA;AACE,MAAA;AACA,sBAAA;AACL,MAAA;AACD,IAAA;AAEM,IAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACK,IAAA;AAED,IAAA;AACE,IAAA;AACL,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACD,QAAA;AACA,QAAA;AACA,wBAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,wBAAA;AACD,MAAA;AACA,IAAA;AAEI,IAAA;AACN,EAAA;AAEuB,EAAA;AACjB,IAAA;AACD,IAAA;AACE,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACD;ANkhBY;AACA;AOnrBC;AAEA;AAEJ;AACJ,EAAA;AACA,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMgB;AAIV,EAAA;AACD,EAAA;AACI,IAAA;AACR,EAAA;AACM,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACS,EAAA;AACV;AAOgB;AAIV,EAAA;AACD,EAAA;AACGC,IAAAA;AACF,IAAA;AACA,IAAA;AACGA,IAAAA;AACR,EAAA;AACI,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACG,IAAA;AACF,MAAA;AACC,MAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAMgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AAIN,IAAA;AACA,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAMgB;AAIP,EAAA;AACA,EAAA;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACA,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACL,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAGgB;AACR,EAAA;AACR;AAGgB;AAKP,EAAA;AACC,EAAA;AACV;AAKgB;AACR,EAAA;AACR;AAQgB;AACT,EAAA;AACC,EAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACG,IAAA;AACH,MAAA;AACH,MAAA;AACA,MAAA;AACM,MAAA;AACP,IAAA;AACA,EAAA;AACF;AAKgB;AACT,EAAA;AACC,EAAA;AACD,IAAA;AACC,IAAA;AACJ,MAAA;AACA,MAAA;AACE,MAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACA,IAAA;AACH,IAAA;AACA,IAAA;AACG,IAAA;AACP,EAAA;AACF;AAGgB;AACT,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAQS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AACS,EAAA;AACV;AAMS;AAIF,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACA,IAAA;AACG,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAegB;AAMP,EAAA;AACF,EAAA;AACI,EAAA;AACJ,EAAA;AAEA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACE,IAAA;AACF,MAAA;AACA,QAAA;AACJ,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACF,IAAA;AACQ,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAOS;AAOF,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACL,EAAA;AACM,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACD,IAAA;AACE,IAAA;AACE,IAAA;AACT,EAAA;AAEI,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACP,IAAA;AACD,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACJ,EAAA;AACK,IAAA;AACJ,IAAA;AACD,EAAA;AACD;AAQgB;AAOP,EAAA;AACF,EAAA;AACI,EAAA;AACJ,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAiBgB;AAMP,EAAA;AACE,EAAA;AACJ,EAAA;AAEA,EAAA;AACD,IAAA;AACG,MAAA;AACF,MAAA;AACL,IAAA;AACI,IAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AAEA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACH,MAAA;AACI,QAAA;AACA,QAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAegB;AAMT,EAAA;AACA,EAAA;AACE,IAAA;AACR,EAAA;AACI,EAAA;AAGE,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEM,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AAIA,IAAA;AACF,IAAA;AAEA,IAAA;AACH,MAAA;AACD,IAAA;AACC,MAAA;AACM,IAAA;AACN,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AAGA,MAAA;AACF,MAAA;AACJ,MAAA;AACC,QAAA;AACI,QAAA;AACJ,QAAA;AACI,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACC,UAAA;AACC,YAAA;AACA,YAAA;AACC,cAAA;AACA,cAAA;AACD,YAAA;AACD,UAAA;AACA,UAAA;AACD,QAAA;AACI,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AAEI,IAAA;AACC,IAAA;AACE,MAAA;AACF,MAAA;AACJ,IAAA;AACF,EAAA;AACO,EAAA;AACR;AAQgB;AAIX,EAAA;AACE,EAAA;AACN,EAAA;AACM,IAAA;AACN,EAAA;AACO,EAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACO,IAAA;AACP,EAAA;AACF;AAMgB;AAIT,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACG,MAAA;AACF,MAAA;AACA,MAAA;AACL,IAAA;AACQ,IAAA;AACT,EAAA;AACO,EAAA;AACR;APkfY;AACA;AQjiCI;AACT,EAAA;AACC,EAAA;AACR;AAuCS;AACA,EAAA;AACD,EAAA;AACE,IAAA;AACD,IAAA;AACR,EAAA;AACD;AAGS;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAgBgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACE,IAAA;AACT,EAAA;AACO,EAAA;AACN,IAAA;AACO,IAAA;AACP,IAAA;AACO,IAAA;AACP,IAAA;AACD,EAAA;AACD;AAGS;AACA,EAAA;AACJ,EAAA;AACK,EAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGS;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACJ,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAYS;AAMF,EAAA;AA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AACR;AAqBM;AAIO;AACA;AACA;AACC;AAEL;AACE,EAAA;AACH,EAAA;AACR;AAES;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACC,IAAA;AACA,IAAA;AACR,EAAA;AACD;AAGgB;AACR,EAAA;AACR;AAES;AACF,EAAA;AACN,EAAA;AACM,IAAA;AACG,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAAA;AACA,EAAA;AACD,EAAA;AACD,IAAA;AACA,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACA,MAAA;AACA,MAAA;AACN,IAAA;AACM,IAAA;AACA,IAAA;AACP,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACA,EAAA;AACF,IAAA;AACA,IAAA;AACP,EAAA;AACK,EAAA;AACC,IAAA;AACA,IAAA;AACN,EAAA;AAEM,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGS;AACA,EAAA;AACE,EAAA;AACD,EAAA;AACF,IAAA;AACE,IAAA;AACH,MAAA;AACE,MAAA;AACP,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAGS;AACE,EAAA;AACA,EAAA;AACJ,EAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACC,EAAA;AACR;AAOS;AACJ,EAAA;AACC,EAAA;AACC,EAAA;AACG,EAAA;AACR,IAAA;AACM,IAAA;AACC,IAAA;AACC,IAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AACM,IAAA;AACR,EAAA;AAC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AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAMS;AACF,EAAA;AACC,EAAA;AACR;AAES;AAaF,EAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACU,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACD;AAES;AAUF,EAAA;AACI,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACD;AAES;AACJ,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAGS;AAOD,EAAA;AACG,EAAA;AACF,EAAA;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACM,EAAA;AACA,EAAA;AACF,EAAA;AACI,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACU,EAAA;AACX;AAMgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACD,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACN,EAAA;AACS,IAAA;AACF,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AAEA,QAAA;AACF,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACA,EAAA;AACR;AAMS;AACF,EAAA;AACF,EAAA;AACH,IAAA;AACD,EAAA;AAEM,EAAA;AACL,IAAA;AACQ,IAAA;AACR,IAAA;AACQ,IAAA;AACR,IAAA;AACD,EAAA;AACI,EAAA;AACG,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACQ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAOgB;AAIP,EAAA;AACE,EAAA;AACJ,EAAA;AACI,EAAA;AACF,EAAA;AACJ,EAAA;AACI,EAAA;AACF,IAAA;AACE,MAAA;AACF,MAAA;AACC,QAAA;AACL,MAAA;AACC,QAAA;AAEC,QAAA;AAEF,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACE,MAAA;AAIF,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACA,MAAA;AACJ,MAAA;AACM,MAAA;AACF,MAAA;AACH,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAsBgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACO,IAAA;AACP,IAAA;AACM,IAAA;AACE,IAAA;AACR,IAAA;AACD,EAAA;AACQ,EAAA;AACE,EAAA;AACH,EAAA;AACR;AAES;AACJ,EAAA;AACG,EAAA;AACR;AAES;AACH,EAAA;AACE,EAAA;AACR;AAMgB;AAKP,EAAA;AACE,EAAA;AACN,EAAA;AACA,EAAA;AACM,EAAA;AACT,IAAA;AACM,IAAA;AACE,IAAA;AACD,MAAA;AACA,MAAA;AAEA,MAAA;AACN,MAAA;AAAgE,QAAA;AAEhE,MAAA;AACM,IAAA;AACN,MAAA;AACD,IAAA;AACM,EAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACV;AAOgBC;AACT,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAES;AACA,EAAA;AACR,EAAA;AACK,IAAA;AACC,IAAA;AACL,IAAA;AACK,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACM,EAAA;AACH,EAAA;AACR;AAES;AACJ,EAAA;AACE,EAAA;AACI,EAAA;AACL,EAAA;AACC,EAAA;AACC,EAAA;AACC,IAAA;AACH,IAAA;AACA,IAAA;AACJ,IAAA;AACOC,MAAAA;AACFA,MAAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AACE,MAAA;AACC,MAAA;AACP,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAMgB;AACN,EAAA;AAEC,EAAA;AACN,EAAA;AACK,EAAA;AACFA,IAAAA;AACFA,IAAAA;AACH,MAAA;AACM,MAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAyBgB;AACT,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AAEN,IAAA;AACK,MAAA;AACA,MAAA;AACE,MAAA;AACF,MAAA;AACH,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACD,QAAA;AACD,MAAA;AACM,MAAA;AACP,IAAA;AACD,EAAA;AACO,EAAA;AACR;AbqrEY;AACA;Acz7FQ;AACA;AACC;AACD;AACC;AACD;AACC;AACR;AAEGC;AACL,EAAA;AACH,EAAA;AACR;AAOgB;AACL,EAAA;AACA,EAAA;AACJ,EAAA;AACI,EAAA;AACHC,IAAAA;AACAC,IAAAA;AACAC,IAAAA;AACEF,IAAAA;AACT,EAAA;AACU,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACH,EAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACD;AAGgB;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACHA,IAAAA;AACAC,IAAAA;AACE,IAAA;AACT,EAAA;AACUE,EAAAA;AACAA,EAAAA;AACAA,EAAAA;AACJ,EAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACN,EAAA;AACD;AAGgB;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACO,EAAA;AACA,EAAA;AACT,EAAA;AACT;AAMgB;AACLJ,EAAAA;AACH,EAAA;AACG,EAAA;AACAA,EAAAA;AACH,EAAA;AACAA,EAAAA;AACR;AAGgB;AACP,EAAA;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACH,IAAA;AACD,IAAA;AACL,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGgB;AACL,EAAA;AACA,EAAA;AACHK,IAAAA;AACC,IAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACU,EAAA;AACHJ,IAAAA;AACAC,IAAAA;AACAG,IAAAA;AACC,IAAA;AACA,MAAA;AACFH,MAAAA;AACL,IAAA;AACD,EAAA;AACU,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACI,EAAA;AACH,EAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACD;AAGgB;AACN,EAAA;AACC,EAAA;AACQ,EAAA;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACR;AAGgB;AACP,EAAA;AACE,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACF,IAAA;AACC,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACE,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACG,IAAA;AACT,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACJ,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACX;AAGgB;AACT,EAAA;AACE,EAAA;AACD,IAAA;AACF,IAAA;AACG,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAEA,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACL,EAAA;AACA,EAAA;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACC,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAGgB;AACP,EAAA;AACF,EAAA;AACA,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAOgB;AACP,EAAA;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AAEO,EAAA;AACR;AAGa;AACZ,EAAA;AAAqB,EAAA;AAAqB,EAAA;AAAoB,EAAA;AAAqB,EAAA;AACnF,EAAA;AAAmB,EAAA;AAAoB,EAAA;AACxC;AACa;AACZ,EAAA;AAAoB,EAAA;AAAoB,EAAA;AAAoB,EAAA;AAAmB,EAAA;AAC/E,EAAA;AAAoB,EAAA;AAAoB,EAAA;AACzC;AAES;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGgB;AACN,EAAA;AACD,EAAA;AACF,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACF,EAAA;AACI,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACF,IAAA;AACF,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMgB;AACP,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACK,EAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AACR,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACS,IAAA;AACR,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACC,QAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACL,IAAA;AACM,IAAA;AACE,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAOgB;AAIR,EAAA;AACC,EAAA;AACJ,EAAA;AACK,EAAA;AACA,EAAA;AACL,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACM,EAAA;AACA,EAAA;AACF,EAAA;AACI,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACK,EAAA;AACA,EAAA;AACC,EAAA;AACJ,EAAA;AACK,EAAA;AACL,EAAA;AACC,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAMS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A;AACO,EAAA;AACR;AAESC;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACAD,QAAAA;AACAA,QAAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACI,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACD,IAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AACD,MAAA;AACE,MAAA;AACF,MAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACN,IAAA;AACK,MAAA;AACC,MAAA;AACA,MAAA;AACN,IAAA;AACI,IAAA;AACG,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAgCS;AACF,EAAA;AACApB,EAAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACK,EAAA;AACR,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,EAAA;AAEK,EAAA;AACF,EAAA;AACK,EAAA;AACF,IAAA;AACN,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACK,EAAA;AAGC,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AAIA,IAAA;AACI,IAAA;AACJ,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACA,QAAA;AACD,QAAA;AACJ,MAAA;AACM,MAAA;AACA,MAAA;AACP,IAAA;AACK,IAAA;AACN,EAAA;AACM,EAAA;AAIA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACP,IAAA;AACK,MAAA;AACE,MAAA;AACF,MAAA;AACC,MAAA;AAEA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AAA2B,QAAA;AAE1B,QAAA;AACD,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AAEK,QAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AAA2B,QAAA;AAE1B,QAAA;AACD,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACS,EAAA;AACV;AAGM;AACY,EAAA;AACA,EAAA;AACA,EAAA;AACjB,EAAA;AACK,IAAA;AACA,IAAA;AACJ,IAAA;AACO,MAAA;AACA,MAAA;AACP,IAAA;AACM,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACC,MAAA;AACA,MAAA;AACN,MAAA;AACA,IAAA;AACF,EAAA;AACiC,EAAA;AAC1B,IAAA;AACC,IAAA;AACR,EAAA;AACiC,EAAA;AACzB,IAAA;AACR,EAAA;AACD;AAESsB;AACD,EAAA;AACR;AAkBM;AACM;AAEH;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACK,IAAA;AACE,IAAA;AACF,IAAA;AACL,IAAA;AACA,IAAA;AACQT,IAAAA;AACT,EAAA;AACD;AAGS;AACE,EAAA;AACH,EAAA;AACR;AAES;AACE,EAAA;AACH,EAAA;AACR;AAGSU;AACF,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACM,EAAA;AACC,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AAGG,MAAA;AACF,MAAA;AACJ,MAAA;AACM,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACI,QAAA;AACL,MAAA;AACM,IAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACJ,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACF,EAAA;AACJ,EAAA;AACK,IAAA;AACI,IAAA;AACF,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACD,MAAA;AACA,MAAA;AACL,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACC,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACI,EAAA;AAEH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACK,EAAA;AACE,EAAA;AACR;AAGS;AACE,EAAA;AACN,EAAA;AACK,EAAA;AACH,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACE,EAAA;AACD,EAAA;AACA,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACH,MAAA;AACL,IAAA;AACK,MAAA;AACE,IAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAOS;AACF,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACL,EAAA;AACK,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACM,IAAA;AACA,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAGS;AAEJ,EAAA;AACA,EAAA;AACJ,EAAA;AACO,IAAA;AACE,IAAA;AACP,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACN,IAAA;AACK,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAESC;AACD,EAAA;AACR;AAESC;AACA,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACF,EAAA;AACD;AAMS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AAGG,EAAA;AACV;AAES;AACE,EAAA;AACH,EAAA;AACR;AAES;AACD,EAAA;AACR;AAmBgB;AAKX,EAAA;AACA,EAAA;AACG,IAAA;AACC,IAAA;AACC,EAAA;AACF,IAAA;AACF,IAAA;AACA,IAAA;AACI,oBAAA;AACF,IAAA;AACA,IAAA;AACL,MAAA;AACA,MAAA;AACE,MAAA;AACH,IAAA;AACO,IAAA;AACR,EAAA;AACD;AAEa;AAEA;AAEA;AAEA;AAQG;AACX,EAAA;AACA,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACD,MAAA;AACN,IAAA;AACI,IAAA;AACE,MAAA;AACA,MAAA;AACL,IAAA;AACK,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACC,EAAA;AACF,IAAA;AACF,IAAA;AACA,IAAA;AACI,oBAAA;AACF,IAAA;AACA,IAAA;AACL,MAAA;AACE,MAAA;AACI,IAAA;AACA,IAAA;AACR,EAAA;AACD;AAMgB;AAKR,EAAA;AACR;AAGgB;AACT,EAAA;AACI,EAAA;AACV,EAAA;AACO,EAAA;AACR;AAagB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACK,MAAA;AACA,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACO,IAAA;AACP,EAAA;AACF;AAGgB;AACT,EAAA;AACA,EAAA;AACD,IAAA;AACJ,EAAA;AACM,EAAA;AACR;Afm1GY;AACA;AgBr+IN;AAEA;AAEG;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACT;AAES;AACD,EAAA;AACR;AAOS;AACA,EAAA;AACD,IAAA;AACF,IAAA;AACJ,IAAA;AACD,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACC,IAAA;AACJ,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AAEI,IAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACI,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAGS;AACE,EAAA;AACJ,EAAA;AACK,EAAA;AACP,EAAA;AACK,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACC,MAAA;AACA,MAAA;AACD,MAAA;AACA,IAAA;AACM,IAAA;AACR,EAAA;AACD;AAES;AACH,EAAA;AACN;AAQS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACD,IAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACF,EAAA;AACI,IAAA;AACA,IAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACI,EAAA;AACH,IAAA;AACE,IAAA;AACR,IAAA;AACD,EAAA;AACU,EAAA;AAEH,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,IAAA;AACF,IAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACF,MAAA;AACA,MAAA;AACL,IAAA;AACD,EAAA;AACI,EAAA;AACG,IAAA;AACE,IAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AAEE,EAAA;AACJ,EAAA;AACA,EAAA;AACF,EAAA;AACK,IAAA;AACT,EAAA;AACO,IAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACC,IAAA;AACR,EAAA;AACD;AAGS;AACJ,EAAA;AACE,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACT;AAOS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACI,EAAA;AACGC,IAAAA;AACAC,IAAAA;AACED,IAAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACC,EAAA;AACE,EAAA;AACD,EAAA;AACT;AAESF;AACD,EAAA;AACR;AAOM;AACA;AAcU;AACX,EAAA;AACC,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AACK,EAAA;AACD,EAAA;AACD,EAAA;AACM,EAAA;AACH,EAAA;AACC,IAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACF,IAAA;AACG,IAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACA,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AAEI,IAAA;AACG,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACL,MAAA;AACD,IAAA;AACA,IAAA;AACK,MAAA;AACH,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACM,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACE,EAAA;AACD,EAAA;AACJ,IAAA;AACG,MAAA;AA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A;AACF,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AACT,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAES;AACD,EAAA;AACR;AAOgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACJ,EAAA;AACG,EAAA;AACG,EAAA;AACR;AAMgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACD,IAAA;AACJ,EAAA;AACM,EAAA;AACR;AAiFM;AAGU;AACV,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AACF,EAAA;AACC,EAAA;AACR;AAGgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACK,EAAA;AACV;AAGgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACF,EAAA;AACK,EAAA;AACV;AAES;AACF,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAES;AAIJ,EAAA;AACG,EAAA;AACF,IAAA;AACJ,IAAA;AACA,IAAA;AACO,IAAA;AACP,IAAA;AACD,EAAA;AACD;AASgB;AAMT,EAAA;AACD,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACF,MAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACM,IAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAUgB;AAKT,EAAA;AACF,EAAA;AACI,IAAA;AACR,EAAA;AACM,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACL,MAAA;AACM,MAAA;AACD,MAAA;AACL,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACF,MAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACM,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACI,QAAA;AACH,QAAA;AACA,QAAA;AACS,MAAA;AACX,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAQgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACC,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACL,EAAA;AACO,EAAA;AACR;AjBgoJY;AACA;AkB/0KC;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGgB;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAMgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACT;AAkCS;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACM,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAES;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAegB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACC,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACE,IAAA;AACL,MAAA;AACK,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACC,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AAEU,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACC,IAAA;AACF,IAAA;AACC,MAAA;AACC,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AAEI,EAAA;AACA,EAAA;AACE,EAAA;AAEF,EAAA;AACC,IAAA;AAEGf,MAAAA;AAEA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACD,MAAA;AACM,MAAA;AACF,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACC,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACC,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACM,EAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACC,IAAA;AAEA,IAAA;AACA,IAAA;AACC,IAAA;AACN,MAAA;AAGD,IAAA;AACD,EAAA;AACS,EAAA;AACA,EAAA;AACH,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAiBgB;AAOT,EAAA;AACA,EAAA;AACD,EAAA;AACG,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACH,IAAA;AACH,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAMgB;AAKP,EAAA;AACF,EAAA;AACG,EAAA;AACV;AlB0uKY;AACA;AmBniLC;AAEA;AAoHC;AACF;AACN;AACA;AAGU;AACP,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAEM;AAGU;AACTgB,EAAAA;AACCA,EAAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACAA,EAAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAGgB;AACX,EAAA;AACA,EAAA;AACK,EAAA;AACA,IAAA;AACT,EAAA;AACK,EAAA;AACE,EAAA;AACR;AAcM;AACY,mBAAA;AACR,EAAA;AACQ,mBAAA;AACA,mBAAA;AAEjB,EAAA;AACM,IAAA;AACL,IAAA;AACO,MAAA;AACA,MAAA;AACD,QAAA;AACJ,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACH,QAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACN,IAAA;AACA,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AAE8B,EAAA;AACvB,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAAA;AAGkB,EAAA;AACb,IAAA;AACC,IAAA;AACD,IAAA;AACA,MAAA;AACJ,IAAA;AACM,MAAA;AACN,IAAA;AACD,EAAA;AAEU,EAAA;AACH,IAAA;AACA,IAAA;AACF,IAAA;AACG,IAAA;AACF,MAAA;AACC,MAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AACD;AAES;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACP;AAES;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACP;AAEM;AAGG;AACE,EAAA;AACF,EAAA;AACC,EAAA;AACF,IAAA;AACC,IAAA;AACP,EAAA;AACF;AAGS;AACE,EAAA;AACA,EAAA;AACJ,EAAA;AACE,EAAA;AACJ,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACS,IAAA;AACT,EAAA;AACD;AAGS;AACE,EAAA;AACF,EAAA;AACA,EAAA;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A;AACG,EAAA;AACA,IAAA;AACAO,IAAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACQ,IAAA;AACD,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AASF,EAAA;AACA,EAAA;AACC,IAAA;AACC,IAAA;AACP,EAAA;AACK,EAAA;AACA,EAAA;AACE,IAAA;AACH,IAAA;AACJ,EAAA;AACK,EAAA;AACE,IAAA;AACH,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAES;AACA,EAAA;AACD,EAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACP,EAAA;AACF;AAES;AACE,EAAA;AACJ,EAAA;AACI,EAAA;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACAiB,IAAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACM,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AAES;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACP,EAAA;AACC,IAAA;AACA,IAAA;AACO,IAAA;AACA,IAAA;AACR,EAAA;AACQ,EAAA;AACA,EAAA;AAEJ,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACI,EAAA;AACI,EAAA;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACC,QAAA;AACE,QAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACD;AASS;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACA,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACA,EAAA;AACF;AASgB;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACL,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAGgB;AACL,EAAA;AACV,EAAA;AACS,EAAA;AACV;AAMgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AAEA,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACS,EAAA;AACV;AAOgB;AAML,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGgB;AAKR,EAAA;AACR;AAGgB;AACR,EAAA;AAGR;AAGgB;AACT,EAAA;AACA,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AACA,QAAA;AACD,MAAA;AACM,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACK,MAAA;AACC,MAAA;AACD,QAAA;AACA,QAAA;AACJ,QAAA;AACA,MAAA;AACI,MAAA;AACJ,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAA;AACO,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,MAAA;AACG,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;ApBuyMY;AACA;AqBj1NA;AAcI;AACL,EAAA;AACH,EAAA;AACR;AASc;AACR;AAEG;AACC,EAAA;AACV;AAGS;AACE,EAAA;AACFG,EAAAA;AACT;AAGS;AACC,EAAA;AACV;AAES;AACD,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACV;AAYgB;AACT,EAAA;AACI,EAAA;AACA,EAAA;AACL,EAAA;AACD,EAAA;AACI,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AACI,IAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACO,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACF,IAAA;AACD,IAAA;AACN,IAAA;AACI,IAAA;AACL,EAAA;AACM,EAAA;AACI,EAAA;AACF,IAAA;AACD,IAAA;AACN,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMgB;AACT,EAAA;AACI,EAAA;AACL,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AAEI,EAAA;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;AACD,IAAA;AACE,MAAA;AACF,MAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACC,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACD,EAAA;AAGR;AAES;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACAA,MAAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACDA,MAAAA;AACDA,MAAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGsB;AAEb;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACCA,IAAAA;AACM,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACCA,IAAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGa;AACZ,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACE,IAAA;AACR,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AAAiB;AAEV,IAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AASgD;AAEvC;AACJ,EAAA;AACE,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAEgB;AACP,EAAA;AACT;AAGgB;AACP,EAAA;AACT;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AACR,EAAA;AACK,IAAA;AACG,MAAA;AAED,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AACD;AAMgB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACC,IAAA;AACP,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACF,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACG,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACE,IAAA;AACR,EAAA;AACS,EAAA;AACV;AAES;AACF,EAAA;AACF,EAAA;AACJ,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAOgB;AACP,EAAA;AACJ,EAAA;AACE,EAAA;AACD,IAAA;AACE,IAAA;AACF,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAUgB;AAMT,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACD,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACM,EAAA;AACD,EAAA;AACV;AAQgB;AAQT,EAAA;AACD,EAAA;AACD,EAAA;AACG,IAAA;AACD,IAAA;AACD,IAAA;AACG,MAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACA,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AAQX,EAAA;AACG,IAAA;AACD,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AACF,MAAA;AACE,MAAA;AACP,IAAA;AACA,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAQgB;AAMT,EAAA;AACA,EAAA;AACE,EAAA;AACJ,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACO,IAAA;AACP,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AACX,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACI,IAAA;AACJ,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AvBojOY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","sourcesContent":[null,"/**\n * The time source that canvas animations and agent performances play on.\n *\n * Defaults to `realClock` (requestAnimationFrame and setTimeout).\n * A `ManualClock` moves only when told to via `advance(ms)` or `tick(fps)`,\n * enabling deterministic frame-by-frame stepping for testing, video generation,\n * or headless server environments.\n */\n\nexport interface Clock {\n\t/** Current time in milliseconds. */\n\tnow(): number;\n\t/** Resolves on the next animation frame. */\n\tframe(): Promise<void>;\n\t/** Resolves after `ms` milliseconds of this clock's time. */\n\tsleep(ms: number): Promise<void>;\n\t/**\n\t * Whether the clock follows wall time. Live clocks stop or degrade when the\n\t * tab is in the background (browsers pause animation frames), so transitions\n\t * can commit immediately rather than leaving operations hanging.\n\t */\n\treadonly live: boolean;\n}\n\n/** Whether the browser tab is hidden (where RAF pauses). */\nexport const pageHidden = (): boolean =>\n\ttypeof document !== 'undefined' && document.hidden;\n\n/** Real-time clock using requestAnimationFrame and setTimeout. */\nexport const realClock: Clock = {\n\tlive: true,\n\tnow: () => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n\tframe: () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\tif (typeof requestAnimationFrame === 'function' && !pageHidden()) {\n\t\t\t\trequestAnimationFrame(() => resolve());\n\t\t\t} else {\n\t\t\t\tsetTimeout(resolve, 16);\n\t\t\t}\n\t\t}),\n\tsleep: (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, ms)))\n};\n\nconst settle = () => new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n/**\n * A deterministic clock that only moves forward when `advance` or `tick` is called.\n * Allows step-by-step frame testing and video rendering without waiting for real time.\n */\nexport class ManualClock implements Clock {\n\treadonly live = false;\n\tprivate time: number;\n\tprivate frames: (() => void)[] = [];\n\tprivate sleepers: { at: number; seq: number; resolve: () => void }[] = [];\n\tprivate seq = 0;\n\n\tconstructor(start = 0) {\n\t\tthis.time = start;\n\t}\n\n\tpublic now(): number {\n\t\treturn this.time;\n\t}\n\n\tpublic frame(): Promise<void> {\n\t\treturn new Promise<void>((resolve) => this.frames.push(resolve));\n\t}\n\n\tpublic sleep(ms: number): Promise<void> {\n\t\treturn new Promise<void>((resolve) =>\n\t\t\tthis.sleepers.push({\n\t\t\t\tat: this.time + Math.max(0, ms),\n\t\t\t\tseq: this.seq++,\n\t\t\t\tresolve\n\t\t\t})\n\t\t);\n\t}\n\n\t/** Whether any frames or sleep timers are currently pending. */\n\tpublic get pending(): boolean {\n\t\treturn this.frames.length > 0 || this.sleepers.length > 0;\n\t}\n\n\t/** Advances time by `ms`, resolving due sleep timers in order, followed by queued frames. */\n\tpublic async advance(ms: number): Promise<void> {\n\t\tconst end = this.time + Math.max(0, ms);\n\t\tawait settle();\n\n\t\twhile (true) {\n\t\t\tlet next: (typeof this.sleepers)[number] | undefined;\n\t\t\tfor (const s of this.sleepers) {\n\t\t\t\tif (s.at <= end && (!next || s.at < next.at || (s.at === next.at && s.seq < next.seq))) {\n\t\t\t\t\tnext = s;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!next) break;\n\n\t\t\tthis.sleepers.splice(this.sleepers.indexOf(next), 1);\n\t\t\tthis.time = Math.max(this.time, next.at);\n\t\t\tnext.resolve();\n\t\t\tawait settle();\n\t\t}\n\n\t\tthis.time = end;\n\t\tconst dueFrames = this.frames.splice(0, this.frames.length);\n\t\tfor (const resolveFrame of dueFrames) {\n\t\t\tresolveFrame();\n\t\t}\n\t\tawait settle();\n\t}\n\n\t/** Convenience method to advance by 1 frame at a given fps (default: 60fps = 16.67ms). */\n\tpublic async tick(fps = 60): Promise<void> {\n\t\tawait this.advance(Math.round(1000 / fps));\n\t}\n}\n","import type {\n\tCanvasNode,\n\tCanvasNodeStyle,\n\tFanOutTrajectoryOptions,\n\tNodeTransition,\n\tTrajectoryFunction,\n\tTrajectoryPoint,\n\tXYPosition\n} from './types.js';\n\n/**\n * Standard cubic ease-out function (pure math, framework-agnostic).\n */\nexport const defaultEasing = (t: number): number => --t * t * t + 1;\n\n/**\n * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.\n */\nexport const linearTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t},\n\t\tscale: progress,\n\t\topacity: Math.min(1, progress * 1.5)\n\t};\n};\n\n/**\n * Linear position trajectory: pure position interpolation without altering scale or opacity.\n */\nexport const linearPositionTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t}\n\t};\n};\n\n/**\n * Creates a fan-out trajectory where nodes arc outward from origin to target.\n */\nexport function createFanOutTrajectory(options: FanOutTrajectoryOptions = {}): TrajectoryFunction {\n\tconst {\n\t\tcurvature = 0.4,\n\t\tstartScale = 0,\n\t\tendScale = 1,\n\t\tstartOpacity = 0,\n\t\tendOpacity = 1\n\t} = options;\n\n\treturn (\n\t\torigin: XYPosition,\n\t\ttarget: XYPosition,\n\t\tprogress: number,\n\t\tindex: number,\n\t\ttotal: number\n\t): TrajectoryPoint => {\n\t\tconst dx = target.x - origin.x;\n\t\tconst dy = target.y - origin.y;\n\n\t\t// Normalized index from -1 (top) to +1 (bottom)\n\t\tconst normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;\n\n\t\t// Perpendicular control point offset to create smooth fan arc\n\t\tconst midX = origin.x + dx * 0.4;\n\t\tconst midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;\n\n\t\t// Quadratic Bezier interpolation: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;\n\n\t\tconst scale = startScale + (endScale - startScale) * progress;\n\t\tconst opacity = startOpacity + (endOpacity - startOpacity) * progress;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale,\n\t\t\topacity\n\t\t};\n\t};\n}\n\n/**\n * Creates a custom quadratic Bezier trajectory with a specified control point resolver.\n */\nexport function createBezierTrajectory(\n\tgetControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition\n): TrajectoryFunction {\n\treturn (origin: XYPosition, target: XYPosition, progress: number): TrajectoryPoint => {\n\t\tconst cp = getControlPoint(origin, target);\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale: progress,\n\t\t\topacity: progress\n\t\t};\n\t};\n}\n\nimport { realClock, type Clock } from './clock.js';\n\n/**\n * Returns a copy of `style` with its opacity set to `opacity`.\n *\n * Supports both CSS strings (`\"color: red; opacity: 0.5;\"`, used by SvelteFlow)\n * and style objects (`{ color: 'red', opacity: 0.5 }`, used by React Flow / xyflow).\n * When `style` is empty, `emptyFormat` decides which representation is produced.\n * Never mutates the input.\n */\nexport function withStyleOpacity(\n\tstyle: CanvasNodeStyle | undefined | null,\n\topacity: number,\n\temptyFormat: 'string' | 'object' = 'string'\n): CanvasNodeStyle {\n\tif (style !== null && typeof style === 'object') {\n\t\treturn { ...style, opacity };\n\t}\n\tif (typeof style === 'string' && style.trim() !== '') {\n\t\tconst rest = style\n\t\t\t.replace(/(^|;)\\s*opacity\\s*:\\s*[^;]*;?/gi, '$1')\n\t\t\t.trim()\n\t\t\t.replace(/;+\\s*$/, '')\n\t\t\t.trim();\n\t\treturn `${rest ? rest + '; ' : ''}opacity: ${opacity};`;\n\t}\n\treturn emptyFormat === 'object' ? { opacity } : `opacity: ${opacity};`;\n}\n\nexport interface MultiNodeTransitionOptions<TNode extends CanvasNode = CanvasNode> {\n\tdefaultDuration?: number;\n\tdefaultEasing?: (t: number) => number;\n\tclock?: Clock;\n\t/**\n\t * Style representation to create for nodes that have no `style` yet when an opacity\n\t * is applied. Defaults to `'string'` (SvelteFlow); pass `'object'` for React Flow.\n\t * Existing styles always keep their representation.\n\t */\n\tstyleFormat?: 'string' | 'object';\n\t/**\n\t * Called on every frame with updated copies of the transitioning nodes, in the same\n\t * order as the `transitions` array. Input nodes are never mutated.\n\t */\n\tonUpdate?: (nodes: TNode[]) => void;\n\t/** Called once all transitions finished, with the final node copies. */\n\tonComplete?: (nodes: TNode[]) => void;\n}\n\n/**\n * Coordinates and animates multiple node transitions simultaneously.\n *\n * The input nodes are treated as immutable: every frame produces updated copies\n * (position, `data.scale` / `data.opacity`, and `style` opacity) which are handed to\n * `onUpdate` and finally `onComplete`. Merge them into your node state by `id`.\n */\nexport function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(\n\ttransitions: NodeTransition<TNode>[],\n\toptions: MultiNodeTransitionOptions<TNode> = {}\n): () => void {\n\tif (transitions.length === 0) {\n\t\toptions.onComplete?.([]);\n\t\treturn () => {};\n\t}\n\n\tconst {\n\t\tdefaultDuration = 400,\n\t\tdefaultEasing: easingFunc = defaultEasing,\n\t\tclock = realClock,\n\t\tstyleFormat = 'string',\n\t\tonUpdate,\n\t\tonComplete\n\t} = options;\n\n\tlet isCancelled = false;\n\tconst startTime = clock.now();\n\n\tconst items = transitions.map((tr, i) => ({\n\t\t...tr,\n\t\tindex: tr.index ?? i,\n\t\ttotal: tr.total ?? transitions.length,\n\t\tduration: tr.duration ?? defaultDuration,\n\t\teasing: tr.easing ?? easingFunc,\n\t\tdelay: tr.delay ?? 0,\n\t\ttrajectory: tr.trajectory ?? linearTrajectory\n\t}));\n\n\t// Latest copy of every node; starts out as the untouched inputs.\n\tconst current: TNode[] = items.map((item) => item.node);\n\n\tfunction frame() {\n\t\tif (isCancelled) return;\n\n\t\tconst now = clock.now();\n\t\tlet allCompleted = true;\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst elapsed = now - (startTime + item.delay);\n\n\t\t\tif (elapsed < 0) {\n\t\t\t\tallCompleted = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Guard zero duration and division by zero (referenced in GOTCHAS.md)\n\t\t\tconst rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);\n\t\t\tconst progress = item.duration <= 0 ? 1 : item.easing(rawProgress);\n\n\t\t\tconst point = item.trajectory(item.from, item.to, progress, item.index, item.total);\n\n\t\t\tconst next: TNode = { ...item.node, position: { ...point.position } };\n\n\t\t\tif (point.scale !== undefined || point.opacity !== undefined) {\n\t\t\t\tconst data: Record<string, any> = { ...(item.node.data ?? {}) };\n\t\t\t\tif (point.scale !== undefined) {\n\t\t\t\t\tdata.scale = point.scale;\n\t\t\t\t}\n\t\t\t\tif (point.opacity !== undefined) {\n\t\t\t\t\tdata.opacity = point.opacity;\n\t\t\t\t\t(next as CanvasNode).style = withStyleOpacity(item.node.style, point.opacity, styleFormat);\n\t\t\t\t}\n\t\t\t\t(next as CanvasNode).data = data;\n\t\t\t}\n\n\t\t\tcurrent[i] = next;\n\n\t\t\tif (rawProgress < 1) {\n\t\t\t\tallCompleted = false;\n\t\t\t}\n\t\t}\n\n\t\tonUpdate?.(current.slice());\n\n\t\tif (!allCompleted) {\n\t\t\tclock.frame().then(frame);\n\t\t} else {\n\t\t\tonComplete?.(current.slice());\n\t\t}\n\t}\n\n\tframe();\n\n\treturn () => {\n\t\tisCancelled = true;\n\t};\n}\n","import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasNode,\n\tDirectionalDisplacementOptions,\n\tRect,\n\tReflowDisplacementOptions,\n\tXYPosition\n} from './types.js';\n\n/**\n * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.\n */\nexport function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tdefaultWidth = 200,\n\tdefaultHeight = 60\n): Rect {\n\tif (nodes.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst width = node.measured?.width ?? (node as any).width ?? defaultWidth;\n\t\tconst height = node.measured?.height ?? (node as any).height ?? defaultHeight;\n\n\t\tminX = Math.min(minX, node.position.x);\n\t\tminY = Math.min(minY, node.position.y);\n\t\tmaxX = Math.max(maxX, node.position.x + width);\n\t\tmaxY = Math.max(maxY, node.position.y + height);\n\t}\n\n\treturn {\n\t\tx: minX,\n\t\ty: minY,\n\t\twidth: maxX - minX,\n\t\theight: maxY - minY\n\t};\n}\n\n/**\n * Pure directional displacement calculation.\n * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.\n */\nexport function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {\n\texistingNodes: TNode[];\n\toriginNode: TNode;\n\tchildNodes: TNode[];\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n\toptions?: DirectionalDisplacementOptions;\n}): Map<string, XYPosition> {\n\tconst {\n\t\texistingNodes,\n\t\toriginNode,\n\t\tchildNodes,\n\t\tgetNodesBounds,\n\t\toptions = {}\n\t} = params;\n\n\tconst {\n\t\tdirection = 'right',\n\t\tgap = 40,\n\t\tspreadSiblingLanes = false,\n\t\tsiblingTolerance = 50,\n\t\tminY\n\t} = options;\n\n\tconst displacedMap = new Map<string, XYPosition>();\n\tif (childNodes.length === 0 || existingNodes.length === 0) {\n\t\treturn displacedMap;\n\t}\n\n\tconst childBounds = getNodesBounds\n\t\t? getNodesBounds(childNodes)\n\t\t: computeNodesBoundingBox(childNodes);\n\n\tconst originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;\n\tconst originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;\n\n\t// The delta space required along primary axis\n\tconst deltaPrimary =\n\t\tdirection === 'right' || direction === 'left'\n\t\t\t? childBounds.width + gap\n\t\t\t: childBounds.height + gap;\n\n\t// Vertical displacement parameters: upward and downward row clearance\n\tconst deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);\n\tconst deltaDown = Math.max(\n\t\t0,\n\t\tchildBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap\n\t);\n\n\t// Both above and below displace equally so the row expands symmetrically\n\tconst equalDelta = Math.max(deltaUp, deltaDown);\n\n\tfor (const node of existingNodes) {\n\t\tif (node.id === originNode.id) continue;\n\n\t\tconst isSiblingRow =\n\t\t\tMath.abs(node.position.y - originNode.position.y) < siblingTolerance;\n\n\t\tswitch (direction) {\n\t\t\tcase 'right': {\n\t\t\t\tif (node.position.x > originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x + deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\tif (node.position.x < originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x - deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'down': {\n\t\t\t\tif (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'up': {\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'vertical': {\n\t\t\t\tif (isSiblingRow) break;\n\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - equalDelta\n\t\t\t\t\t});\n\t\t\t\t} else if (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + equalDelta\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sibling lane spreading to avoid overlap among child nodes\n\tif (spreadSiblingLanes && (direction === 'right' || direction === 'left')) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tconst node = existingNodes.find((n) => n.id === nodeId);\n\t\t\tif (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {\n\t\t\t\tconst isAbove = node.position.y < originNode.position.y;\n\t\t\t\tdisplacedMap.set(nodeId, {\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y + (isAbove ? -deltaUp : deltaDown)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Floor constraint\n\tif (minY !== undefined) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tif (pos.y < minY) {\n\t\t\t\tdisplacedMap.set(nodeId, { x: pos.x, y: minY });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn displacedMap;\n}\n\n/**\n * Calculates Dagre reflow displacement preserving the position of an anchor node.\n */\nexport function calculateDagreReflowDisplacement<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(params: {\n\tcurrentNodes: TNode[];\n\tcurrentEdges: TEdge[];\n\tfutureNodes: TNode[];\n\tfutureEdges: TEdge[];\n\toptions: ReflowDisplacementOptions;\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n}): Map<string, XYPosition> {\n\tconst {\n\t\tcurrentNodes,\n\t\tfutureNodes,\n\t\tfutureEdges,\n\t\toptions\n\t} = params;\n\n\tconst {\n\t\tanchorNodeId,\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = 200,\n\t\tdefaultNodeHeight = 60\n\t} = options;\n\n\tconst anchorNode = currentNodes.find((n) => n.id === anchorNodeId);\n\tif (!anchorNode) return new Map();\n\n\tconst g = new dagre.graphlib.Graph();\n\tg.setDefaultEdgeLabel(() => ({}));\n\tg.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(futureNodes.map((n) => n.id));\n\n\tfutureNodes.forEach((node) => {\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\t\tg.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tfutureEdges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tg.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(g);\n\n\tconst anchorLayoutPos = g.node(anchorNodeId);\n\tif (!anchorLayoutPos) return new Map();\n\n\tconst anchorWidth = anchorNode.measured?.width ?? (anchorNode as any).width ?? defaultNodeWidth;\n\tconst anchorHeight = anchorNode.measured?.height ?? (anchorNode as any).height ?? defaultNodeHeight;\n\n\tconst anchorLayoutUpperLeft = {\n\t\tx: anchorLayoutPos.x - anchorWidth / 2,\n\t\ty: anchorLayoutPos.y - anchorHeight / 2\n\t};\n\n\tconst deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;\n\tconst deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;\n\n\tconst displacementMap = new Map<string, XYPosition>();\n\n\tcurrentNodes.forEach((node) => {\n\t\tif (node.id === anchorNodeId) return;\n\n\t\tconst layoutNode = g.node(node.id);\n\t\tif (!layoutNode) return;\n\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\n\t\tconst upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;\n\t\tconst upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;\n\n\t\tif (\n\t\t\tMath.abs(upperLeftX - node.position.x) > 1 ||\n\t\t\tMath.abs(upperLeftY - node.position.y) > 1\n\t\t) {\n\t\t\tdisplacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });\n\t\t}\n\t});\n\n\treturn displacementMap;\n}\n","import type {\n\tCanvasClipboardPayload,\n\tCanvasEdge,\n\tCanvasNode,\n\tXYPosition\n} from './types.js';\n\n/**\n * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.\n */\nexport function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {\n\tconst target = (event as any).target as HTMLElement | null;\n\tif (!target) return false;\n\tif (\n\t\ttarget.tagName === 'INPUT' ||\n\t\ttarget.tagName === 'TEXTAREA' ||\n\t\ttarget.tagName === 'SELECT' ||\n\t\ttarget.isContentEditable ||\n\t\ttarget.closest?.('[contenteditable=\"true\"]') ||\n\t\ttarget.closest?.('.ProseMirror')\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Validates that an object conforms to minimal CanvasNode shape:\n * a string `id` and a `position` whose `x` / `y` are finite numbers.\n */\nexport function isValidCanvasNode(obj: unknown): obj is CanvasNode {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; position?: unknown };\n\tif (typeof candidate.id !== 'string') return false;\n\tconst position = candidate.position as { x?: unknown; y?: unknown } | null | undefined;\n\treturn (\n\t\ttypeof position === 'object' &&\n\t\tposition !== null &&\n\t\ttypeof position.x === 'number' &&\n\t\tNumber.isFinite(position.x) &&\n\t\ttypeof position.y === 'number' &&\n\t\tNumber.isFinite(position.y)\n\t);\n}\n\n/**\n * Validates that an object conforms to minimal CanvasEdge shape (string `id`, `source`, `target`).\n */\nexport function isValidCanvasEdge(obj: unknown): obj is CanvasEdge {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; source?: unknown; target?: unknown };\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.source === 'string' &&\n\t\ttypeof candidate.target === 'string'\n\t);\n}\n\n/**\n * Checks if a string is a valid HTTP/HTTPS URL.\n */\nexport function isValidUrl(url: string): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:';\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Serializes nodes and optional edges into clipboard JSON payload.\n */\nexport function serializeCanvasNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(nodes: TNode[], edges?: TEdge[]): string {\n\tconst payload: CanvasClipboardPayload<TNode, TEdge> = {\n\t\tversion: 1,\n\t\tnodes,\n\t\tedges\n\t};\n\treturn JSON.stringify(payload);\n}\n\n/**\n * Parses raw JSON string into a CanvasClipboardPayload if valid.\n */\nexport function parseCanvasClipboardData<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(raw: string): CanvasClipboardPayload<TNode, TEdge> | null {\n\ttry {\n\t\tconst parsed = JSON.parse(raw);\n\t\tif (\n\t\t\ttypeof parsed === 'object' &&\n\t\t\tparsed !== null &&\n\t\t\tArray.isArray(parsed.nodes) &&\n\t\t\tparsed.nodes.length > 0 &&\n\t\t\tparsed.nodes.every(isValidCanvasNode)\n\t\t) {\n\t\t\t// Untrusted input: drop malformed edges instead of crashing downstream consumers.\n\t\t\tconst edges = Array.isArray(parsed.edges)\n\t\t\t\t? (parsed.edges as unknown[]).filter(isValidCanvasEdge)\n\t\t\t\t: undefined;\n\t\t\treturn { ...parsed, edges } as CanvasClipboardPayload<TNode, TEdge>;\n\t\t}\n\t} catch {\n\t\t// Not JSON or invalid payload\n\t}\n\treturn null;\n}\n\n/**\n * Generates unique new IDs for pasted nodes and updates edge references accordingly.\n *\n * - Nodes without a valid id / finite numeric position are skipped.\n * - `parentId` (and legacy `parentNode`) references to other pasted nodes are remapped to\n * the new IDs; such children keep their parent-relative position (no extra offset).\n * References to parents outside the pasted set are left unchanged.\n * - Edges are kept only when both endpoints were pasted; a non-array `edges` is ignored.\n */\nexport function remapPastedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[] | null | undefined = [],\n\toffset: XYPosition = { x: 30, y: 30 },\n\tidGenerator: () => string = () => `node-${Math.random().toString(36).substring(2, 9)}`\n): { nodes: TNode[]; edges: TEdge[] } {\n\tconst validNodes = (Array.isArray(nodes) ? nodes : []).filter(isValidCanvasNode) as TNode[];\n\tconst validEdges = (Array.isArray(edges) ? edges : []).filter(isValidCanvasEdge) as TEdge[];\n\n\tconst dx = Number.isFinite(offset?.x) ? offset.x : 0;\n\tconst dy = Number.isFinite(offset?.y) ? offset.y : 0;\n\n\tconst idMap = new Map<string, string>();\n\tfor (const node of validNodes) {\n\t\tidMap.set(node.id, idGenerator());\n\t}\n\n\tconst newNodes = validNodes.map((node) => {\n\t\tconst next: CanvasNode = {\n\t\t\t...node,\n\t\t\tid: idMap.get(node.id)!,\n\t\t\tposition: {\n\t\t\t\tx: node.position.x + dx,\n\t\t\t\ty: node.position.y + dy\n\t\t\t},\n\t\t\tselected: true\n\t\t};\n\n\t\tlet parentRemapped = false;\n\t\tfor (const key of ['parentId', 'parentNode'] as const) {\n\t\t\tconst parent = (node as CanvasNode)[key];\n\t\t\tif (typeof parent === 'string' && idMap.has(parent)) {\n\t\t\t\tnext[key] = idMap.get(parent)!;\n\t\t\t\tparentRemapped = true;\n\t\t\t}\n\t\t}\n\t\tif (parentRemapped) {\n\t\t\t// Position is relative to the (also pasted, also offset) parent.\n\t\t\tnext.position = { x: node.position.x, y: node.position.y };\n\t\t}\n\n\t\treturn next as TNode;\n\t});\n\n\tconst newEdges = validEdges\n\t\t.filter((e) => idMap.has(e.source) && idMap.has(e.target))\n\t\t.map((edge) => ({\n\t\t\t...edge,\n\t\t\tid: `edge-${Math.random().toString(36).substring(2, 9)}`,\n\t\t\tsource: idMap.get(edge.source)!,\n\t\t\ttarget: idMap.get(edge.target)!,\n\t\t\tselected: false\n\t\t}));\n\n\treturn { nodes: newNodes, edges: newEdges };\n}\n\n/**\n * Repositions nodes so their bounding box origin matches the given target position.\n */\nexport function repositionNodesTo<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\ttargetOrigin: XYPosition\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\tconst minX = Math.min(...nodes.map((n) => n.position.x));\n\tconst minY = Math.min(...nodes.map((n) => n.position.y));\n\tconst deltaX = targetOrigin.x - minX;\n\tconst deltaY = targetOrigin.y - minY;\n\n\treturn nodes.map((n) => ({\n\t\t...n,\n\t\tposition: {\n\t\t\tx: n.position.x + deltaX,\n\t\t\ty: n.position.y + deltaY\n\t\t}\n\t}));\n}\n","import type { CanvasEdge, CanvasNode, Rect } from './types.js';\n\nexport interface CanvasHeightOptions {\n\t/** Smallest height returned (default 320). */\n\tmin?: number;\n\t/** Largest height returned; taller content is scaled/zoomed (default 560). */\n\tmax?: number;\n\t/** Extra padding around the content (default 48). */\n\tpadding?: number;\n}\n\nfunction clampHeight(contentHeight: number, options: CanvasHeightOptions = {}): number {\n\tconst { min = 320, max = 560, padding = 48 } = options;\n\treturn Math.round(Math.min(max, Math.max(min, contentHeight + padding)));\n}\n\n/**\n * Calculates the bounding box extent of all positioned nodes, or `null` for an empty graph.\n */\nexport function layoutBounds<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[]\n): Rect | null {\n\tif (nodes.length === 0) return null;\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst x = node.position.x;\n\t\tconst y = node.position.y;\n\t\tconst width = node.width ?? 180;\n\t\tconst height = node.height ?? 80;\n\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x + width);\n\t\tmaxY = Math.max(maxY, y + height);\n\t}\n\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Returns a clamped container height that fits the layout at 1:1 scale without overflow.\n */\nexport function canvasHeight<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\toptions?: CanvasHeightOptions\n): number {\n\treturn clampHeight(layoutBounds(nodes)?.height ?? 0, options);\n}\n\nconst DEFAULT_RANK_HEIGHT = 80 + 72; // Average node height + default rank separation\nconst NODES_PER_RANK = 2;\n\n/**\n * Estimates the container height to reserve before layout calculation finishes,\n * based on node and edge counts alone. Prevents layout shift (CLS).\n */\nexport function estimateCanvasHeight<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions?: CanvasHeightOptions\n): number {\n\tconst ranks = Math.max(1, Math.ceil(nodes.length / NODES_PER_RANK));\n\treturn clampHeight(ranks * DEFAULT_RANK_HEIGHT, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n\treturn a + (b - a) * t;\n}\n\n/**\n * Interpolates node positions and sizes between two layouts partway at progress `t` (0 = from, 1 = to).\n * Nodes present in both interpolate smoothly; nodes newly added in `to` snap to target.\n */\nexport function blendLayout<TNode extends CanvasNode = CanvasNode>(\n\tfrom: TNode[],\n\tto: TNode[],\n\tt: number\n): TNode[] {\n\tif (t >= 1) return to;\n\n\tconst fromMap = new Map<string, TNode>(from.map((n) => [n.id, n]));\n\n\treturn to.map((target) => {\n\t\tconst origin = fromMap.get(target.id);\n\t\tif (!origin) return target;\n\n\t\treturn {\n\t\t\t...target,\n\t\t\tposition: {\n\t\t\t\tx: mix(origin.position.x, target.position.x, t),\n\t\t\t\ty: mix(origin.position.y, target.position.y, t)\n\t\t\t},\n\t\t\twidth:\n\t\t\t\torigin.width !== undefined && target.width !== undefined\n\t\t\t\t\t? mix(origin.width, target.width, t)\n\t\t\t\t\t: target.width,\n\t\t\theight:\n\t\t\t\torigin.height !== undefined && target.height !== undefined\n\t\t\t\t\t? mix(origin.height, target.height, t)\n\t\t\t\t\t: target.height\n\t\t};\n\t});\n}\n\n/**\n * Filters out nodes that are not touched by any edge.\n *\n * During LLM streaming, nodes often arrive a chunk or token before the edge\n * that connects them. Displaying them immediately causes them to be placed as root\n * nodes only to jump into their correct position once the edge arrives.\n * Hiding orphan nodes during active streaming keeps the layout calm and jitter-free.\n */\nexport function connectedOnly<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[]\n): TNode[] {\n\tif (edges.length === 0) return nodes;\n\n\tconst linkedNodeIds = new Set<string>();\n\tfor (const edge of edges) {\n\t\tlinkedNodeIds.add(edge.source);\n\t\tlinkedNodeIds.add(edge.target);\n\t}\n\n\tconst filtered = nodes.filter((node) => linkedNodeIds.has(node.id));\n\t// If filtering would remove everything, return the original nodes\n\treturn filtered.length > 0 ? filtered : nodes;\n}\n","import { realClock, type Clock } from './clock.js';\nimport { ensureLayout } from './layout.js';\nimport { linearPositionTrajectory, runMultiNodeTransition } from './trajectory.js';\nimport type { CanvasEdge, CanvasNode, IncrementalLayoutOptions, NodeTransition, XYPosition } from './types.js';\nimport { connectedOnly } from './view.js';\n\nexport interface StreamingLayoutManagerOptions {\n\tthrottleMs?: number;\n\tglideDurationMs?: number;\n\tconnectedOnly?: boolean;\n\tclock?: Clock;\n\tlayoutOptions?: IncrementalLayoutOptions;\n\tonLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n}\n\n/**\n * Manages throttled incremental layout recalculation and smooth tweened node gliding\n * during active streaming (e.g. LLM streaming or graph generation).\n *\n * - `pushStreamUpdate` is a true throttle (leading + trailing edge): the first update\n * lays out immediately, further updates within `throttleMs` are coalesced and the\n * latest one is laid out when the window closes. Layouts therefore keep running at\n * most once per `throttleMs` while a stream is active, instead of being postponed\n * until the stream goes quiet.\n * - Every layout is computed from the graph (respecting `layoutOptions.storedPositions`\n * as pinned/manual coordinates) and each known node glides from its *current*\n * position (the `currentPositions` argument) to its newly computed position.\n */\nexport class StreamingLayoutManager<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> {\n\tprivate throttleMs: number;\n\tprivate glideDurationMs: number;\n\tprivate connectedOnly: boolean;\n\tprivate clock: Clock;\n\tprivate layoutOptions: IncrementalLayoutOptions;\n\tprivate onLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n\n\tprivate pendingTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pendingArgs: [TNode[], TEdge[], Map<string, XYPosition>] | null = null;\n\tprivate activeTransitionCancel: (() => void) | null = null;\n\tprivate targetPositions = new Map<string, XYPosition>();\n\tprivate livePositions = new Map<string, XYPosition>();\n\n\tconstructor(options: StreamingLayoutManagerOptions = {}) {\n\t\tthis.throttleMs = options.throttleMs ?? 200;\n\t\tthis.glideDurationMs = options.glideDurationMs ?? 300;\n\t\tthis.connectedOnly = options.connectedOnly ?? false;\n\t\tthis.clock = options.clock ?? realClock;\n\t\tthis.layoutOptions = options.layoutOptions ?? { direction: 'LR' };\n\t\tthis.onLayoutUpdated = options.onLayoutUpdated;\n\t}\n\n\t/**\n\t * Feed incoming nodes and edges into the streaming manager.\n\t * Throttles rapid streaming bursts (leading + trailing edge) and triggers smooth layout gliding.\n\t */\n\tpublic pushStreamUpdate(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\t// Inside the throttle window: remember the latest update for the trailing edge.\n\t\t\tthis.pendingArgs = [nodes, edges, currentPositions];\n\t\t\treturn;\n\t\t}\n\n\t\t// Leading edge: lay out immediately and open a throttle window.\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t\tthis.startWindow();\n\t}\n\n\tprivate startWindow(): void {\n\t\tthis.pendingTimer = setTimeout(() => {\n\t\t\tthis.pendingTimer = null;\n\t\t\tconst args = this.pendingArgs;\n\t\t\tthis.pendingArgs = null;\n\t\t\tif (args) {\n\t\t\t\t// Trailing edge: lay out the latest coalesced update and keep throttling.\n\t\t\t\tthis.recalculateAndGlide(...args);\n\t\t\t\tthis.startWindow();\n\t\t\t}\n\t\t}, Math.max(0, this.throttleMs));\n\t}\n\n\t/**\n\t * Forces immediate calculation and cancels pending throttled timers.\n\t */\n\tpublic flush(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tthis.clearPending();\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t}\n\n\t/**\n\t * Latest computed layout targets (the positions nodes are gliding towards).\n\t */\n\tpublic getTargetPositions(): Map<string, XYPosition> {\n\t\treturn new Map(this.targetPositions);\n\t}\n\n\tprivate clearPending(): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tthis.pendingArgs = null;\n\t}\n\n\tprivate recalculateAndGlide(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tconst targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;\n\t\tif (targetNodes.length === 0) return;\n\n\t\t// Where nodes are right now: the caller's view, overridden by any glide still in\n\t\t// flight (the caller may not have applied the latest animated frame yet).\n\t\tconst startPositions = new Map<string, XYPosition>(currentPositions);\n\t\tif (this.activeTransitionCancel) {\n\t\t\tfor (const [id, pos] of this.livePositions) {\n\t\t\t\tstartPositions.set(id, pos);\n\t\t\t}\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\n\t\t// Compute fresh layout targets. Only explicitly stored (pinned/manual) positions\n\t\t// are preserved; current positions are the glide *origin*, not the destination.\n\t\tconst layouted = ensureLayout(targetNodes, edges, this.layoutOptions);\n\n\t\tconst nextPositions = new Map<string, XYPosition>();\n\t\tconst transitions: NodeTransition<TNode>[] = [];\n\n\t\tfor (const node of layouted) {\n\t\t\tconst target = { ...node.position };\n\t\t\tnextPositions.set(node.id, target);\n\n\t\t\tconst current = startPositions.get(node.id);\n\t\t\tif (!current) continue; // brand-new node: appears directly at its target\n\n\t\t\tconst distance = Math.hypot(target.x - current.x, target.y - current.y);\n\t\t\tif (distance > 2) {\n\t\t\t\ttransitions.push({\n\t\t\t\t\tnode: { ...node, position: { ...current } },\n\t\t\t\t\tfrom: current,\n\t\t\t\t\tto: target,\n\t\t\t\t\ttrajectory: linearPositionTrajectory,\n\t\t\t\t\tduration: this.glideDurationMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.targetPositions = nextPositions;\n\n\t\tif (transitions.length === 0) {\n\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\treturn;\n\t\t}\n\n\t\tconst live = new Map<string, XYPosition>(nextPositions);\n\t\tfor (const trans of transitions) {\n\t\t\tlive.set(trans.node.id, { ...trans.from });\n\t\t}\n\t\tthis.livePositions = live;\n\n\t\tlet finished = false;\n\t\tconst cancel = runMultiNodeTransition(transitions, {\n\t\t\tclock: this.clock,\n\t\t\tonUpdate: (updated) => {\n\t\t\t\tconst frame = new Map<string, XYPosition>(this.livePositions);\n\t\t\t\tfor (const node of updated) {\n\t\t\t\t\tframe.set(node.id, { ...node.position });\n\t\t\t\t}\n\t\t\t\tthis.livePositions = frame;\n\t\t\t\tthis.onLayoutUpdated?.(frame);\n\t\t\t},\n\t\t\tonComplete: () => {\n\t\t\t\tfinished = true;\n\t\t\t\tthis.activeTransitionCancel = null;\n\t\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\t}\n\t\t});\n\t\t// A zero-duration glide completes synchronously; don't keep a stale canceller.\n\t\tthis.activeTransitionCancel = finished ? null : cancel;\n\t}\n\n\tpublic destroy(): void {\n\t\tthis.clearPending();\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\t}\n}\n","import { boundsOf, estimateNodeSize } from './layout.js';\nimport type { CanvasNode, CanvasNodeStyle, Rect, XYPosition } from './types.js';\n\n/**\n * Edge (or centre line) used by `alignNodes`.\n * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.\n */\nexport type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\n\n/** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */\nexport type DistributeAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.\n */\nexport type DistributeMode = 'gaps' | 'centres';\n\n/** Dimension(s) copied by `matchSize`. */\nexport type SizeDimension = 'width' | 'height' | 'both';\n\n/**\n * Partial node change produced by the arrange helpers. `position` is in the node's own\n * coordinate space (relative to its `parentId` parent when it has one).\n */\nexport interface CanvasNodeUpdate {\n\tid: string;\n\tposition?: XYPosition;\n\twidth?: number;\n\theight?: number;\n\tzIndex?: number;\n}\n\nexport interface NodeRectOptions {\n\t/** Width used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultWidth?: number;\n\t/** Height used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultHeight?: number;\n\t/** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */\n\testimate?: boolean;\n}\n\n/** Data key marking a node as locked. See `lockNodes`. */\nexport const LOCKED_DATA_KEY = 'locked';\n/** Data key holding the interaction flags a node had before it was locked. */\nexport const LOCKED_PREVIOUS_FLAGS_KEY = 'lockedPrevious';\n\nfunction parsePx(value: unknown): number | undefined {\n\tif (typeof value === 'number' && Number.isFinite(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst match = /^\\s*(-?\\d+(?:\\.\\d+)?)(px)?\\s*$/i.exec(value);\n\t\tif (match) return Number(match[1]);\n\t}\n\treturn undefined;\n}\n\n/**\n * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)\n * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.\n */\nexport function readStyleSize(style: CanvasNodeStyle | undefined | null): {\n\twidth?: number;\n\theight?: number;\n} {\n\tif (!style) return {};\n\tif (typeof style === 'object') {\n\t\treturn { width: parsePx(style.width), height: parsePx(style.height) };\n\t}\n\tconst read = (prop: string) => {\n\t\tconst match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style);\n\t\treturn match ? parsePx(match[1]) : undefined;\n\t};\n\treturn { width: read('width'), height: read('height') };\n}\n\n/**\n * Returns `style` with its pixel `width` / `height` replaced, keeping its representation\n * (string or object). Only dimensions already present in the style are rewritten, because\n * xyflow applies the node's own `width` / `height` props otherwise.\n */\nexport function withStyleSize(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tsize: { width?: number; height?: number }\n): CanvasNodeStyle | undefined {\n\tif (!style) return style ?? undefined;\n\tif (typeof style === 'object') {\n\t\tconst next: Record<string, any> = { ...style };\n\t\tif (size.width !== undefined && 'width' in next) next.width = size.width;\n\t\tif (size.height !== undefined && 'height' in next) next.height = size.height;\n\t\treturn next;\n\t}\n\tlet next = style;\n\tfor (const prop of ['width', 'height'] as const) {\n\t\tconst value = size[prop];\n\t\tif (value === undefined) continue;\n\t\tnext = next.replace(\n\t\t\tnew RegExp(`((?:^|;)\\\\s*${prop}\\\\s*:\\\\s*)[^;]+`, 'i'),\n\t\t\t`$1${value}px`\n\t\t);\n\t}\n\treturn next;\n}\n\n/**\n * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel\n * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).\n */\nexport function getNodeSize(\n\tnode: CanvasNode,\n\toptions: NodeRectOptions = {}\n): { width: number; height: number } {\n\tconst styleSize = readStyleSize(node.style);\n\tlet width = node.measured?.width ?? node.width ?? styleSize.width;\n\tlet height = node.measured?.height ?? node.height ?? styleSize.height;\n\tif (width === undefined || height === undefined) {\n\t\tconst fallback =\n\t\t\toptions.estimate === false\n\t\t\t\t? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 }\n\t\t\t\t: estimateNodeSize(node, options.defaultWidth, options.defaultHeight);\n\t\twidth ??= fallback.width;\n\t\theight ??= fallback.height;\n\t}\n\treturn { width, height };\n}\n\n/**\n * Absolute (flow-space) position of a node, following its `parentId` chain.\n * Cycles and missing parents are tolerated.\n */\nexport function getAbsolutePosition(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>\n): XYPosition {\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set<string>([node.id]);\n\tlet parentId = node.parentId;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tconst parent = lookup.get(parentId);\n\t\tif (!parent) break;\n\t\tseen.add(parentId);\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId;\n\t}\n\treturn { x, y };\n}\n\n/** Builds an id lookup for a node array. */\nexport function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\n/** Absolute flow-space rectangle of a node. */\nexport function getNodeRect(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions?: NodeRectOptions\n): Rect {\n\tconst { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\treturn { x, y, ...getNodeSize(node, options) };\n}\n\n/**\n * Whether a node has been locked with `lockNodes`.\n */\nexport function isLocked(node: CanvasNode | undefined | null): boolean {\n\treturn node?.data?.[LOCKED_DATA_KEY] === true;\n}\n\n/**\n * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`\n * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`\n * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and\n * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.\n */\nexport function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || isLocked(node)) return node;\n\t\tconst previous: Record<string, boolean> = {};\n\t\tif (node.draggable !== undefined) previous.draggable = node.draggable;\n\t\tif (node.deletable !== undefined) previous.deletable = node.deletable;\n\t\treturn {\n\t\t\t...node,\n\t\t\tdraggable: false,\n\t\t\tdeletable: false,\n\t\t\tdata: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }\n\t\t};\n\t});\n}\n\n/**\n * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.\n */\nexport function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || !isLocked(node)) return node;\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},\n\t\t\t...data\n\t\t} = node.data as Record<string, any>;\n\t\tconst next = { ...node, data } as TNode;\n\t\tdelete next.draggable;\n\t\tdelete next.deletable;\n\t\tif (previous.draggable !== undefined) next.draggable = previous.draggable;\n\t\tif (previous.deletable !== undefined) next.deletable = previous.deletable;\n\t\treturn next;\n\t});\n}\n\n/** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */\nexport function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\tconst anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));\n\treturn anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);\n}\n\ninterface Item {\n\tnode: CanvasNode;\n\trect: Rect;\n\tlocked: boolean;\n}\n\nfunction collect(nodes: CanvasNode[], ids: Iterable<string>, options?: NodeRectOptions) {\n\tconst lookup = createNodeLookup(nodes);\n\tconst items: Item[] = [];\n\tconst seen = new Set<string>();\n\tfor (const id of ids) {\n\t\tconst node = lookup.get(id);\n\t\tif (!node || seen.has(id)) continue;\n\t\tseen.add(id);\n\t\titems.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });\n\t}\n\treturn { lookup, items };\n}\n\n/**\n * Converts absolute target positions into node-relative `position` updates. When a parent\n * moves in the same operation, children are made relative to the parent's new position.\n */\nfunction toPositionUpdates(\n\tlookup: Map<string, CanvasNode>,\n\ttargets: Map<string, XYPosition>\n): CanvasNodeUpdate[] {\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const [id, abs] of targets) {\n\t\tconst node = lookup.get(id)!;\n\t\tlet position = abs;\n\t\tif (node.parentId && lookup.has(node.parentId)) {\n\t\t\tconst parent = lookup.get(node.parentId)!;\n\t\t\tconst parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);\n\t\t\tposition = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };\n\t\t}\n\t\tif (position.x !== node.position.x || position.y !== node.position.y) {\n\t\t\tupdates.push({ id, position });\n\t\t}\n\t}\n\treturn updates;\n}\n\nexport interface AlignNodesOptions extends NodeRectOptions {\n\t/**\n\t * What to align to: the selection bounding box (default) or a fixed rectangle.\n\t * Locked nodes never move but still count towards the selection bounds, so they act as anchors.\n\t */\n\tto?: 'selection' | Rect;\n}\n\n/**\n * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's\n * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless\n * `options.to` is a rectangle. Locked nodes are not moved.\n */\nexport function alignNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tedge: AlignEdge,\n\toptions: AlignNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst fixed = typeof options.to === 'object' ? options.to : null;\n\tif (items.length === 0 || (!fixed && items.length < 2)) return [];\n\tconst bounds = fixed ?? boundsOf(items.map((i) => i.rect))!;\n\n\tconst targets = new Map<string, XYPosition>();\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tlet { x, y } = rect;\n\t\tswitch (edge) {\n\t\t\tcase 'left':\n\t\t\t\tx = bounds.x;\n\t\t\t\tbreak;\n\t\t\tcase 'center':\n\t\t\t\tx = bounds.x + bounds.width / 2 - rect.width / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'right':\n\t\t\t\tx = bounds.x + bounds.width - rect.width;\n\t\t\t\tbreak;\n\t\t\tcase 'top':\n\t\t\t\ty = bounds.y;\n\t\t\t\tbreak;\n\t\t\tcase 'middle':\n\t\t\t\ty = bounds.y + bounds.height / 2 - rect.height / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'bottom':\n\t\t\t\ty = bounds.y + bounds.height - rect.height;\n\t\t\t\tbreak;\n\t\t}\n\t\ttargets.set(node.id, { x, y });\n\t}\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface DistributeNodesOptions extends NodeRectOptions {\n\t/** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */\n\tgap?: number;\n}\n\nfunction distributeAxis(\n\titems: Item[],\n\thorizontal: boolean,\n\tmode: DistributeMode,\n\tgapOverride: number | undefined,\n\ttargets: Map<string, XYPosition>\n) {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst size = (r: Rect) => (horizontal ? r.width : r.height);\n\tconst sorted = [...items].sort(\n\t\t(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)\n\t);\n\tconst first = sorted[0].rect;\n\tconst last = sorted[sorted.length - 1].rect;\n\tconst n = sorted.length;\n\tconst set = (item: Item, value: number) => {\n\t\tif (item.locked) return;\n\t\tconst current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };\n\t\ttargets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });\n\t};\n\n\tif (mode === 'centres') {\n\t\tconst c0 = start(first) + size(first) / 2;\n\t\tconst c1 = start(last) + size(last) / 2;\n\t\tconst step = (c1 - c0) / (n - 1);\n\t\tsorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));\n\t\treturn;\n\t}\n\n\tconst span = start(last) + size(last) - start(first);\n\tconst total = sorted.reduce((sum, item) => sum + size(item.rect), 0);\n\tconst gap = gapOverride ?? (span - total) / (n - 1);\n\tlet cursor = start(first);\n\tfor (const item of sorted) {\n\t\tset(item, cursor);\n\t\tcursor += size(item.rect) + gap;\n\t}\n}\n\n/**\n * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).\n * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)\n * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit\n * `gap`). Locked nodes keep their place in the order but are not moved.\n */\nexport function distributeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\taxis: DistributeAxis,\n\tmode: DistributeMode = 'gaps',\n\toptions: DistributeNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst minimum = options.gap !== undefined && mode === 'gaps' ? 2 : 3;\n\tif (items.length < minimum) return [];\n\tconst targets = new Map<string, XYPosition>();\n\tif (axis !== 'vertical') distributeAxis(items, true, mode, options.gap, targets);\n\tif (axis !== 'horizontal') distributeAxis(items, false, mode, options.gap, targets);\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface MatchSizeOptions extends NodeRectOptions {\n\t/**\n\t * Size source: the `largest` (default) or `smallest` node, the `first` id given,\n\t * or a specific node id.\n\t */\n\treference?: 'largest' | 'smallest' | 'first' | { id: string };\n\t/** Keep each node's centre in place instead of its top-left corner (default `false`). */\n\tkeepCentre?: boolean;\n}\n\n/**\n * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size\n * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can\n * be the reference.\n */\nexport function matchSize(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tdimension: SizeDimension,\n\toptions: MatchSizeOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { items } = collect(nodes, ids, options);\n\tif (items.length < 2) return [];\n\tconst reference = options.reference ?? 'largest';\n\n\tconst pick = (dim: 'width' | 'height'): number => {\n\t\tif (typeof reference === 'object') {\n\t\t\tconst item = items.find((i) => i.node.id === reference.id);\n\t\t\tif (item) return item.rect[dim];\n\t\t}\n\t\tif (reference === 'first') return items[0].rect[dim];\n\t\tconst values = items.map((i) => i.rect[dim]);\n\t\treturn reference === 'smallest' ? Math.min(...values) : Math.max(...values);\n\t};\n\tconst width = dimension !== 'height' ? pick('width') : undefined;\n\tconst height = dimension !== 'width' ? pick('height') : undefined;\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tconst update: CanvasNodeUpdate = { id: node.id };\n\t\tif (width !== undefined && width !== rect.width) update.width = width;\n\t\tif (height !== undefined && height !== rect.height) update.height = height;\n\t\tif (update.width === undefined && update.height === undefined) continue;\n\t\tif (options.keepCentre) {\n\t\t\tupdate.position = {\n\t\t\t\tx: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,\n\t\t\t\ty: node.position.y - ((update.height ?? rect.height) - rect.height) / 2\n\t\t\t};\n\t\t}\n\t\tupdates.push(update);\n\t}\n\treturn updates;\n}\n\n/** Z-order operation. */\nexport type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';\n\n/**\n * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).\n *\n * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal\n * `zIndex`, by array order. This computes that effective order for the siblings that share\n * the targets' parent, applies the operation, and returns `zIndex` updates assigning each\n * sibling its new rank. The array order is left alone, because xyflow requires parents to\n * precede their children. Locked nodes are not restacked. \"Forward\" moves past the next\n * sibling the node overlaps, falling back to the next sibling when nothing overlaps.\n */\nexport function reorderNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\toperation: ZOrderOperation,\n\toptions: NodeRectOptions = {}\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targetIds = new Set(\n\t\t[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))\n\t);\n\tif (targetIds.size === 0) return [];\n\n\t// Restack each sibling group (nodes sharing a parent) independently.\n\tconst groups = new Map<string, CanvasNode[]>();\n\tfor (const node of nodes) {\n\t\tconst key = node.parentId ?? '';\n\t\tif (!groups.has(key)) groups.set(key, []);\n\t\tgroups.get(key)!.push(node);\n\t}\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const siblings of groups.values()) {\n\t\tif (!siblings.some((n) => targetIds.has(n.id))) continue;\n\t\tconst order = siblings\n\t\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t\t.map((entry) => entry.node);\n\t\tconst isTarget = (n: CanvasNode) => targetIds.has(n.id);\n\t\tlet next: CanvasNode[];\n\n\t\tif (operation === 'bringToFront') {\n\t\t\tnext = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];\n\t\t} else if (operation === 'sendToBack') {\n\t\t\tnext = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];\n\t\t} else {\n\t\t\tnext = [...order];\n\t\t\tconst forward = operation === 'bringForward';\n\t\t\tconst rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));\n\t\t\tconst intersects = (a: Rect, b: Rect) =>\n\t\t\t\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n\t\t\t// Walk from the end we are moving towards so adjacent targets keep their relative order.\n\t\t\tconst indices = next.map((_, i) => i);\n\t\t\tif (forward) indices.reverse();\n\t\t\tfor (const i of indices) {\n\t\t\t\tconst node = next[i];\n\t\t\t\tif (!isTarget(node)) continue;\n\t\t\t\tconst step = forward ? 1 : -1;\n\t\t\t\tlet j = i + step;\n\t\t\t\tlet swapWith = -1;\n\t\t\t\tlet firstNonTarget = -1;\n\t\t\t\twhile (j >= 0 && j < next.length) {\n\t\t\t\t\tif (!isTarget(next[j])) {\n\t\t\t\t\t\tif (firstNonTarget === -1) firstNonTarget = j;\n\t\t\t\t\t\tif (intersects(rects.get(node.id)!, rects.get(next[j].id)!)) {\n\t\t\t\t\t\t\tswapWith = j;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tj += step;\n\t\t\t\t}\n\t\t\t\tif (swapWith === -1) swapWith = firstNonTarget;\n\t\t\t\tif (swapWith === -1) continue;\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\tnext.splice(swapWith, 0, node);\n\t\t\t}\n\t\t}\n\n\t\tif (next.every((node, i) => node === order[i])) continue;\n\t\tnext.forEach((node, rank) => {\n\t\t\tconst current = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\t\tif (current !== rank) updates.push({ id: node.id, zIndex: rank });\n\t\t});\n\t}\n\treturn updates;\n}\n\n/**\n * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep\n * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present\n * in `style` (string or object). An existing `measured` size is updated too, so geometry\n * helpers see the new size before xyflow re-measures the node.\n */\nexport function applyNodeUpdates<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tupdates: CanvasNodeUpdate[]\n): TNode[] {\n\tif (updates.length === 0) return nodes;\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const update of updates) {\n\t\tbyId.set(update.id, { ...byId.get(update.id), ...update });\n\t}\n\treturn nodes.map((node) => {\n\t\tconst update = byId.get(node.id);\n\t\tif (!update) return node;\n\t\tconst next = { ...node } as TNode;\n\t\tif (update.position) next.position = { ...update.position };\n\t\tif (update.zIndex !== undefined) (next as CanvasNode).zIndex = update.zIndex;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tif (update.width !== undefined) next.width = update.width;\n\t\t\tif (update.height !== undefined) next.height = update.height;\n\t\t\tif (node.style) next.style = withStyleSize(node.style, update);\n\t\t\tif (node.measured) {\n\t\t\t\tnext.measured = {\n\t\t\t\t\t...node.measured,\n\t\t\t\t\t...(update.width !== undefined ? { width: update.width } : {}),\n\t\t\t\t\t...(update.height !== undefined ? { height: update.height } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t});\n}\n\n/**\n * Inverse of a set of updates against the current nodes, for undo. Restores the previous\n * position / size / zIndex of every node an update touches.\n */\nexport function invertNodeUpdates(\n\tnodes: CanvasNode[],\n\tupdates: CanvasNodeUpdate[]\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst inverse: CanvasNodeUpdate[] = [];\n\tfor (const update of updates) {\n\t\tconst node = lookup.get(update.id);\n\t\tif (!node) continue;\n\t\tconst back: CanvasNodeUpdate = { id: node.id };\n\t\tif (update.position) back.position = { ...node.position };\n\t\tif (update.zIndex !== undefined) back.zIndex = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tconst size = getNodeSize(node);\n\t\t\tif (update.width !== undefined) back.width = size.width;\n\t\t\tif (update.height !== undefined) back.height = size.height;\n\t\t}\n\t\tinverse.push(back);\n\t}\n\treturn inverse;\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/**\n * A guide line to render while dragging, in flow coordinates.\n * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.\n * `measure` lines (with a distance `label`) and dashed `extension` lines come from the\n * distance measuring helpers (`measureBetween`, `measureSelection`).\n */\nexport interface GuideLine {\n\torientation: 'vertical' | 'horizontal';\n\tkind: 'edge' | 'centre' | 'spacing' | 'measure' | 'extension';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n\t/** Text drawn at the middle of the line (measuring distances). */\n\tlabel?: string;\n}\n\n/**\n * Snaps `value` to a grid with the given `spacing` and `origin`. A spacing of 0 or less\n * (or a non-finite one) leaves the value unchanged.\n *\n * Ported from vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nexport function snapToGrid(value: number, spacing: number, origin = 0): number {\n\tif (!(spacing > 0) || !Number.isFinite(spacing)) return value;\n\treturn Math.round((value - origin) / spacing) * spacing + origin;\n}\n\nexport interface SnapGuidesOptions {\n\t/** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** Snap distance in screen pixels (default 6). */\n\tthreshold?: number;\n\t/** Snap edges to other shapes' edges (default `true`). */\n\tedges?: boolean;\n\t/** Snap centres to other shapes' centres (default `true`). */\n\tcentres?: boolean;\n\t/** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */\n\tspacing?: boolean;\n\t/**\n\t * Grid spacing in flow units (default 0, off). An axis that does not snap to a shape snaps\n\t * its top-left (drag) or moving edge (resize) to the grid; grid snaps draw no guides.\n\t */\n\tgridSize?: number;\n\t/** Grid origin in flow units (default `{ x: 0, y: 0 }`). */\n\tgridOrigin?: XYPosition;\n}\n\nexport interface SnapResult {\n\t/** Snapped top-left of the moving rectangle. */\n\tposition: XYPosition;\n\t/** Offset applied to the input rectangle (`position - rect`). */\n\toffset: XYPosition;\n\t/** Guide segments to draw for the snap that was applied. */\n\tguides: GuideLine[];\n\t/** Whether x snapped to a shape (grid snaps do not count). */\n\tsnappedX: boolean;\n\tsnappedY: boolean;\n}\n\ninterface AxisEntries {\n\tvalues: Float64Array;\n\trects: Rect[];\n}\n\nfunction buildAxis(entries: { value: number; rect: Rect }[]): AxisEntries {\n\tentries.sort((a, b) => a.value - b.value);\n\treturn {\n\t\tvalues: Float64Array.from(entries.map((e) => e.value)),\n\t\trects: entries.map((e) => e.rect)\n\t};\n}\n\n/** First index whose value is >= `v`. */\nfunction lowerBound(values: Float64Array, v: number): number {\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/**\n * Pre-sorted snap targets for a set of static rectangles. Build it once when a drag starts\n * and reuse it on every pointer move; each query is a binary search, so snapping stays\n * cheap with hundreds or thousands of shapes.\n */\nexport interface GuideIndex {\n\trects: Rect[];\n\tedgeX: AxisEntries;\n\tcentreX: AxisEntries;\n\tedgeY: AxisEntries;\n\tcentreY: AxisEntries;\n}\n\n/** Builds a `GuideIndex` from the rectangles the moving shape may snap to. */\nexport function createGuideIndex(rects: Rect[]): GuideIndex {\n\tconst edgeX: { value: number; rect: Rect }[] = [];\n\tconst centreX: { value: number; rect: Rect }[] = [];\n\tconst edgeY: { value: number; rect: Rect }[] = [];\n\tconst centreY: { value: number; rect: Rect }[] = [];\n\tfor (const rect of rects) {\n\t\tedgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });\n\t\tcentreX.push({ value: rect.x + rect.width / 2, rect });\n\t\tedgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });\n\t\tcentreY.push({ value: rect.y + rect.height / 2, rect });\n\t}\n\treturn {\n\t\trects,\n\t\tedgeX: buildAxis(edgeX),\n\t\tcentreX: buildAxis(centreX),\n\t\tedgeY: buildAxis(edgeY),\n\t\tcentreY: buildAxis(centreY)\n\t};\n}\n\n/** Nearest indexed value within `tolerance` of `v`, as a signed delta, or `null`. */\nfunction nearest(axis: AxisEntries, v: number, tolerance: number): number | null {\n\tconst { values } = axis;\n\tlet best: number | null = null;\n\tfor (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {\n\t\tconst delta = values[i] - v;\n\t\tif (best === null || Math.abs(delta) < Math.abs(best)) best = delta;\n\t}\n\treturn best;\n}\n\n/** Rectangles whose indexed value equals `v` (within `eps`). */\nfunction matches(axis: AxisEntries, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {\n\t\tout.push(rects[i]);\n\t}\n\treturn out;\n}\n\ninterface SpacingCandidate {\n\tdelta: number;\n\tguides: GuideLine[];\n}\n\n/**\n * Equal-spacing candidates along one axis. Looks at shapes in the same row (horizontal) or\n * column (vertical) as the moving rectangle and proposes: centring between the nearest\n * neighbours, or repeating the gap a neighbour has to its own neighbour.\n */\nfunction spacingCandidate(\n\trects: Rect[],\n\tmoving: Rect,\n\thorizontal: boolean,\n\ttolerance: number\n): SpacingCandidate | null {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst end = (r: Rect) => (horizontal ? r.x + r.width : r.y + r.height);\n\tconst crossStart = (r: Rect) => (horizontal ? r.y : r.x);\n\tconst crossEnd = (r: Rect) => (horizontal ? r.y + r.height : r.x + r.width);\n\tconst size = end(moving) - start(moving);\n\tconst inLane = (r: Rect, lane: Rect) => crossStart(r) < crossEnd(lane) && crossEnd(r) > crossStart(lane);\n\n\tconst lane = rects.filter((r) => inLane(r, moving));\n\tif (lane.length === 0) return null;\n\n\tlet before: Rect | null = null;\n\tlet after: Rect | null = null;\n\tfor (const r of lane) {\n\t\tif (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;\n\t\tif (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;\n\t}\n\n\tconst neighbourOf = (r: Rect, forward: boolean): Rect | null => {\n\t\tlet best: Rect | null = null;\n\t\tfor (const other of lane) {\n\t\t\tif (other === r || !inLane(other, r)) continue;\n\t\t\tif (forward ? start(other) >= end(r) : end(other) <= start(r)) {\n\t\t\t\tif (!best || (forward ? start(other) < start(best) : end(other) > end(best))) best = other;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst crossMid = (a: Rect, b: Rect) =>\n\t\t(Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;\n\tconst segment = (from: number, to: number, cross: number): GuideLine =>\n\t\thorizontal\n\t\t\t? { orientation: 'horizontal', kind: 'spacing', x1: from, y1: cross, x2: to, y2: cross }\n\t\t\t: { orientation: 'vertical', kind: 'spacing', x1: cross, y1: from, x2: cross, y2: to };\n\n\tconst candidates: { pos: number; guides: (pos: number) => GuideLine[] }[] = [];\n\tif (before && after) {\n\t\tconst b = before;\n\t\tconst a = after;\n\t\tconst pos = (end(b) + start(a) - size) / 2;\n\t\tif (pos >= end(b)) {\n\t\t\tcandidates.push({\n\t\t\t\tpos,\n\t\t\t\tguides: (p) => [\n\t\t\t\t\tsegment(end(b), p, crossMid(b, moving)),\n\t\t\t\t\tsegment(p + size, start(a), crossMid(a, moving))\n\t\t\t\t]\n\t\t\t});\n\t\t}\n\t}\n\tif (before) {\n\t\tconst b = before;\n\t\tconst prev = neighbourOf(b, false);\n\t\tif (prev) {\n\t\t\tconst gap = start(b) - end(prev);\n\t\t\tcandidates.push({\n\t\t\t\tpos: end(b) + gap,\n\t\t\t\tguides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]\n\t\t\t});\n\t\t}\n\t}\n\tif (after) {\n\t\tconst a = after;\n\t\tconst next = neighbourOf(a, true);\n\t\tif (next) {\n\t\t\tconst gap = start(next) - end(a);\n\t\t\tcandidates.push({\n\t\t\t\tpos: start(a) - gap - size,\n\t\t\t\tguides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet best: SpacingCandidate | null = null;\n\tfor (const c of candidates) {\n\t\tconst delta = c.pos - start(moving);\n\t\tif (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {\n\t\t\tbest = { delta, guides: c.guides(c.pos) };\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Computes smart-guide snapping for a moving rectangle (a dragged node or a selection's\n * bounding box) against other shapes. Edges snap to edges and centres to centres within\n * `threshold` screen pixels (converted to flow units via `zoom`). Each axis snaps\n * independently to its nearest candidate. Returns the snapped position and the guides to draw.\n */\nexport function computeSnapGuides(\n\tmoving: Rect,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapGuidesOptions = {}\n): SnapResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst useSpacing = options.spacing !== false;\n\tconst eps = 0.5 / zoom;\n\n\tconst snapAxis = (horizontal: boolean) => {\n\t\tconst start = horizontal ? moving.x : moving.y;\n\t\tconst size = horizontal ? moving.width : moving.height;\n\t\tconst edgeAxis = horizontal ? index.edgeX : index.edgeY;\n\t\tconst centreAxis = horizontal ? index.centreX : index.centreY;\n\t\tlet delta: number | null = null;\n\t\tconst consider = (d: number | null) => {\n\t\t\tif (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;\n\t\t};\n\t\tif (useEdges) {\n\t\t\tconsider(nearest(edgeAxis, start, tolerance));\n\t\t\tconsider(nearest(edgeAxis, start + size, tolerance));\n\t\t}\n\t\tif (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));\n\t\tconst spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;\n\t\tif (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {\n\t\t\treturn { delta: spacing.delta, spacing };\n\t\t}\n\t\treturn { delta: delta as number | null, spacing: null };\n\t};\n\n\tconst sx = snapAxis(true);\n\tconst sy = snapAxis(false);\n\tconst grid = options.gridSize ?? 0;\n\tconst gridDelta = (v: number, origin: number) => snapToGrid(v, grid, origin) - v;\n\tconst dx = sx.delta ?? (grid > 0 ? gridDelta(moving.x, options.gridOrigin?.x ?? 0) : 0);\n\tconst dy = sy.delta ?? (grid > 0 ? gridDelta(moving.y, options.gridOrigin?.y ?? 0) : 0);\n\tconst snapped: Rect = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };\n\tconst guides: GuideLine[] = [];\n\n\tconst alignmentGuides = (horizontal: boolean) => {\n\t\tconst start = horizontal ? snapped.x : snapped.y;\n\t\tconst size = horizontal ? snapped.width : snapped.height;\n\t\tconst lines: { value: number; kind: 'edge' | 'centre'; axis: AxisEntries }[] = [];\n\t\tif (useEdges) {\n\t\t\tconst axis = horizontal ? index.edgeX : index.edgeY;\n\t\t\tlines.push({ value: start, kind: 'edge', axis }, { value: start + size, kind: 'edge', axis });\n\t\t}\n\t\tif (useCentres) {\n\t\t\tlines.push({ value: start + size / 2, kind: 'centre', axis: horizontal ? index.centreX : index.centreY });\n\t\t}\n\t\tfor (const line of lines) {\n\t\t\tconst aligned = matches(line.axis, line.value, eps);\n\t\t\tif (aligned.length === 0) continue;\n\t\t\tconst span = boundsOf([snapped, ...aligned])!;\n\t\t\tguides.push(\n\t\t\t\thorizontal\n\t\t\t\t\t? { orientation: 'vertical', kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height }\n\t\t\t\t\t: { orientation: 'horizontal', kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }\n\t\t\t);\n\t\t}\n\t};\n\n\tif (sx.delta !== null) {\n\t\tif (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));\n\t\telse alignmentGuides(true);\n\t}\n\tif (sy.delta !== null) {\n\t\tif (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));\n\t\telse alignmentGuides(false);\n\t}\n\n\treturn {\n\t\tposition: { x: snapped.x, y: snapped.y },\n\t\toffset: { x: dx, y: dy },\n\t\tguides,\n\t\tsnappedX: sx.delta !== null,\n\t\tsnappedY: sy.delta !== null\n\t};\n}\n\n/** Spacing guides are computed before the other axis snaps; shift their cross coordinate to match. */\nfunction shiftGuides(guides: GuideLine[], dx: number, dy: number): GuideLine[] {\n\treturn guides.map((g) =>\n\t\tg.orientation === 'horizontal'\n\t\t\t? { ...g, y1: g.y1 + dy, y2: g.y2 + dy }\n\t\t\t: { ...g, x1: g.x1 + dx, x2: g.x2 + dx }\n\t);\n}\n\n/** Ids of the given nodes plus every descendant (children move with their parent). */\nexport function collectDescendantIds(nodes: CanvasNode[], ids: Iterable<string>): Set<string> {\n\tconst result = new Set(ids);\n\tlet grew = true;\n\twhile (grew) {\n\t\tgrew = false;\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && result.has(node.parentId) && !result.has(node.id)) {\n\t\t\t\tresult.add(node.id);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Builds a `GuideIndex` of every node except the dragged ones and their descendants.\n * Call once at drag start and pass it to `snapNodeDrag` on every move.\n */\nexport function createNodeGuideIndex(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: NodeRectOptions & { filter?: (node: CanvasNode) => boolean } = {}\n): GuideIndex {\n\tconst exclude = collectDescendantIds(nodes, draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst rects: Rect[] = [];\n\tfor (const node of nodes) {\n\t\tif (exclude.has(node.id) || node.hidden === true) continue;\n\t\tif (options.filter && !options.filter(node)) continue;\n\t\trects.push(getNodeRect(node, lookup, options));\n\t}\n\treturn createGuideIndex(rects);\n}\n\nexport interface SnapNodeDragOptions extends SnapGuidesOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex`, reused across drag frames. Built on demand when omitted. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeDragResult extends SnapResult {\n\t/** Position updates moving every dragged node by `offset` (node-relative coordinates). */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a drag of one or more nodes. The dragged nodes' bounding box is snapped against all\n * other nodes; the resulting offset is applied to each dragged node. Dragged nodes whose\n * parent is also dragged are skipped, because they move with the parent.\n */\nexport function snapNodeDrag(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: SnapNodeDragOptions = {}\n): SnapNodeDragResult {\n\tconst ids = new Set(draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst dragged = [...ids].map((id) => lookup.get(id)).filter((n): n is CanvasNode => !!n);\n\tconst empty: SnapNodeDragResult = {\n\t\tposition: { x: 0, y: 0 },\n\t\toffset: { x: 0, y: 0 },\n\t\tguides: [],\n\t\tsnappedX: false,\n\t\tsnappedY: false,\n\t\tupdates: []\n\t};\n\tif (dragged.length === 0) return empty;\n\n\tconst bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)))!;\n\tconst index = options.index ?? createNodeGuideIndex(nodes, ids, options);\n\tconst result = computeSnapGuides(bounds, index, options);\n\tconst { x: dx, y: dy } = result.offset;\n\tconst updates: CanvasNodeUpdate[] = [];\n\tif (dx !== 0 || dy !== 0) {\n\t\tfor (const node of dragged) {\n\t\t\tif (node.parentId && ids.has(node.parentId)) continue;\n\t\t\tupdates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });\n\t\t}\n\t}\n\treturn { ...result, updates };\n}\n","import { centre } from './layout.js';\nimport type { Position, Rect, Side, XYPosition } from './types.js';\n\n/**\n * Connection line styles supported by both SvelteFlow and React Flow\n * (`connectionLineType` / edge `type`).\n */\nexport type CanvasConnectionLineType = 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';\n\nexport const CONNECTION_LINE_TYPES: readonly CanvasConnectionLineType[] = [\n\t'default',\n\t'straight',\n\t'step',\n\t'smoothstep',\n\t'simplebezier'\n];\n\n/** Maps a handle `Position` to the placement `Side` vocabulary used by `placeBeside`. */\nexport function positionToSide(position: Position): Side {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\t\treturn 'below';\n\t\tdefault:\n\t\t\treturn position;\n\t}\n}\n\n/** Maps a placement `Side` to the handle `Position` vocabulary used by xyflow. */\nexport function sideToPosition(side: Side): Position {\n\tswitch (side) {\n\t\tcase 'above':\n\t\t\treturn 'top';\n\t\tcase 'below':\n\t\t\treturn 'bottom';\n\t\tdefault:\n\t\t\treturn side;\n\t}\n}\n\n/** The handle position facing the other way. */\nexport function oppositePosition(position: Position): Position {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'bottom';\n\t\tcase 'bottom':\n\t\t\treturn 'top';\n\t\tcase 'left':\n\t\t\treturn 'right';\n\t\tdefault:\n\t\t\treturn 'left';\n\t}\n}\n\n/** Midpoint of a rectangle's side, where a centred handle sits. */\nexport function handlePoint(rect: Rect, position: Position): XYPosition {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: rect.y + rect.height / 2 };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: rect.y + rect.height / 2 };\n\t}\n}\n\nexport interface ConnectorSnapTarget {\n\tid: string;\n\trect: Rect;\n\t/** Sides that can accept a connection (default: all four). */\n\tpositions?: Position[];\n}\n\nexport interface ConnectorSnapOptions {\n\t/** Viewport zoom; `radius` is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** How far outside a shape the pointer may be and still attach, in screen pixels (default 24). */\n\tradius?: number;\n\t/** Node ids that cannot be attached to, e.g. the connection's source node. */\n\texcludeIds?: Iterable<string>;\n\t/**\n\t * `handle` snaps to the side's midpoint (a centred handle, default). `edge` snaps to the\n\t * closest point along that side, for floating edges.\n\t */\n\tmode?: 'handle' | 'edge';\n}\n\nexport interface ConnectorSnap {\n\tnodeId: string;\n\tposition: Position;\n\tside: Side;\n\t/** Attachment point in flow coordinates. */\n\tpoint: XYPosition;\n\t/** Distance from the pointer to `point`, in flow units. */\n\tdistance: number;\n}\n\n/**\n * Finds the shape side a connection line should attach to for a pointer (flow coordinates).\n * Shapes the pointer is inside win over shapes it is merely near; among those, the closest\n * allowed side wins. Returns `null` when nothing is within `radius`.\n */\nexport function findConnectorSnap(\n\tpointer: XYPosition,\n\ttargets: ConnectorSnapTarget[],\n\toptions: ConnectorSnapOptions = {}\n): ConnectorSnap | null {\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst radius = (options.radius ?? 24) / zoom;\n\tconst exclude = new Set(options.excludeIds ?? []);\n\tconst all: Position[] = ['top', 'right', 'bottom', 'left'];\n\n\tlet best: (ConnectorSnap & { inside: boolean; rank: number }) | null = null;\n\tfor (const target of targets) {\n\t\tif (exclude.has(target.id)) continue;\n\t\tconst { rect } = target;\n\t\t// Cheap rejection: pointer outside the rect grown by the radius.\n\t\tif (\n\t\t\tpointer.x < rect.x - radius ||\n\t\t\tpointer.x > rect.x + rect.width + radius ||\n\t\t\tpointer.y < rect.y - radius ||\n\t\t\tpointer.y > rect.y + rect.height + radius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst inside =\n\t\t\tpointer.x >= rect.x &&\n\t\t\tpointer.x <= rect.x + rect.width &&\n\t\t\tpointer.y >= rect.y &&\n\t\t\tpointer.y <= rect.y + rect.height;\n\n\t\tfor (const position of target.positions ?? all) {\n\t\t\tconst point =\n\t\t\t\toptions.mode === 'edge' ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);\n\t\t\t// Rank sides by distance from the pointer to the side line, so a pointer near the\n\t\t\t// middle of a wide shape attaches to the side it is closest to.\n\t\t\tconst sidePoint = closestPointOnSide(rect, position, pointer);\n\t\t\tconst rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);\n\t\t\tconst distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);\n\t\t\tif (!inside && rank > radius) continue;\n\t\t\tconst better =\n\t\t\t\t!best ||\n\t\t\t\t(inside && !best.inside) ||\n\t\t\t\t(inside === best.inside && rank < best.rank);\n\t\t\tif (better) {\n\t\t\t\tbest = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };\n\t\t\t}\n\t\t}\n\t}\n\tif (!best) return null;\n\tconst { inside: _inside, rank: _rank, ...snap } = best;\n\treturn snap;\n}\n\nfunction closestPointOnSide(rect: Rect, position: Position, p: XYPosition): XYPosition {\n\tconst clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));\n\tconst clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: clampX, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: clampX, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: clampY };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: clampY };\n\t}\n}\n\n/**\n * Picks the facing sides for a connection between two shapes, the way Freeform attaches\n * connection lines: along the axis with the larger centre offset.\n */\nexport function nearestHandlePositions(\n\tsource: Rect,\n\ttarget: Rect\n): { source: Position; target: Position } {\n\tconst a = centre(source);\n\tconst b = centre(target);\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tif (Math.abs(dx) >= Math.abs(dy)) {\n\t\treturn dx >= 0 ? { source: 'right', target: 'left' } : { source: 'left', target: 'right' };\n\t}\n\treturn dy >= 0 ? { source: 'bottom', target: 'top' } : { source: 'top', target: 'bottom' };\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/**\n * Viewport transform in xyflow's convention: `screen = flow * zoom + (x, y)`.\n */\nexport interface CanvasViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/**\n * A named, saved view of the canvas (Freeform \"Scenes\").\n */\nexport interface CanvasScene {\n\tid: string;\n\tname: string;\n\tviewport: CanvasViewport;\n\t/** Optional nodes the scene is about, e.g. to re-fit the scene after they move. */\n\tnodeIds?: string[];\n}\n\nexport interface CanvasScenesState {\n\tscenes: readonly CanvasScene[];\n\t/** Id of the scene last shown, or `null`. */\n\tcurrentId: string | null;\n\t/** Index of the current scene, or `-1`. */\n\tcurrentIndex: number;\n}\n\nexport interface CanvasScenesOptions {\n\tinitialScenes?: CanvasScene[];\n\t/** Id generator for new scenes (default: `crypto.randomUUID` with a counter fallback). */\n\tcreateId?: () => string;\n\t/** Whether `next` / `previous` wrap around at the ends (default `false`). */\n\twrap?: boolean;\n\tonChange?: (state: CanvasScenesState) => void;\n}\n\nexport interface SerializedCanvasScenes {\n\tversion: 1;\n\tscenes: CanvasScene[];\n}\n\nlet fallbackCounter = 0;\nfunction defaultId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID();\n\t}\n\tfallbackCounter += 1;\n\treturn `scene-${Date.now().toString(36)}-${fallbackCounter}`;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Validates the shape of a scene. */\nexport function isValidCanvasScene(value: unknown): value is CanvasScene {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst scene = value as Record<string, any>;\n\tconst vp = scene.viewport;\n\treturn (\n\t\ttypeof scene.id === 'string' &&\n\t\ttypeof scene.name === 'string' &&\n\t\ttypeof vp === 'object' &&\n\t\tvp !== null &&\n\t\tisFiniteNumber(vp.x) &&\n\t\tisFiniteNumber(vp.y) &&\n\t\tisFiniteNumber(vp.zoom) &&\n\t\tvp.zoom > 0 &&\n\t\t(scene.nodeIds === undefined ||\n\t\t\t(Array.isArray(scene.nodeIds) && scene.nodeIds.every((id: unknown) => typeof id === 'string')))\n\t);\n}\n\nfunction cloneScene(scene: CanvasScene): CanvasScene {\n\tconst copy: CanvasScene = {\n\t\tid: scene.id,\n\t\tname: scene.name,\n\t\tviewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }\n\t};\n\tif (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];\n\treturn copy;\n}\n\n/**\n * Parses serialized scenes (a JSON string, a `{ version, scenes }` object or a bare array).\n * Invalid entries and duplicate ids are dropped.\n */\nexport function parseCanvasScenes(input: string | unknown): CanvasScene[] {\n\tlet value: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst list = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === 'object' && value !== null && Array.isArray((value as any).scenes)\n\t\t\t? (value as any).scenes\n\t\t\t: [];\n\tconst seen = new Set<string>();\n\tconst scenes: CanvasScene[] = [];\n\tfor (const entry of list) {\n\t\tif (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;\n\t\tseen.add(entry.id);\n\t\tscenes.push(cloneScene(entry));\n\t}\n\treturn scenes;\n}\n\n/**\n * Headless, framework-agnostic store of named viewports. Snapshots returned by `getState()`\n * are cached and only replaced on change, so it can back React's `useSyncExternalStore`.\n */\nexport class CanvasScenesStore {\n\tprivate scenes: CanvasScene[];\n\tprivate currentId: string | null = null;\n\tprivate cached: CanvasScenesState;\n\tprivate listeners = new Set<(state: CanvasScenesState) => void>();\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (state: CanvasScenesState) => void;\n\twrap: boolean;\n\n\tconstructor(options: CanvasScenesOptions = {}) {\n\t\tthis.scenes = parseCanvasScenes(options.initialScenes ?? []);\n\t\tthis.createId = options.createId ?? defaultId;\n\t\tthis.wrap = options.wrap ?? false;\n\t\tthis.onChange = options.onChange;\n\t\tthis.cached = this.snapshot();\n\t}\n\n\tprivate snapshot(): CanvasScenesState {\n\t\tconst currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;\n\t\treturn {\n\t\t\tscenes: this.scenes,\n\t\t\tcurrentId: currentIndex === -1 ? null : this.currentId,\n\t\t\tcurrentIndex\n\t\t};\n\t}\n\n\tprivate commit(scenes: CanvasScene[] = this.scenes) {\n\t\tthis.scenes = scenes;\n\t\tif (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;\n\t\tthis.cached = this.snapshot();\n\t\tthis.onChange?.(this.cached);\n\t\tfor (const listener of this.listeners) listener(this.cached);\n\t}\n\n\tgetState(): CanvasScenesState {\n\t\treturn this.cached;\n\t}\n\n\tgetScene(id: string): CanvasScene | undefined {\n\t\treturn this.scenes.find((s) => s.id === id);\n\t}\n\n\tsubscribe(listener: (state: CanvasScenesState) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** First unused \"Scene N\" name. */\n\tnextDefaultName(): string {\n\t\tconst names = new Set(this.scenes.map((s) => s.name));\n\t\tlet n = this.scenes.length + 1;\n\t\twhile (names.has(`Scene ${n}`)) n += 1;\n\t\treturn `Scene ${n}`;\n\t}\n\n\t/**\n\t * Adds a scene (appended, or inserted at `index`) and makes it current.\n\t */\n\tadd(input: { viewport: CanvasViewport; name?: string; nodeIds?: string[]; id?: string }, index?: number): CanvasScene {\n\t\tconst scene = cloneScene({\n\t\t\tid: input.id && !this.getScene(input.id) ? input.id : this.createId(),\n\t\t\tname: input.name?.trim() || this.nextDefaultName(),\n\t\t\tviewport: input.viewport,\n\t\t\tnodeIds: input.nodeIds\n\t\t});\n\t\tif (!isValidCanvasScene(scene)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tconst at = index === undefined ? scenes.length : Math.max(0, Math.min(scenes.length, index));\n\t\tscenes.splice(at, 0, scene);\n\t\tthis.currentId = scene.id;\n\t\tthis.commit(scenes);\n\t\treturn scene;\n\t}\n\n\t/** Patches a scene's name, viewport or node ids. Returns the updated scene. */\n\tupdate(id: string, patch: Partial<Omit<CanvasScene, 'id'>>): CanvasScene | undefined {\n\t\tconst index = this.scenes.findIndex((s) => s.id === id);\n\t\tif (index === -1) return undefined;\n\t\tconst current = this.scenes[index];\n\t\tconst next = cloneScene({\n\t\t\t...current,\n\t\t\t...patch,\n\t\t\tname: patch.name !== undefined ? patch.name.trim() || current.name : current.name,\n\t\t\tviewport: patch.viewport ?? current.viewport,\n\t\t\tid\n\t\t});\n\t\tif (!isValidCanvasScene(next)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tscenes[index] = next;\n\t\tthis.commit(scenes);\n\t\treturn next;\n\t}\n\n\trename(id: string, name: string): CanvasScene | undefined {\n\t\treturn this.update(id, { name });\n\t}\n\n\t/** Moves a scene to `toIndex` (clamped). */\n\tmove(id: string, toIndex: number): void {\n\t\tconst from = this.scenes.findIndex((s) => s.id === id);\n\t\tif (from === -1) return;\n\t\tconst to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));\n\t\tif (from === to) return;\n\t\tconst scenes = [...this.scenes];\n\t\tconst [scene] = scenes.splice(from, 1);\n\t\tscenes.splice(to, 0, scene);\n\t\tthis.commit(scenes);\n\t}\n\n\t/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */\n\treorder(ids: string[]): void {\n\t\tconst byId = new Map(this.scenes.map((s) => [s.id, s]));\n\t\tconst ordered: CanvasScene[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst scene = byId.get(id);\n\t\t\tif (scene) {\n\t\t\t\tordered.push(scene);\n\t\t\t\tbyId.delete(id);\n\t\t\t}\n\t\t}\n\t\tthis.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.getScene(id)) return;\n\t\tthis.commit(this.scenes.filter((s) => s.id !== id));\n\t}\n\n\tclear(): void {\n\t\tthis.currentId = null;\n\t\tthis.commit([]);\n\t}\n\n\t/** Marks a scene as current and returns it. */\n\tgoTo(id: string): CanvasScene | undefined {\n\t\tconst scene = this.getScene(id);\n\t\tif (!scene) return undefined;\n\t\tif (this.currentId !== id) {\n\t\t\tthis.currentId = id;\n\t\t\tthis.commit();\n\t\t}\n\t\treturn scene;\n\t}\n\n\tprivate step(direction: 1 | -1): CanvasScene | undefined {\n\t\tconst count = this.scenes.length;\n\t\tif (count === 0) return undefined;\n\t\tconst index = this.cached.currentIndex;\n\t\tlet next: number;\n\t\tif (index === -1) next = direction === 1 ? 0 : count - 1;\n\t\telse next = index + direction;\n\t\tif (next < 0 || next >= count) {\n\t\t\tif (!this.wrap) return undefined;\n\t\t\tnext = (next + count) % count;\n\t\t}\n\t\treturn this.goTo(this.scenes[next].id);\n\t}\n\n\t/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */\n\tnext(): CanvasScene | undefined {\n\t\treturn this.step(1);\n\t}\n\n\t/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */\n\tprevious(): CanvasScene | undefined {\n\t\treturn this.step(-1);\n\t}\n\n\ttoJSON(): SerializedCanvasScenes {\n\t\treturn { version: 1, scenes: this.scenes.map(cloneScene) };\n\t}\n\n\tserialize(): string {\n\t\treturn JSON.stringify(this.toJSON());\n\t}\n\n\t/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */\n\tload(input: string | unknown): void {\n\t\tthis.currentId = null;\n\t\tthis.commit(parseCanvasScenes(input));\n\t}\n}\n\nexport function createCanvasScenesStore(options?: CanvasScenesOptions): CanvasScenesStore {\n\treturn new CanvasScenesStore(options);\n}\n\nexport interface FitViewportOptions {\n\t/** Padding around the content as a fraction of the content size per side (default 0.1, like xyflow). */\n\tpadding?: number;\n\tminZoom?: number;\n\tmaxZoom?: number;\n}\n\n/**\n * Viewport that fits `rect` into a container of `size`, centred (xyflow `fitView` maths).\n */\nexport function fitViewportToRect(\n\trect: Rect,\n\tsize: { width: number; height: number },\n\toptions: FitViewportOptions = {}\n): CanvasViewport {\n\tconst { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;\n\tconst paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);\n\tconst paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);\n\tconst raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);\n\tconst zoom = Math.min(maxZoom, Math.max(minZoom, raw));\n\tconst cx = rect.x + rect.width / 2;\n\tconst cy = rect.y + rect.height / 2;\n\treturn { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };\n}\n\n/**\n * Viewport that fits the given nodes (all nodes when `ids` is omitted), or `null` when none match.\n */\nexport function fitViewportToNodes(\n\tnodes: CanvasNode[],\n\tsize: { width: number; height: number },\n\tids?: Iterable<string>,\n\toptions: FitViewportOptions & NodeRectOptions = {}\n): CanvasViewport | null {\n\tconst lookup = createNodeLookup(nodes);\n\tconst wanted = ids ? new Set(ids) : null;\n\tconst rects = nodes\n\t\t.filter((n) => (wanted ? wanted.has(n.id) : true))\n\t\t.map((n) => getNodeRect(n, lookup, options));\n\tconst bounds = boundsOf(rects);\n\treturn bounds ? fitViewportToRect(bounds, size, options) : null;\n}\n","/**\n * Polygon boolean operations (union, intersection, difference, xor and divide) on\n * multi-polygons with holes.\n *\n * Algorithm (overlay + winding classification):\n * 1. Every input edge is collected into one segment set. Vertices are merged through a\n * tolerance-aware point pool, so points closer than `epsilon` become one vertex.\n * 2. Segments are split at every crossing, T-junction and collinear overlap until no two\n * segments cross (a planar arrangement). Splitting is repeated because snapping a new\n * vertex can move a segment by up to `epsilon`.\n * 3. Coincident segments (shared edges) are merged, keeping for every operand the signed\n * number of its edges that run along the segment.\n * 4. For every segment the winding number of each operand on its right side is computed\n * with a ray cast from the segment midpoint (the segment itself excluded), and the left\n * side follows from the signed multiplicities. The fill rule turns windings into\n * inside / outside flags and the operation decides whether each side is in the result.\n * 5. Segments with the result on exactly one side are kept, oriented with the result on\n * their left, and traced into rings (taking the sharpest left turn at shared vertices,\n * so rings that touch at a vertex stay separate simple rings). Rings with a positive\n * signed area are outer rings; negative ones are holes, assigned to the smallest outer\n * ring that contains them.\n *\n * Handles shared edges, touching vertices, collinear overlaps, holes, nested and disjoint\n * inputs and self-intersecting input rings (resolved by the operand's fill rule).\n *\n * Limits: the overlay is O(n²) in the number of edges (pruned by a sweep on x), which is\n * comfortable for canvas shapes (thousands of edges) but not for GIS-scale data. Features\n * narrower than `epsilon` collapse. Coordinates must be finite.\n */\nimport type { Point } from './types.js';\n\n/** A closed ring of points. The closing point may be repeated or omitted. */\nexport type PolygonRing = Point[];\n/** A polygon: the outer ring followed by its holes. */\nexport type PolygonWithHoles = PolygonRing[];\n/** A set of polygons. */\nexport type MultiPolygon = PolygonWithHoles[];\n\n/** SVG fill rule used to decide which areas of a ring set are inside. */\nexport type PolygonFillRule = 'nonzero' | 'evenodd';\n\n/** Boolean operations. `xor` is Freeform's Exclude. */\nexport type PolygonBooleanOperation = 'union' | 'intersection' | 'difference' | 'xor';\n\n/**\n * A raw ring set with a fill rule (e.g. flattened SVG path data). Ring orientation is\n * significant for `nonzero`.\n */\nexport interface PolygonOperand {\n\trings: PolygonRing[];\n\tfillRule?: PolygonFillRule;\n}\n\nexport type PolygonInput = MultiPolygon | PolygonOperand;\n\nexport interface PolygonBooleanOptions {\n\t/**\n\t * Distance below which points are treated as equal. Defaults to `1e-9` times the\n\t * largest coordinate magnitude of the inputs (at least `1e-9`).\n\t */\n\tepsilon?: number;\n}\n\n/** One region of `dividePolygons`, with the indices of the operands that cover it. */\nexport interface PolygonPiece {\n\tpolygon: PolygonWithHoles;\n\toperands: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Basic measures\n// ---------------------------------------------------------------------------\n\n/** Signed shoelace area of a ring (positive when counter-clockwise in a y-up frame). */\nexport function ringSignedArea(ring: readonly Point[]): number {\n\tlet sum = 0;\n\tconst n = ring.length;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst [x1, y1] = ring[i];\n\t\tconst [x2, y2] = ring[(i + 1) % n];\n\t\tsum += x1 * y2 - x2 * y1;\n\t}\n\treturn sum / 2;\n}\n\n/** Area of a polygon (outer ring minus holes). */\nexport function polygonArea(polygon: PolygonWithHoles): number {\n\tif (polygon.length === 0) return 0;\n\tlet area = Math.abs(ringSignedArea(polygon[0]));\n\tfor (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));\n\treturn area;\n}\n\n/** Total area of a multi-polygon (holes subtracted). */\nexport function multiPolygonArea(multi: MultiPolygon): number {\n\tlet total = 0;\n\tfor (const polygon of multi) total += polygonArea(polygon);\n\treturn total;\n}\n\n/** Axis-aligned bounds of a multi-polygon (or ring list), or `null` when empty. */\nexport function multiPolygonBounds(\n\tinput: MultiPolygon | PolygonRing[]\n): { minX: number; minY: number; maxX: number; maxY: number } | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tconst visit = (ring: PolygonRing) => {\n\t\tfor (const [x, y] of ring) {\n\t\t\tif (x < minX) minX = x;\n\t\t\tif (y < minY) minY = y;\n\t\t\tif (x > maxX) maxX = x;\n\t\t\tif (y > maxY) maxY = y;\n\t\t}\n\t};\n\tfor (const item of input as any[]) {\n\t\tif (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {\n\t\t\tfor (const ring of item as PolygonRing[]) visit(ring);\n\t\t} else {\n\t\t\tvisit(item as PolygonRing);\n\t\t}\n\t}\n\treturn minX === Infinity ? null : { minX, minY, maxX, maxY };\n}\n\n/**\n * Whether a point is inside a ring set under a fill rule. Points exactly on an edge may\n * report either side.\n */\nexport function pointInRings(\n\tpoint: Point,\n\trings: readonly (readonly Point[])[],\n\tfillRule: PolygonFillRule = 'nonzero'\n): boolean {\n\tconst [px, py] = point;\n\tlet winding = 0;\n\tfor (const ring of rings) {\n\t\tconst n = ring.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst [ax, ay] = ring[i];\n\t\t\tconst [bx, by] = ring[(i + 1) % n];\n\t\t\tif (ay <= py && by > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding++;\n\t\t\t} else if (by <= py && ay > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding--;\n\t\t\t}\n\t\t}\n\t}\n\treturn fillRule === 'evenodd' ? (winding & 1) === 1 : winding !== 0;\n}\n\n/** Whether a point is inside a multi-polygon (holes excluded). */\nexport function pointInMultiPolygon(point: Point, multi: MultiPolygon): boolean {\n\treturn multi.some((polygon) => pointInRings(point, polygon, 'evenodd'));\n}\n\n// ---------------------------------------------------------------------------\n// Operand normalisation\n// ---------------------------------------------------------------------------\n\nfunction cleanRing(ring: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tconst last = out[out.length - 1];\n\t\tif (last && last[0] === p[0] && last[1] === p[1]) continue;\n\t\tout.push([p[0], p[1]]);\n\t}\n\twhile (out.length > 1) {\n\t\tconst first = out[0];\n\t\tconst last = out[out.length - 1];\n\t\tif (first[0] === last[0] && first[1] === last[1]) out.pop();\n\t\telse break;\n\t}\n\treturn out;\n}\n\nfunction isOperand(input: PolygonInput): input is PolygonOperand {\n\treturn !Array.isArray(input) && typeof input === 'object' && input !== null && Array.isArray(input.rings);\n}\n\ninterface NormalizedOperand {\n\trings: Point[][];\n\tfillRule: PolygonFillRule;\n}\n\n/**\n * Multi-polygons are re-oriented (outer rings positive, holes negative) and filled with\n * `nonzero`, so overlapping polygons in one operand behave as their union.\n */\nfunction normalizeInput(input: PolygonInput): NormalizedOperand {\n\tif (isOperand(input)) {\n\t\treturn {\n\t\t\trings: input.rings.map(cleanRing).filter((r) => r.length >= 3),\n\t\t\tfillRule: input.fillRule ?? 'nonzero'\n\t\t};\n\t}\n\tconst rings: Point[][] = [];\n\tfor (const polygon of input ?? []) {\n\t\tpolygon.forEach((raw, index) => {\n\t\t\tconst ring = cleanRing(raw);\n\t\t\tif (ring.length < 3) return;\n\t\t\tconst area = ringSignedArea(ring);\n\t\t\tif (area === 0) return;\n\t\t\tconst wantPositive = index === 0;\n\t\t\trings.push(area > 0 === wantPositive ? ring : ring.reverse());\n\t\t});\n\t}\n\treturn { rings, fillRule: 'nonzero' };\n}\n\n// ---------------------------------------------------------------------------\n// Overlay\n// ---------------------------------------------------------------------------\n\ninterface Vertex {\n\tid: number;\n\tx: number;\n\ty: number;\n}\n\n/** Merges points closer than `eps` (grid hash with neighbour lookup). */\nclass PointPool {\n\treadonly vertices: Vertex[] = [];\n\tprivate readonly cells = new Map<string, number[]>();\n\tconstructor(private readonly eps: number) {}\n\n\tget(x: number, y: number): Vertex {\n\t\tconst cx = Math.floor(x / this.eps);\n\t\tconst cy = Math.floor(y / this.eps);\n\t\tlet best: Vertex | null = null;\n\t\tlet bestDist = Infinity;\n\t\tfor (let dx = -1; dx <= 1; dx++) {\n\t\t\tfor (let dy = -1; dy <= 1; dy++) {\n\t\t\t\tconst ids = this.cells.get(`${cx + dx},${cy + dy}`);\n\t\t\t\tif (!ids) continue;\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst v = this.vertices[id];\n\t\t\t\t\tconst d = Math.hypot(v.x - x, v.y - y);\n\t\t\t\t\tif (d <= this.eps && d < bestDist) {\n\t\t\t\t\t\tbest = v;\n\t\t\t\t\t\tbestDist = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (best) return best;\n\t\tconst vertex: Vertex = { id: this.vertices.length, x, y };\n\t\tthis.vertices.push(vertex);\n\t\tconst key = `${cx},${cy}`;\n\t\tconst bucket = this.cells.get(key);\n\t\tif (bucket) bucket.push(vertex.id);\n\t\telse this.cells.set(key, [vertex.id]);\n\t\treturn vertex;\n\t}\n}\n\ninterface RawSegment {\n\ta: Vertex;\n\tb: Vertex;\n\toperand: number;\n}\n\ninterface Splits {\n\tsegment: RawSegment;\n\tpoints: Map<number, { t: number; v: Vertex }>;\n}\n\n/** Projection parameter of `p` on `a→b` and its distance to the segment line. */\nfunction project(a: Vertex, b: Vertex, p: Vertex): { t: number; dist: number } {\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tconst len2 = dx * dx + dy * dy;\n\tconst t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;\n\tconst cx = a.x + t * dx;\n\tconst cy = a.y + t * dy;\n\treturn { t, dist: Math.hypot(p.x - cx, p.y - cy) };\n}\n\nfunction addSplit(splits: Splits, t: number, v: Vertex) {\n\tconst { a, b } = splits.segment;\n\tif (v.id === a.id || v.id === b.id) return;\n\tif (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });\n}\n\n/** One pass: splits every segment at crossings / touches with other segments. */\nfunction splitPass(segments: RawSegment[], pool: PointPool, eps: number): { segments: RawSegment[]; changed: boolean } {\n\tconst order = segments\n\t\t.map((s, i) => ({\n\t\t\ti,\n\t\t\tminX: Math.min(s.a.x, s.b.x),\n\t\t\tmaxX: Math.max(s.a.x, s.b.x),\n\t\t\tminY: Math.min(s.a.y, s.b.y),\n\t\t\tmaxY: Math.max(s.a.y, s.b.y)\n\t\t}))\n\t\t.sort((p, q) => p.minX - q.minX);\n\tconst splits: Splits[] = segments.map((segment) => ({ segment, points: new Map() }));\n\n\tfor (let oi = 0; oi < order.length; oi++) {\n\t\tconst bi = order[oi];\n\t\tconst s1 = segments[bi.i];\n\t\tfor (let oj = oi + 1; oj < order.length; oj++) {\n\t\t\tconst bj = order[oj];\n\t\t\tif (bj.minX > bi.maxX + eps) break;\n\t\t\tif (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;\n\t\t\tconst s2 = segments[bj.i];\n\t\t\tconst sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;\n\t\t\tconst sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;\n\t\t\tif (sharedA && sharedB) continue; // identical (or reversed) segment\n\n\t\t\tlet touched = false;\n\t\t\t// Endpoints of one segment lying on the other: T-junctions and collinear overlaps.\n\t\t\tfor (const [seg, other, split] of [\n\t\t\t\t[s1, s2, splits[bi.i]],\n\t\t\t\t[s2, s1, splits[bj.i]]\n\t\t\t] as const) {\n\t\t\t\tfor (const p of [other.a, other.b]) {\n\t\t\t\t\tif (p.id === seg.a.id || p.id === seg.b.id) continue;\n\t\t\t\t\tconst { t, dist } = project(seg.a, seg.b, p);\n\t\t\t\t\tconst len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);\n\t\t\t\t\tif (dist <= eps && t * len > eps && (1 - t) * len > eps) {\n\t\t\t\t\t\taddSplit(split, t, p);\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (touched) continue;\n\n\t\t\t// Proper crossing.\n\t\t\tconst d1x = s1.b.x - s1.a.x;\n\t\t\tconst d1y = s1.b.y - s1.a.y;\n\t\t\tconst d2x = s2.b.x - s2.a.x;\n\t\t\tconst d2y = s2.b.y - s2.a.y;\n\t\t\tconst denom = d1x * d2y - d1y * d2x;\n\t\t\tconst len1 = Math.hypot(d1x, d1y);\n\t\t\tconst len2 = Math.hypot(d2x, d2y);\n\t\t\tif (Math.abs(denom) <= 1e-12 * len1 * len2) continue; // parallel\n\t\t\tconst ex = s2.a.x - s1.a.x;\n\t\t\tconst ey = s2.a.y - s1.a.y;\n\t\t\tconst t = (ex * d2y - ey * d2x) / denom;\n\t\t\tconst u = (ex * d1y - ey * d1x) / denom;\n\t\t\tconst tolT = eps / len1;\n\t\t\tconst tolU = eps / len2;\n\t\t\tif (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;\n\t\t\tif (sharedA || sharedB) continue; // they meet at the shared endpoint only\n\t\t\tconst v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);\n\t\t\taddSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);\n\t\t\taddSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);\n\t\t}\n\t}\n\n\tlet changed = false;\n\tconst out: RawSegment[] = [];\n\tfor (const { segment, points } of splits) {\n\t\tif (points.size === 0) {\n\t\t\tout.push(segment);\n\t\t\tcontinue;\n\t\t}\n\t\tchanged = true;\n\t\tconst ordered = [...points.values()].sort((p, q) => p.t - q.t);\n\t\tlet prev = segment.a;\n\t\tfor (const { v } of ordered) {\n\t\t\tif (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });\n\t\t\tprev = v;\n\t\t}\n\t\tif (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });\n\t}\n\treturn { segments: out, changed };\n}\n\ninterface Edge {\n\ta: Vertex;\n\tb: Vertex;\n\t/** Signed multiplicity per operand (+1 per edge running a→b, −1 per edge running b→a). */\n\tmult: number[];\n}\n\ninterface Overlay {\n\tedges: Edge[];\n\t/** Winding number per operand on the right of each edge. */\n\tright: number[][];\n\t/** Winding number per operand on the left of each edge. */\n\tleft: number[][];\n\tfillRules: PolygonFillRule[];\n\teps: number;\n}\n\nfunction defaultEpsilon(operands: NormalizedOperand[]): number {\n\tlet max = 0;\n\tfor (const op of operands) {\n\t\tfor (const ring of op.rings) {\n\t\t\tfor (const [x, y] of ring) {\n\t\t\t\tconst m = Math.max(Math.abs(x), Math.abs(y));\n\t\t\t\tif (m > max) max = m;\n\t\t\t}\n\t\t}\n\t}\n\treturn Math.max(1e-9, max * 1e-9);\n}\n\nfunction buildOverlay(inputs: PolygonInput[], options: PolygonBooleanOptions): Overlay {\n\tconst operands = inputs.map(normalizeInput);\n\tconst eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);\n\tconst pool = new PointPool(eps);\n\tlet segments: RawSegment[] = [];\n\toperands.forEach((op, operand) => {\n\t\tfor (const ring of op.rings) {\n\t\t\tconst verts = ring.map(([x, y]) => pool.get(x, y));\n\t\t\tfor (let i = 0; i < verts.length; i++) {\n\t\t\t\tconst a = verts[i];\n\t\t\t\tconst b = verts[(i + 1) % verts.length];\n\t\t\t\tif (a.id !== b.id) segments.push({ a, b, operand });\n\t\t\t}\n\t\t}\n\t});\n\n\tfor (let pass = 0; pass < 8; pass++) {\n\t\tconst result = splitPass(segments, pool, eps);\n\t\tsegments = result.segments;\n\t\tif (!result.changed) break;\n\t}\n\n\t// Merge coincident segments.\n\tconst count = operands.length;\n\tconst byKey = new Map<string, Edge>();\n\tfor (const s of segments) {\n\t\tconst forward = s.a.id < s.b.id;\n\t\tconst a = forward ? s.a : s.b;\n\t\tconst b = forward ? s.b : s.a;\n\t\tconst key = `${a.id},${b.id}`;\n\t\tlet edge = byKey.get(key);\n\t\tif (!edge) {\n\t\t\tedge = { a, b, mult: new Array(count).fill(0) };\n\t\t\tbyKey.set(key, edge);\n\t\t}\n\t\tedge.mult[s.operand] += forward ? 1 : -1;\n\t}\n\tconst edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));\n\n\tconst right: number[][] = [];\n\tconst left: number[][] = [];\n\tfor (let i = 0; i < edges.length; i++) {\n\t\tconst edge = edges[i];\n\t\t// Cast the ray across the edge rather than along it: rotating by −90° keeps windings\n\t\t// and turns a mostly horizontal edge into a mostly vertical one.\n\t\tconst horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);\n\t\tconst tx = (v: Vertex) => (horizontal ? v.y : v.x);\n\t\tconst ty = (v: Vertex) => (horizontal ? -v.x : v.y);\n\t\tconst ax = tx(edge.a);\n\t\tconst ay = ty(edge.a);\n\t\tconst bx = tx(edge.b);\n\t\tconst by = ty(edge.b);\n\t\tconst mx = (ax + bx) / 2;\n\t\tconst my = (ay + by) / 2;\n\t\tconst winding = new Array(count).fill(0);\n\t\tfor (let j = 0; j < edges.length; j++) {\n\t\t\tif (j === i) continue;\n\t\t\tconst other = edges[j];\n\t\t\tconst oax = tx(other.a);\n\t\t\tconst oay = ty(other.a);\n\t\t\tconst obx = tx(other.b);\n\t\t\tconst oby = ty(other.b);\n\t\t\tlet dir = 0;\n\t\t\tif (oay <= my && oby > my) dir = 1;\n\t\t\telse if (oby <= my && oay > my) dir = -1;\n\t\t\telse continue;\n\t\t\tconst x = oax + ((my - oay) / (oby - oay)) * (obx - oax);\n\t\t\tif (x <= mx) continue;\n\t\t\tfor (let k = 0; k < count; k++) {\n\t\t\t\tif (other.mult[k] !== 0) winding[k] += dir * other.mult[k];\n\t\t\t}\n\t\t}\n\t\t// The ray starts just past the midpoint on the +x side, which is the right of the\n\t\t// edge when it points up (dy > 0) and its left otherwise.\n\t\tconst sideIsRight = by > ay;\n\t\tconst r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);\n\t\tright.push(r);\n\t\tleft.push(r.map((w, k) => w + edge.mult[k]));\n\t}\n\treturn { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };\n}\n\nfunction insideFlags(windings: number[], fillRules: PolygonFillRule[]): boolean[] {\n\treturn windings.map((w, k) => (fillRules[k] === 'evenodd' ? (w & 1) === 1 : w !== 0));\n}\n\n// ---------------------------------------------------------------------------\n// Ring extraction\n// ---------------------------------------------------------------------------\n\ninterface DirectedEdge {\n\tfrom: Vertex;\n\tto: Vertex;\n\tangle: number;\n}\n\n/** Traces the kept directed edges (result on the left) into rings. */\nfunction traceRings(directed: DirectedEdge[]): Vertex[][] {\n\tconst outgoing = new Map<number, DirectedEdge[]>();\n\tfor (const e of directed) {\n\t\tconst list = outgoing.get(e.from.id);\n\t\tif (list) list.push(e);\n\t\telse outgoing.set(e.from.id, [e]);\n\t}\n\tconst TWO_PI = Math.PI * 2;\n\tconst next = (incoming: DirectedEdge): DirectedEdge | undefined => {\n\t\tconst candidates = outgoing.get(incoming.to.id);\n\t\tif (!candidates) return undefined;\n\t\tconst back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);\n\t\tlet best: DirectedEdge | undefined;\n\t\tlet bestTurn = Infinity;\n\t\tfor (const c of candidates) {\n\t\t\t// Clockwise sweep from the reversed incoming edge: the first hit is the sharpest\n\t\t\t// left turn, which keeps the result region on the left and splits rings at\n\t\t\t// vertices they merely touch.\n\t\t\tlet turn = back - c.angle;\n\t\t\tturn -= Math.floor(turn / TWO_PI) * TWO_PI;\n\t\t\tif (turn <= 1e-15) turn = TWO_PI;\n\t\t\tif (turn < bestTurn) {\n\t\t\t\tbestTurn = turn;\n\t\t\t\tbest = c;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst used = new Set<DirectedEdge>();\n\tconst rings: Vertex[][] = [];\n\tfor (const start of directed) {\n\t\tif (used.has(start)) continue;\n\t\tconst ring: Vertex[] = [];\n\t\tlet edge: DirectedEdge | undefined = start;\n\t\tlet guard = directed.length + 1;\n\t\twhile (edge && !used.has(edge) && guard-- > 0) {\n\t\t\tused.add(edge);\n\t\t\tring.push(edge.from);\n\t\t\tedge = next(edge);\n\t\t}\n\t\tif (ring.length >= 3) rings.push(ring);\n\t}\n\treturn rings;\n}\n\n/** Removes vertices that lie on the straight line between their neighbours. */\nfunction removeCollinear(ring: Point[], eps: number): Point[] {\n\tlet points = ring;\n\tlet changed = true;\n\twhile (changed && points.length > 3) {\n\t\tchanged = false;\n\t\tconst out: Point[] = [];\n\t\tconst n = points.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];\n\t\t\tconst cur = points[i];\n\t\t\tconst nxt = points[(i + 1) % n];\n\t\t\tconst dx = nxt[0] - prev[0];\n\t\t\tconst dy = nxt[1] - prev[1];\n\t\t\tconst len = Math.hypot(dx, dy);\n\t\t\tconst cross = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;\n\t\t\tconst dot = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;\n\t\t\tif (len > 0 && Math.abs(cross) / len <= eps && dot > 0 && dot < len * len) {\n\t\t\t\tchanged = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push(cur);\n\t\t}\n\t\tif (out.length < 3) return out;\n\t\tpoints = out;\n\t}\n\treturn points;\n}\n\nfunction assemble(rings: Vertex[][], eps: number): MultiPolygon {\n\tconst outers: { ring: Point[]; area: number }[] = [];\n\tconst holes: { ring: Point[]; area: number }[] = [];\n\tconst minArea = eps * eps;\n\tfor (const vertices of rings) {\n\t\tconst ring = removeCollinear(\n\t\t\tvertices.map((v) => [v.x, v.y] as Point),\n\t\t\teps\n\t\t);\n\t\tif (ring.length < 3) continue;\n\t\tconst area = ringSignedArea(ring);\n\t\tif (Math.abs(area) <= minArea) continue;\n\t\t(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });\n\t}\n\touters.sort((a, b) => b.area - a.area);\n\tconst polygons: PolygonWithHoles[] = outers.map((o) => [o.ring]);\n\tfor (const hole of holes) {\n\t\t// Sample the midpoint of a hole edge: overlay edges are unique, so it is not on an\n\t\t// outer ring's boundary.\n\t\tconst [p, q] = [hole.ring[0], hole.ring[1]];\n\t\tconst sample: Point = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];\n\t\tlet owner = -1;\n\t\tfor (let i = outers.length - 1; i >= 0; i--) {\n\t\t\t// Iterating smallest-first finds the tightest container.\n\t\t\tif (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], 'evenodd')) {\n\t\t\t\towner = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (owner >= 0) polygons[owner].push(hole.ring);\n\t}\n\treturn polygons;\n}\n\nfunction extract(overlay: Overlay, isInside: (flags: boolean[]) => boolean): MultiPolygon {\n\tconst directed: DirectedEdge[] = [];\n\toverlay.edges.forEach((edge, i) => {\n\t\tconst l = isInside(insideFlags(overlay.left[i], overlay.fillRules));\n\t\tconst r = isInside(insideFlags(overlay.right[i], overlay.fillRules));\n\t\tif (l === r) return;\n\t\tconst [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];\n\t\tdirected.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });\n\t});\n\treturn assemble(traceRings(directed), overlay.eps);\n}\n\nfunction predicate(operation: PolygonBooleanOperation): (flags: boolean[]) => boolean {\n\tswitch (operation) {\n\t\tcase 'union':\n\t\t\treturn (f) => f.some(Boolean);\n\t\tcase 'intersection':\n\t\t\treturn (f) => f.length > 0 && f.every(Boolean);\n\t\tcase 'difference':\n\t\t\treturn (f) => f[0] === true && !f.slice(1).some(Boolean);\n\t\tcase 'xor':\n\t\t\treturn (f) => f.filter(Boolean).length % 2 === 1;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\n/**\n * Applies a boolean operation to any number of operands.\n * - `union`: covered by any operand.\n * - `intersection`: covered by every operand.\n * - `difference`: the first operand minus all others.\n * - `xor`: covered by an odd number of operands (Exclude).\n *\n * Returns simple rings: outer rings have a positive signed area, holes a negative one.\n * Rings may touch at vertices but never cross.\n */\nexport function polygonBoolean(\n\toperation: PolygonBooleanOperation,\n\toperands: PolygonInput[],\n\toptions: PolygonBooleanOptions = {}\n): MultiPolygon {\n\tif (operands.length === 0) return [];\n\treturn extract(buildOverlay(operands, options), predicate(operation));\n}\n\nexport const unionPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('union', [a, b], options);\nexport const intersectPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('intersection', [a, b], options);\nexport const subtractPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('difference', [a, b], options);\nexport const xorPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('xor', [a, b], options);\n\n/**\n * Splits the overlay of the operands into its distinct regions (Freeform's Divide): every\n * connected area covered by the same set of operands becomes one piece. Pieces are ordered\n * by their operand set, then by size (largest first).\n */\nexport function dividePolygons(operands: PolygonInput[], options: PolygonBooleanOptions = {}): PolygonPiece[] {\n\tif (operands.length === 0) return [];\n\tconst overlay = buildOverlay(operands, options);\n\tconst signatures = new Map<string, number[]>();\n\tconst note = (windings: number[]) => {\n\t\tconst flags = insideFlags(windings, overlay.fillRules);\n\t\tif (!flags.some(Boolean)) return;\n\t\tconst key = flags.map((f) => (f ? '1' : '0')).join('');\n\t\tif (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => (f ? [k] : [])));\n\t};\n\toverlay.edges.forEach((_, i) => {\n\t\tnote(overlay.left[i]);\n\t\tnote(overlay.right[i]);\n\t});\n\tconst pieces: PolygonPiece[] = [];\n\tconst keys = [...signatures.keys()].sort();\n\tfor (const key of keys) {\n\t\tconst polygons = extract(overlay, (flags) => flags.map((f) => (f ? '1' : '0')).join('') === key);\n\t\tpolygons.sort((a, b) => polygonArea(b) - polygonArea(a));\n\t\tfor (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key)! });\n\t}\n\treturn pieces;\n}\n","/**\n * Least-squares cubic Bézier fitting of point sequences (Schneider-style: fit one cubic with\n * fixed end tangents, reparameterise with Newton steps, split at the worst point), with\n * corner preservation by a turning-angle test.\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/fit.rs and\n * vectorcraft-main/crates/pathops/src/edit.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from './types.js';\n\n/** Same as `formatNumber` in shapeGeometry (kept local to avoid an import cycle). */\nfunction formatNumber(value: number, precision: number): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\n/** One cubic Bézier segment. */\nexport interface CubicSegment {\n\tp0: Point;\n\tp1: Point;\n\tp2: Point;\n\tp3: Point;\n}\n\nexport interface FitCubicsOptions {\n\t/**\n\t * Vertices where the polyline turns by more than this many degrees stay sharp corners\n\t * (default 30). Use 180 to smooth through every vertex.\n\t */\n\tcornerAngleDeg?: number;\n\t/** Treat the points as a closed ring (the last point connects back to the first). */\n\tclosed?: boolean;\n}\n\n/** Maximum Newton reparameterisation rounds per fit. */\nconst MAX_REPARAM = 6;\n\ntype Vec = [number, number];\n\nconst sub = (a: Point, b: Point): Vec => [a[0] - b[0], a[1] - b[1]];\nconst add = (a: Point, v: Vec, s = 1): Vec => [a[0] + v[0] * s, a[1] + v[1] * s];\nconst dot = (a: Vec, b: Vec) => a[0] * b[0] + a[1] * b[1];\nconst dist = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]);\n\nfunction unit(v: Vec): Vec | null {\n\tconst l = Math.hypot(v[0], v[1]);\n\treturn l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;\n}\n\nfunction evalCubic(c: CubicSegment, t: number): Vec {\n\tconst s = 1 - t;\n\tconst b0 = s * s * s;\n\tconst b1 = 3 * s * s * t;\n\tconst b2 = 3 * s * t * t;\n\tconst b3 = t * t * t;\n\treturn [\n\t\tb0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],\n\t\tb0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]\n\t];\n}\n\n/** Evaluates a cubic segment at `t` ∈ [0, 1]. */\nexport function cubicPointAt(c: CubicSegment, t: number): Point {\n\treturn evalCubic(c, t);\n}\n\nfunction dedup(points: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of points) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tif (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);\n\t}\n\treturn out;\n}\n\n/** Chord-length parameters (0 to 1), one per point. */\nfunction chordParams(pts: Point[]): number[] {\n\tconst u = [0];\n\tlet acc = 0;\n\tfor (let i = 1; i < pts.length; i++) {\n\t\tacc += dist(pts[i - 1], pts[i]);\n\t\tu.push(acc);\n\t}\n\treturn acc > 0 ? u.map((x) => x / acc) : u;\n}\n\n/** Solves the 2×2 normal equations for the handle lengths along the fixed tangents. */\nfunction generate(pts: Point[], u: number[], t0: Vec, t1: Vec): CubicSegment {\n\tconst p0 = pts[0];\n\tconst p3 = pts[pts.length - 1];\n\tconst dirIn: Vec = [-t1[0], -t1[1]];\n\tlet c00 = 0;\n\tlet c01 = 0;\n\tlet c11 = 0;\n\tlet x0 = 0;\n\tlet x1 = 0;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst t = u[i];\n\t\tconst s = 1 - t;\n\t\tconst b0 = s * s * s;\n\t\tconst b1 = 3 * s * s * t;\n\t\tconst b2 = 3 * s * t * t;\n\t\tconst b3 = t * t * t;\n\t\tconst a0: Vec = [t0[0] * b1, t0[1] * b1];\n\t\tconst a1: Vec = [dirIn[0] * b2, dirIn[1] * b2];\n\t\tc00 += dot(a0, a0);\n\t\tc01 += dot(a0, a1);\n\t\tc11 += dot(a1, a1);\n\t\tconst tmp: Vec = [\n\t\t\tpts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),\n\t\t\tpts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))\n\t\t];\n\t\tx0 += dot(a0, tmp);\n\t\tx1 += dot(a1, tmp);\n\t}\n\tconst det = c00 * c11 - c01 * c01;\n\tconst seg = dist(p0, p3);\n\tconst eps = 1e-6 * seg;\n\tlet al = seg / 3;\n\tlet ar = seg / 3;\n\tif (Math.abs(det) > 1e-12) {\n\t\tal = (x0 * c11 - x1 * c01) / det;\n\t\tar = (c00 * x1 - c01 * x0) / det;\n\t}\n\tif (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {\n\t\tal = seg / 3;\n\t\tar = seg / 3;\n\t}\n\t// Guard against wild handles on nearly degenerate data.\n\tconst cap = seg * 4 + 1e-9;\n\tal = Math.min(al, cap);\n\tar = Math.min(ar, cap);\n\treturn { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };\n}\n\n/** Largest distance between a point and the curve at its parameter, and that point's index. */\nfunction maxError(pts: Point[], c: CubicSegment, u: number[]): [number, number] {\n\tlet e = 0;\n\tlet idx = pts.length >> 1;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst d = dist(evalCubic(c, u[i]), pts[i]);\n\t\tif (d > e) {\n\t\t\te = d;\n\t\t\tidx = i;\n\t\t}\n\t}\n\treturn [e, idx];\n}\n\n/** One Newton–Raphson step towards the parameter of the point on `c` nearest `p`. */\nfunction newton(c: CubicSegment, p: Point, t: number): number {\n\tconst s = 1 - t;\n\tconst q = evalCubic(c, t);\n\tconst d1: Vec = [\n\t\t3 * (s * s * (c.p1[0] - c.p0[0]) + 2 * s * t * (c.p2[0] - c.p1[0]) + t * t * (c.p3[0] - c.p2[0])),\n\t\t3 * (s * s * (c.p1[1] - c.p0[1]) + 2 * s * t * (c.p2[1] - c.p1[1]) + t * t * (c.p3[1] - c.p2[1]))\n\t];\n\tconst d2: Vec = [\n\t\t6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),\n\t\t6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))\n\t];\n\tconst diff: Vec = [q[0] - p[0], q[1] - p[1]];\n\tconst num = dot(diff, d1);\n\tconst den = dot(d1, d1) + dot(diff, d2);\n\tif (Math.abs(den) < 1e-12) return t;\n\tconst nt = t - num / den;\n\treturn Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;\n}\n\n/**\n * Best single cubic through `pts` with the given unit end tangents (`t1` points forward at\n * the end), starting from parameters `u`. Returns the curve, its max error and the index of\n * the worst point.\n */\nfunction fitSingleFrom(pts: Point[], u: number[], t0: Vec, t1: Vec): [CubicSegment, number, number] {\n\tlet best = generate(pts, u, t0, t1);\n\tlet [bestErr, bestSplit] = maxError(pts, best, u);\n\tconst params = u.slice();\n\tfor (let round = 0; round < MAX_REPARAM; round++) {\n\t\tfor (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);\n\t\tconst c = generate(pts, params, t0, t1);\n\t\tconst [e, s] = maxError(pts, c, params);\n\t\tif (e < bestErr) {\n\t\t\tbest = c;\n\t\t\tbestErr = e;\n\t\t\tbestSplit = s;\n\t\t} else break;\n\t}\n\treturn [best, bestErr, bestSplit];\n}\n\nfunction fitRec(pts: Point[], t0: Vec, t1: Vec, tol: number, out: CubicSegment[], depth: number) {\n\tconst n = pts.length;\n\tif (n === 2) {\n\t\tconst d = dist(pts[0], pts[1]) / 3;\n\t\tout.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });\n\t\treturn;\n\t}\n\tconst [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);\n\tif (err <= tol || depth > 40) {\n\t\tout.push(c);\n\t\treturn;\n\t}\n\t// Split at the worst point with a centre tangent estimated from its neighbours.\n\tconst split = Math.min(n - 2, Math.max(1, splitAt));\n\tconst tc = unit(sub(pts[split + 1], pts[split - 1])) ?? t0;\n\tfitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);\n\tfitRec(pts.slice(split), tc, t1, tol, out, depth + 1);\n}\n\n/**\n * Unit tangent at `a` estimated from the parabola through `a`, `b`, `c` (chord-length\n * parameters): second-order accurate, so polylines sampled from curves get their true end\n * direction rather than the first chord's.\n */\nfunction tangentFrom(a: Point, b: Point, c: Point | undefined): Vec | null {\n\tconst chord = unit(sub(b, a));\n\tif (!c) return chord;\n\tconst d1 = dist(a, b);\n\tconst d2 = dist(b, c);\n\tif (!(d1 > 0) || !(d2 > 0)) return chord;\n\tconst ka = -(2 * d1 + d2) / (d1 * (d1 + d2));\n\tconst kb = (d1 + d2) / (d1 * d2);\n\tconst kc = -d1 / (d2 * (d1 + d2));\n\tconst est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);\n\t// Fall back to the chord when the estimate swings too far (noisy or sharp data).\n\treturn est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;\n}\n\n/**\n * Fits a G1-continuous chain of cubics through `points` (an open run) with the unit\n * tangents `t0` (leaving the first point) and `t1` (arriving at the last point, forward);\n * both are estimated from the end points when omitted. Every point lies within `tolerance`\n * of the curve at its fitted parameter.\n */\nexport function fitCubicRun(points: readonly Point[], tolerance: number, t0?: Point, t1?: Point): CubicSegment[] {\n\tconst pts = dedup(points);\n\tconst out: CubicSegment[] = [];\n\tif (pts.length < 2) return out;\n\tconst n = pts.length;\n\tconst start = (t0 && unit([t0[0], t0[1]])) ?? tangentFrom(pts[0], pts[1], pts[2]) ?? [1, 0];\n\tconst back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);\n\tconst end = (t1 && unit([t1[0], t1[1]])) ?? (back ? ([-back[0], -back[1]] as Vec) : [1, 0]);\n\tfitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);\n\treturn out;\n}\n\n/** Turning angle in degrees between the directions `a`→`b` and `b`→`c`. */\nfunction turnDeg(a: Point, b: Point, c: Point): number {\n\tconst u = unit(sub(b, a));\n\tconst v = unit(sub(c, b));\n\tif (!u || !v) return 0;\n\treturn (Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180) / Math.PI;\n}\n\n/**\n * Corner vertices of a polyline. The turn at each vertex is measured to the nearest\n * neighbours at least `span` away on each side (so sample noise smaller than the fitting\n * tolerance does not read as corners), and only the sharpest vertex of a neighbourhood is\n * kept. End points of open polylines are not included.\n */\nfunction findCorners(pts: Point[], closed: boolean, cornerAngle: number, span: number): number[] {\n\tconst n = pts.length;\n\tconst at = (i: number) => pts[closed ? ((i % n) + n) % n : i];\n\tconst reach = (i: number, step: 1 | -1): number | null => {\n\t\tfor (let k = 1; k < (closed ? n : Infinity); k++) {\n\t\t\tconst j = i + step * k;\n\t\t\tif (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;\n\t\t\tif (dist(at(j), pts[i]) >= span) return j;\n\t\t}\n\t\treturn null;\n\t};\n\tconst turns = new Float64Array(n);\n\tconst windows: [number, number][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\twindows.push([i, i]);\n\t\tif (!closed && (i === 0 || i === n - 1)) continue;\n\t\tconst prev = reach(i, -1);\n\t\tconst next = reach(i, 1);\n\t\tif (prev === null || next === null) continue;\n\t\twindows[i] = [prev, next];\n\t\tturns[i] = turnDeg(at(prev), pts[i], at(next));\n\t}\n\tconst corners: number[] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tif (!(turns[i] > cornerAngle)) continue;\n\t\tconst [lo, hi] = windows[i];\n\t\tlet sharpest = true;\n\t\tfor (let j = lo; j <= hi && sharpest; j++) {\n\t\t\tconst k = closed ? ((j % n) + n) % n : j;\n\t\t\tif (k === i || dist(pts[k], pts[i]) >= span) continue;\n\t\t\tif (turns[k] > turns[i] || (turns[k] === turns[i] && k < i)) sharpest = false;\n\t\t}\n\t\tif (sharpest) corners.push(i);\n\t}\n\treturn corners;\n}\n\n/**\n * Points to fit for a run of polyline vertices: the vertices, plus extra samples along edges\n * much longer than the run's typical edge so straight stretches stay straight.\n */\nfunction densify(run: Point[]): Point[] {\n\tif (run.length < 3) return run;\n\tconst lengths: number[] = [];\n\tfor (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));\n\tconst sorted = [...lengths].sort((a, b) => a - b);\n\tconst median = sorted[sorted.length >> 1];\n\tif (!(median > 0)) return run;\n\tconst out: Point[] = [run[0]];\n\tfor (let i = 1; i < run.length; i++) {\n\t\tconst len = lengths[i - 1];\n\t\tif (len > 2 * median) {\n\t\t\tconst steps = Math.min(64, Math.ceil(len / median));\n\t\t\tconst a = run[i - 1];\n\t\t\tconst b = run[i];\n\t\t\tfor (let k = 1; k < steps; k++) {\n\t\t\t\tconst t = k / steps;\n\t\t\t\tout.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);\n\t\t\t}\n\t\t}\n\t\tout.push(run[i]);\n\t}\n\treturn out;\n}\n\n/**\n * Fits cubic Bézier segments to a polyline (`closed` for a ring), keeping vertices that turn\n * by more than `cornerAngleDeg` (default 30°) as sharp corners. Each stretch between corners\n * is fitted Schneider-style so that every input point is within `tolerance` of the result.\n *\n * Returns the segments in order (consecutive segments share end points; a closed result ends\n * where it starts). Fewer than two distinct points give an empty array.\n */\nexport function fitCubics(points: readonly Point[], tolerance: number, options: FitCubicsOptions = {}): CubicSegment[] {\n\tlet pts = dedup(points);\n\tconst closed = options.closed === true;\n\tif (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);\n\tif (pts.length < 2) return [];\n\tconst tol = Math.max(tolerance, 1e-6);\n\tconst cornerAngle = options.cornerAngleDeg ?? 30;\n\tconst n = pts.length;\n\tconst span = 4 * tol;\n\n\tif (!closed || n < 3) {\n\t\tconst corners = [0, ...(n > 2 ? findCorners(pts, false, cornerAngle, span) : []), n - 1];\n\t\tconst out: CubicSegment[] = [];\n\t\tfor (let k = 1; k < corners.length; k++) {\n\t\t\tconst run = pts.slice(corners[k - 1], corners[k] + 1);\n\t\t\tout.push(...fitCubicRun(densify(run), tol));\n\t\t}\n\t\tif (closed && n === 2) out.push(...fitCubicRun([pts[1], pts[0]], tol));\n\t\treturn out;\n\t}\n\n\tconst corners = findCorners(pts, true, cornerAngle, span);\n\tif (corners.length === 0) {\n\t\t// Smooth ring: start anywhere, with the same tangent leaving and arriving.\n\t\tconst ring = [...pts, pts[0]];\n\t\tconst t = unit(sub(pts[1], pts[n - 1])) ?? [1, 0];\n\t\t// (A central difference: already second-order accurate at the seam.)\n\t\treturn fitCubicRun(densify(ring), tol, t, t);\n\t}\n\tconst out: CubicSegment[] = [];\n\tfor (let k = 0; k < corners.length; k++) {\n\t\tconst from = corners[k];\n\t\tconst to = corners[(k + 1) % corners.length];\n\t\tconst run: Point[] = [];\n\t\tfor (let i = from; ; i = (i + 1) % n) {\n\t\t\trun.push(pts[i]);\n\t\t\tif (run.length > 1 && i === to) break;\n\t\t}\n\t\tout.push(...fitCubicRun(densify(run), tol));\n\t}\n\treturn out;\n}\n\n/** Whether a cubic is (within `tol`) the straight line from `p0` to `p3`. */\nexport function isStraightCubic(c: CubicSegment, tol: number): boolean {\n\tconst chord = sub(c.p3, c.p0);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tif (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;\n\tconst dir: Vec = [chord[0] / len, chord[1] / len];\n\treturn [c.p1, c.p2].every((h) => {\n\t\tconst v = sub(h, c.p0);\n\t\tconst along = dot(v, dir);\n\t\tconst across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);\n\t\treturn across <= tol && along >= -tol && along <= len + tol;\n\t});\n}\n\nexport interface FitPathDataOptions extends FitCubicsOptions {\n\t/** Fitting tolerance in path units (default 0.5). */\n\ttolerance?: number;\n\t/** Decimal places (default 3). */\n\tprecision?: number;\n\t/** Added to every point before formatting. */\n\toffset?: { x: number; y: number };\n\t/**\n\t * Keep the original polyline when fitting would need more segments than it has edges\n\t * (default `true`), so fitting never makes a path heavier.\n\t */\n\tnoWorse?: boolean;\n}\n\n/** SVG `d` for fitted segments (`L` for straight ones, `C` otherwise). */\nexport function cubicsToPathData(\n\tsegments: readonly CubicSegment[],\n\toptions: { precision?: number; offset?: { x: number; y: number }; closed?: boolean; straightTolerance?: number } = {}\n): string {\n\tif (segments.length === 0) return '';\n\tconst f = (n: number) => formatNumber(n, options.precision ?? 3);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst pt = (p: Point) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;\n\tconst straight = options.straightTolerance ?? 1e-3;\n\tlet d = `M${pt(segments[0].p0)}`;\n\tsegments.forEach((c, i) => {\n\t\tconst last = options.closed && i === segments.length - 1;\n\t\tif (isStraightCubic(c, straight)) {\n\t\t\tif (!last) d += `L${pt(c.p3)}`;\n\t\t} else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;\n\t});\n\treturn options.closed ? `${d}Z` : d;\n}\n\nfunction polylineToPathData(points: readonly Point[], closed: boolean, options: FitPathDataOptions): string {\n\tif (points.length < 2) return '';\n\tconst f = (n: number) => formatNumber(n, options.precision ?? 3);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tlet d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;\n\tfor (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;\n\treturn closed ? `${d}Z` : d;\n}\n\n/**\n * Fits smooth curves to a polygon ring (closed by default) and returns SVG path data with\n * `C` curves for smooth stretches, `L` lines for straight ones and sharp corners kept.\n * Use it for boolean results of curved shapes or to smooth freehand strokes\n * (`{ closed: false }`, see `fitFreehandPath`).\n */\nexport function fitPathData(ring: readonly Point[], options: FitPathDataOptions = {}): string {\n\tconst closed = options.closed ?? true;\n\tconst tolerance = options.tolerance ?? 0.5;\n\tconst pts = dedup(ring);\n\tconst segments = fitCubics(pts, tolerance, { ...options, closed });\n\tif (segments.length === 0) return '';\n\tconst edges = closed ? pts.length : pts.length - 1;\n\tif (options.noWorse !== false) {\n\t\tconst straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;\n\t\t// Fitting only pays off when it removes vertices.\n\t\tif (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);\n\t\tif (segments.length > edges) return polylineToPathData(pts, closed, options);\n\t}\n\treturn cubicsToPathData(segments, {\n\t\tprecision: options.precision,\n\t\toffset: options.offset,\n\t\tclosed,\n\t\tstraightTolerance: tolerance * 0.02\n\t});\n}\n\n/**\n * Smooths a freehand stroke (pointer samples) into an open SVG path of cubic curves.\n * `tolerance` is the largest distance (in the points' units) a sample may be from the\n * curve (default 1); sharp turns above `cornerAngleDeg` (default 30) stay corners.\n */\nexport function fitFreehandPath(\n\tpoints: readonly Point[],\n\toptions: Omit<FitPathDataOptions, 'closed'> = {}\n): string {\n\treturn fitPathData(points, { tolerance: 1, ...options, closed: false });\n}\n","/**\n * Shape geometry: shape descriptions (rect, rounded rect, ellipse, regular polygon, star and\n * SVG path data) to polygons, SVG path parsing / flattening, and polygons back to path data.\n */\nimport type { Point } from './types.js';\nimport type { MultiPolygon, PolygonFillRule, PolygonRing } from './polygonBoolean.js';\nimport { fitPathData } from './curveFit.js';\n\n// ---------------------------------------------------------------------------\n// Shape descriptions\n// ---------------------------------------------------------------------------\n\n/** Paint shared by every shape kind. Colours are any CSS colour. */\nexport interface ShapePaint {\n\tfill?: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\topacity?: number;\n}\n\ninterface ShapeBase extends ShapePaint {\n\t/** Clockwise rotation in degrees (on a y-down canvas) about the centre of the node box. */\n\trotation?: number;\n}\n\n/** Rectangle filling the node box, optionally with rounded corners. */\nexport interface RectShape extends ShapeBase {\n\tkind: 'rect';\n\t/** One radius, or `[topLeft, topRight, bottomRight, bottomLeft]`. Clamped to fit. */\n\tcornerRadius?: number | readonly [number, number, number, number];\n}\n\n/** Ellipse (circle when the node is square) inscribed in the node box. */\nexport interface EllipseShape extends ShapeBase {\n\tkind: 'ellipse';\n}\n\n/** Regular polygon, stretched so its bounding box fills the node box. */\nexport interface RegularPolygonShape extends ShapeBase {\n\tkind: 'polygon';\n\t/** Number of sides (at least 3, default 6). The first vertex points up. */\n\tsides?: number;\n}\n\n/** Star, stretched so its bounding box fills the node box. */\nexport interface StarShape extends ShapeBase {\n\tkind: 'star';\n\t/** Number of points (at least 3, default 5). The first point points up. */\n\tpoints?: number;\n\t/** Inner radius as a fraction of the outer radius (default 0.5). */\n\tinnerRadius?: number;\n}\n\n/** Arbitrary SVG path data. */\nexport interface PathShape extends ShapeBase {\n\tkind: 'path';\n\td: string;\n\t/**\n\t * `[minX, minY, width, height]` of the path's coordinate space, scaled to the node box.\n\t * Without it the path coordinates are node-local pixels.\n\t */\n\tviewBox?: readonly [number, number, number, number];\n\t/** Fill rule (default `nonzero`, as in SVG). */\n\tfillRule?: PolygonFillRule;\n}\n\n/**\n * Shape description stored on a node as `data.shape`. Geometry is relative to the node\n * box (`0,0` to `width,height`), so resizing the node resizes the shape.\n */\nexport type ShapeDescription = RectShape | EllipseShape | RegularPolygonShape | StarShape | PathShape;\n\nexport type ShapeKind = ShapeDescription['kind'];\n\nexport const SHAPE_KINDS: readonly ShapeKind[] = ['rect', 'ellipse', 'polygon', 'star', 'path'];\n\n/** Whether a value is a supported shape description. */\nexport function isShapeDescription(value: unknown): value is ShapeDescription {\n\tif (!value || typeof value !== 'object') return false;\n\tconst kind = (value as { kind?: unknown }).kind;\n\tif (!SHAPE_KINDS.includes(kind as ShapeKind)) return false;\n\treturn kind !== 'path' || typeof (value as PathShape).d === 'string';\n}\n\nexport interface ShapeSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface FlattenOptions {\n\t/** Maximum distance between a curve and its polyline, in output units (default 0.25). */\n\ttolerance?: number;\n\t/** Upper bound on the segments one curve is split into (default 512). */\n\tmaxSegments?: number;\n}\n\n// ---------------------------------------------------------------------------\n// SVG path parsing\n// ---------------------------------------------------------------------------\n\n/** Normalised absolute path command produced by `parseSvgPath`. */\nexport type PathCommand =\n\t| { type: 'M'; x: number; y: number }\n\t| { type: 'L'; x: number; y: number }\n\t| { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number }\n\t| { type: 'Q'; x1: number; y1: number; x: number; y: number }\n\t| {\n\t\t\ttype: 'A';\n\t\t\trx: number;\n\t\t\try: number;\n\t\t\trotation: number;\n\t\t\tlargeArc: boolean;\n\t\t\tsweep: boolean;\n\t\t\tx: number;\n\t\t\ty: number;\n\t }\n\t| { type: 'Z' };\n\nconst PARAM_COUNTS: Record<string, number> = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };\n\n/** Character scanner for path data (handles `1.5.5`, `1e-3`, `-1-2` and compact arc flags). */\nclass PathScanner {\n\tprivate pos = 0;\n\tconstructor(private readonly text: string) {}\n\n\tprivate skip() {\n\t\twhile (this.pos < this.text.length && /[\\s,]/.test(this.text[this.pos])) this.pos++;\n\t}\n\n\t/** The next command letter, or `null` when a number (or the end) follows. */\n\tcommand(): string | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {\n\t\t\tthis.pos++;\n\t\t\treturn ch;\n\t\t}\n\t\treturn null;\n\t}\n\n\tdone(): boolean {\n\t\tthis.skip();\n\t\treturn this.pos >= this.text.length;\n\t}\n\n\tnumber(): number | null {\n\t\tthis.skip();\n\t\tconst match = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));\n\t\tif (!match) return null;\n\t\tthis.pos += match[0].length;\n\t\tconst value = Number(match[0]);\n\t\treturn Number.isFinite(value) ? value : null;\n\t}\n\n\tflag(): number | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch === '0' || ch === '1') {\n\t\t\tthis.pos++;\n\t\t\treturn ch === '1' ? 1 : 0;\n\t\t}\n\t\treturn null;\n\t}\n}\n\n/**\n * Parses SVG path data into absolute `M`, `L`, `C`, `Q`, `A` and `Z` commands. Relative\n * commands, `H` / `V`, smooth curves (`S` / `T`) and implicit repeated commands are\n * normalised. Parsing stops at the first malformed command (as browsers do).\n */\nexport function parseSvgPath(d: string): PathCommand[] {\n\tconst scanner = new PathScanner(d ?? '');\n\tconst commands: PathCommand[] = [];\n\tlet cx = 0;\n\tlet cy = 0;\n\tlet startX = 0;\n\tlet startY = 0;\n\tlet lastControl: { x: number; y: number; type: 'C' | 'Q' } | null = null;\n\tlet command = '';\n\n\twhile (!scanner.done()) {\n\t\tconst letter = scanner.command();\n\t\tif (letter) {\n\t\t\tcommand = letter;\n\t\t\tif (command === 'z' || command === 'Z') {\n\t\t\t\tcommands.push({ type: 'Z' });\n\t\t\t\tcx = startX;\n\t\t\t\tcy = startY;\n\t\t\t\tlastControl = null;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if (!command || command === 'z' || command === 'Z') {\n\t\t\tbreak;\n\t\t}\n\t\tconst lower = command.toLowerCase();\n\t\tconst count = PARAM_COUNTS[lower];\n\t\tconst args: number[] = [];\n\t\tfor (let k = 0; k < count; k++) {\n\t\t\tconst value = lower === 'a' && (k === 3 || k === 4) ? scanner.flag() : scanner.number();\n\t\t\tif (value === null) return commands;\n\t\t\targs.push(value);\n\t\t}\n\t\tconst rel = command !== command.toUpperCase();\n\t\tconst ox = rel ? cx : 0;\n\t\tconst oy = rel ? cy : 0;\n\t\tswitch (lower) {\n\t\t\tcase 'm': {\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tstartX = cx;\n\t\t\t\tstartY = cy;\n\t\t\t\tcommands.push({ type: 'M', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\t// Extra coordinate pairs after a moveto are implicit linetos.\n\t\t\t\tcommand = rel ? 'l' : 'L';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'l':\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tcx = args[0] + (rel ? cx : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tcy = args[0] + (rel ? cy : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'c': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1: args[0] + ox,\n\t\t\t\t\ty1: args[1] + oy,\n\t\t\t\t\tx2: args[2] + ox,\n\t\t\t\t\ty2: args[3] + oy,\n\t\t\t\t\tx: args[4] + ox,\n\t\t\t\t\ty: args[5] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 's': {\n\t\t\t\tconst x1: number = lastControl?.type === 'C' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'C' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1,\n\t\t\t\t\ty1,\n\t\t\t\t\tx2: args[0] + ox,\n\t\t\t\t\ty2: args[1] + oy,\n\t\t\t\t\tx: args[2] + ox,\n\t\t\t\t\ty: args[3] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'q': {\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x1, y: c.y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 't': {\n\t\t\t\tconst x1: number = lastControl?.type === 'Q' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'Q' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1, y1, x: args[0] + ox, y: args[1] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: x1, y: y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'a': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'A',\n\t\t\t\t\trx: Math.abs(args[0]),\n\t\t\t\t\try: Math.abs(args[1]),\n\t\t\t\t\trotation: args[2],\n\t\t\t\t\tlargeArc: args[3] !== 0,\n\t\t\t\t\tsweep: args[4] !== 0,\n\t\t\t\t\tx: args[5] + ox,\n\t\t\t\t\ty: args[6] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = null;\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn commands;\n}\n\n// ---------------------------------------------------------------------------\n// Curve flattening\n// ---------------------------------------------------------------------------\n\nfunction segmentCount(curvature: number, tolerance: number, max: number): number {\n\tif (!(curvature > 0)) return 1;\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));\n}\n\nfunction flattenCubic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx2: number,\n\ty2: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\t// Uniform subdivision error <= max|B''| / (8 n²) with |B''| <= 6 * max second difference.\n\tconst dd = Math.max(\n\t\tMath.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),\n\t\tMath.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)\n\t);\n\tconst n = segmentCount((6 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tconst a = mt * mt * mt;\n\t\tconst b = 3 * mt * mt * t;\n\t\tconst c = 3 * mt * t * t;\n\t\tconst e = t * t * t;\n\t\tout.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);\n\t}\n}\n\nfunction flattenQuadratic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);\n\tconst n = segmentCount((2 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tout.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);\n\t}\n}\n\nfunction arcSteps(radius: number, sweep: number, tolerance: number, max: number): number {\n\tif (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));\n\tconst step = 2 * Math.acos(1 - tolerance / radius);\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));\n}\n\n/** Flattens an SVG elliptical arc (endpoint parameterisation, SVG 1.1 F.6.5). */\nfunction flattenArc(\n\tout: Point[],\n\tp0: Point,\n\tcmd: Extract<PathCommand, { type: 'A' }>,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst [x0, y0] = p0;\n\tlet { rx, ry } = cmd;\n\tconst { x, y } = cmd;\n\tif (x0 === x && y0 === y) return;\n\tif (rx === 0 || ry === 0) {\n\t\tout.push([x, y]);\n\t\treturn;\n\t}\n\tconst phi = (cmd.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst dx = (x0 - x) / 2;\n\tconst dy = (y0 - y) / 2;\n\tconst x1p = cos * dx + sin * dy;\n\tconst y1p = -sin * dx + cos * dy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\tconst s = Math.sqrt(lambda);\n\t\trx *= s;\n\t\try *= s;\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tlet coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));\n\tif (cmd.largeArc === cmd.sweep) coef = -coef;\n\tconst cxp = (coef * rx * y1p) / ry;\n\tconst cyp = (-coef * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x0 + x) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y0 + y) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => {\n\t\tconst a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\t\treturn a;\n\t};\n\tconst theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;\n\telse if (cmd.sweep && delta < 0) delta += 2 * Math.PI;\n\tconst n = arcSteps(Math.max(rx, ry), delta, tolerance, max);\n\tfor (let k = 1; k < n; k++) {\n\t\tconst t = theta1 + (delta * k) / n;\n\t\tconst ex = rx * Math.cos(t);\n\t\tconst ey = ry * Math.sin(t);\n\t\tout.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);\n\t}\n\tout.push([x, y]);\n}\n\n/**\n * Flattens SVG path data into closed rings (one per subpath; open subpaths are closed the\n * way SVG fills them). Curves are approximated within `tolerance`.\n */\nexport function flattenSvgPath(d: string | PathCommand[], options: FlattenOptions = {}): PolygonRing[] {\n\tconst tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;\n\tconst max = options.maxSegments ?? 512;\n\tconst commands = typeof d === 'string' ? parseSvgPath(d) : d;\n\tconst rings: PolygonRing[] = [];\n\tlet current: Point[] = [];\n\tlet start: Point = [0, 0];\n\tconst flush = () => {\n\t\tif (current.length >= 3) rings.push(current);\n\t\tcurrent = [];\n\t};\n\tconst last = (): Point => current[current.length - 1] ?? start;\n\tfor (const cmd of commands) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tflush();\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tcurrent = [start];\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tcurrent.push([cmd.x, cmd.y]);\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'A':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenArc(current, last(), cmd, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\tflush();\n\t\t\t\t// A command after Z without a moveto starts at the subpath start.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tflush();\n\treturn rings.map(dedupeRing).filter((r) => r.length >= 3);\n}\n\nfunction dedupeRing(ring: Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tconst prev = out[out.length - 1];\n\t\tif (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);\n\t}\n\twhile (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Shapes to polygons\n// ---------------------------------------------------------------------------\n\nfunction cornerRadii(shape: RectShape, width: number, height: number): [number, number, number, number] {\n\tconst raw = shape.cornerRadius ?? 0;\n\tlet radii = (typeof raw === 'number' ? [raw, raw, raw, raw] : [...raw]).map((r) =>\n\t\tNumber.isFinite(r) && r > 0 ? r : 0\n\t) as [number, number, number, number];\n\t// Scale all radii down together when adjacent corners would overlap (CSS rule).\n\tconst factor = Math.min(\n\t\t1,\n\t\twidth / Math.max(radii[0] + radii[1], 1e-12),\n\t\theight / Math.max(radii[1] + radii[2], 1e-12),\n\t\twidth / Math.max(radii[2] + radii[3], 1e-12),\n\t\theight / Math.max(radii[3] + radii[0], 1e-12)\n\t);\n\tif (factor < 1) radii = radii.map((r) => r * factor) as [number, number, number, number];\n\treturn radii;\n}\n\n/** Unit vertices of a regular polygon / star, stretched to fill the box. */\nfunction polygonVertices(count: number, inner: number | null, width: number, height: number): Point[] {\n\tconst raw: Point[] = [];\n\tconst total = inner === null ? count : count * 2;\n\tfor (let k = 0; k < total; k++) {\n\t\tconst angle = -Math.PI / 2 + (k * 2 * Math.PI) / total;\n\t\tconst r = inner !== null && k % 2 === 1 ? inner : 1;\n\t\traw.push([r * Math.cos(angle), r * Math.sin(angle)]);\n\t}\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of raw) {\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x);\n\t\tmaxY = Math.max(maxY, y);\n\t}\n\tconst sx = width / (maxX - minX || 1);\n\tconst sy = height / (maxY - minY || 1);\n\treturn raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);\n}\n\nfunction rotationTransform(shape: ShapeDescription, size: ShapeSize): ((p: Point) => Point) | null {\n\tconst deg = shape.rotation ?? 0;\n\tif (!deg || !Number.isFinite(deg)) return null;\n\tconst rad = (deg * Math.PI) / 180;\n\tconst cos = Math.cos(rad);\n\tconst sin = Math.sin(rad);\n\tconst cx = size.width / 2;\n\tconst cy = size.height / 2;\n\treturn ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];\n}\n\n/**\n * SVG path data for a shape in node-local pixels (exact curves: arcs for rounded corners\n * and ellipses), plus the fill rule and a `transform` for rotation / path view boxes.\n * Use it to render a shape; use `shapeToPolygon` for geometry.\n */\nexport function getShapeSvg(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { d: string; transform?: string; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tconst f = (n: number) => formatNumber(n, 3);\n\tconst transforms: string[] = [];\n\tif (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);\n\tlet d = '';\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tswitch (shape.kind) {\n\t\tcase 'rect': {\n\t\t\tconst [tl, tr, br, bl] = cornerRadii(shape, w, h);\n\t\t\tif (tl + tr + br + bl === 0) {\n\t\t\t\td = `M0 0H${f(w)}V${f(h)}H0Z`;\n\t\t\t} else {\n\t\t\t\tconst arc = (r: number, x: number, y: number) => (r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : '');\n\t\t\t\td =\n\t\t\t\t\t`M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}` +\n\t\t\t\t\t`H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'ellipse': {\n\t\t\tconst rx = w / 2;\n\t\t\tconst ry = h / 2;\n\t\t\td = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'polygon':\n\t\tcase 'star': {\n\t\t\tconst points =\n\t\t\t\tshape.kind === 'polygon'\n\t\t\t\t\t? polygonVertices(Math.max(3, Math.floor(shape.sides ?? 6)), null, w, h)\n\t\t\t\t\t: polygonVertices(Math.max(3, Math.floor(shape.points ?? 5)), clampRatio(shape.innerRadius), w, h);\n\t\t\td = ringsToPath([points], f);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'path': {\n\t\t\td = shape.d;\n\t\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\t\tconst vb = validViewBox(shape.viewBox);\n\t\t\tif (vb) {\n\t\t\t\ttransforms.push(\n\t\t\t\t\t`scale(${f(w / vb[2])} ${f(h / vb[3])})`,\n\t\t\t\t\t`translate(${f(-vb[0])} ${f(-vb[1])})`\n\t\t\t\t);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn transforms.length > 0 ? { d, transform: transforms.join(' '), fillRule } : { d, fillRule };\n}\n\n/** Everything a renderer needs to draw a shape as one SVG `<path>`. */\nexport interface ShapeRenderData {\n\t/** SVG `viewBox` (`0 0 width height`): node-local pixels. */\n\tviewBox: string;\n\twidth: number;\n\theight: number;\n\td: string;\n\ttransform?: string;\n\tfillRule: PolygonFillRule;\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: number;\n\topacity?: number;\n}\n\n/**\n * Resolves the SVG drawing of a shape for a node size. Without a size, a path's view box\n * size (or 100x100) is used. Paint defaults: `fill: currentColor`, `stroke: none`,\n * `strokeWidth: 1`.\n */\nexport function getShapeRenderData(shape: ShapeDescription, size: Partial<ShapeSize> = {}): ShapeRenderData {\n\tconst vb = shape.kind === 'path' ? validViewBox(shape.viewBox) : null;\n\tconst width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;\n\tconst height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;\n\tconst svg = getShapeSvg(shape, { width, height });\n\tconst data: ShapeRenderData = {\n\t\tviewBox: `0 0 ${formatNumber(width, 3)} ${formatNumber(height, 3)}`,\n\t\twidth,\n\t\theight,\n\t\td: svg.d,\n\t\tfillRule: svg.fillRule,\n\t\tfill: shape.fill ?? 'currentColor',\n\t\tstroke: shape.stroke ?? 'none',\n\t\tstrokeWidth: shape.strokeWidth ?? 1\n\t};\n\tif (svg.transform) data.transform = svg.transform;\n\tif (shape.opacity !== undefined) data.opacity = shape.opacity;\n\treturn data;\n}\n\nfunction clampRatio(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) return 0.5;\n\treturn Math.min(1, Math.max(0.01, value));\n}\n\nfunction validViewBox(vb: PathShape['viewBox']): readonly [number, number, number, number] | null {\n\tif (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;\n\treturn vb;\n}\n\n/**\n * Converts a shape to rings in node-local pixels (rotation applied), flattening curves at\n * `tolerance`. Returns the rings with the fill rule they must be read with.\n */\nexport function shapeToPolygon(\n\tshape: ShapeDescription,\n\tsize: ShapeSize,\n\toptions: FlattenOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { rings: [], fillRule: 'nonzero' };\n\tlet rings: PolygonRing[];\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tif (shape.kind === 'path') {\n\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\tconst vb = validViewBox(shape.viewBox);\n\t\tif (vb) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\t// Flatten in path units with the tolerance scaled to stay within `tolerance` px.\n\t\t\tconst tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);\n\t\t\trings = flattenSvgPath(shape.d, { ...options, tolerance }).map((ring) =>\n\t\t\t\tring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy] as Point)\n\t\t\t);\n\t\t} else {\n\t\t\trings = flattenSvgPath(shape.d, options);\n\t\t}\n\t} else {\n\t\tconst { d } = getShapeSvg({ ...shape, rotation: 0 }, size);\n\t\trings = flattenSvgPath(d, options);\n\t}\n\tconst rotate = rotationTransform(shape, size);\n\tif (rotate) rings = rings.map((ring) => ring.map(rotate));\n\treturn { rings, fillRule };\n}\n\n// ---------------------------------------------------------------------------\n// Polygons to path data\n// ---------------------------------------------------------------------------\n\n/** Formats a number with at most `precision` decimals, without trailing zeros. */\nexport function formatNumber(value: number, precision = 3): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\nfunction ringsToPath(rings: readonly (readonly Point[])[], f: (n: number) => string): string {\n\tlet d = '';\n\tfor (const ring of rings) {\n\t\tif (ring.length < 2) continue;\n\t\td += `M${f(ring[0][0])} ${f(ring[0][1])}`;\n\t\tfor (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;\n\t\td += 'Z';\n\t}\n\treturn d;\n}\n\n/** Perpendicular distance from `p` to the segment `a`–`b`. */\nfunction segmentDistance(p: Point, a: Point, b: Point): number {\n\tconst dx = b[0] - a[0];\n\tconst dy = b[1] - a[1];\n\tconst len2 = dx * dx + dy * dy;\n\tif (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);\n\tconst t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));\n\treturn Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));\n}\n\nfunction rdp(points: Point[], tolerance: number): Point[] {\n\tif (points.length <= 2) return points;\n\tconst keep = new Uint8Array(points.length);\n\tkeep[0] = 1;\n\tkeep[points.length - 1] = 1;\n\tconst stack: [number, number][] = [[0, points.length - 1]];\n\twhile (stack.length) {\n\t\tconst [s, e] = stack.pop()!;\n\t\tlet maxDist = -1;\n\t\tlet index = -1;\n\t\tfor (let i = s + 1; i < e; i++) {\n\t\t\tconst dist = segmentDistance(points[i], points[s], points[e]);\n\t\t\tif (dist > maxDist) {\n\t\t\t\tmaxDist = dist;\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t}\n\t\tif (index !== -1 && maxDist > tolerance) {\n\t\t\tkeep[index] = 1;\n\t\t\tstack.push([s, index], [index, e]);\n\t\t}\n\t}\n\treturn points.filter((_, i) => keep[i]);\n}\n\n/**\n * Simplifies a closed ring with Ramer–Douglas–Peucker at `tolerance`. Returns an empty\n * ring when fewer than three points survive.\n */\nexport function simplifyRing(ring: readonly Point[], tolerance: number): Point[] {\n\tif (ring.length <= 3 || !(tolerance > 0)) return [...ring];\n\t// Anchor on the point farthest from the first one so the closing seam is not lost.\n\tlet far = 0;\n\tlet farDist = -1;\n\tfor (let i = 1; i < ring.length; i++) {\n\t\tconst dist = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);\n\t\tif (dist > farDist) {\n\t\t\tfarDist = dist;\n\t\t\tfar = i;\n\t\t}\n\t}\n\tconst first = rdp(ring.slice(0, far + 1) as Point[], tolerance);\n\tconst second = rdp([...ring.slice(far), ring[0]] as Point[], tolerance);\n\tconst out = [...first.slice(0, -1), ...second.slice(0, -1)];\n\treturn out.length >= 3 ? out : [];\n}\n\nexport interface PolygonToPathOptions {\n\t/** Decimal places in the output (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker tolerance; `0` (default) keeps every vertex. */\n\tsimplify?: number;\n\t/** Added to every point before formatting (e.g. to make coordinates node-local). */\n\toffset?: { x: number; y: number };\n\t/**\n\t * `'lines'` (default) writes every ring as `M`/`L`/`Z`. `'fit'` fits cubic Béziers to each\n\t * ring (`fitPathData`), so flattened curves come back as smooth `C` curves while sharp\n\t * corners (turning more than `cornerAngleDeg`) and straight edges stay as they are.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in path units for `curves: 'fit'` (default 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n}\n\n/**\n * Converts a multi-polygon (or a list of rings) to SVG path data using `M`/`L`/`Z`. Holes\n * keep their opposite orientation, so the path fills correctly with either fill rule.\n */\nexport function polygonToSvgPath(input: MultiPolygon | PolygonRing[], options: PolygonToPathOptions = {}): string {\n\tconst precision = options.precision ?? 3;\n\tconst f = (n: number) => formatNumber(n, precision);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst rings: Point[][] = [];\n\tconst fit = options.curves === 'fit';\n\tlet fitted = '';\n\tfor (const item of input as any[]) {\n\t\tconst list: Point[][] =\n\t\t\titem.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];\n\t\tfor (const ring of list) {\n\t\t\tlet r = ring;\n\t\t\tif (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);\n\t\t\tif (r.length < 3) continue;\n\t\t\tif (fit) {\n\t\t\t\tfitted += fitPathData(r, {\n\t\t\t\t\ttolerance: options.curveTolerance ?? 0.25,\n\t\t\t\t\tcornerAngleDeg: options.cornerAngleDeg,\n\t\t\t\t\tprecision,\n\t\t\t\t\toffset: { x: dx, y: dy }\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\trings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy] as Point) : r);\n\t\t}\n\t}\n\treturn fit ? fitted : ringsToPath(rings, f);\n}\n","/**\n * Bézier path model shared by the curve-preserving path operations: absolute `M` / `L` /\n * `Q` / `C` / `Z` commands, conversion from SVG path data (arcs become cubics), segment\n * math (evaluation, splitting, extrema, nearest point), exact areas, winding numbers and\n * arc-length utilities.\n */\nimport type { Point } from '../types.js';\nimport { formatNumber, parseSvgPath, type PathCommand } from '../shapeGeometry.js';\nimport type { PolygonFillRule } from '../polygonBoolean.js';\n\n// ---------------------------------------------------------------------------\n// Public path model\n// ---------------------------------------------------------------------------\n\n/** One absolute path command. Every subpath starts with `M`; `Z` closes it. */\nexport type BezierCommand =\n\t| { type: 'M'; x: number; y: number }\n\t| { type: 'L'; x: number; y: number }\n\t| { type: 'Q'; x1: number; y1: number; x: number; y: number }\n\t| { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number }\n\t| { type: 'Z' };\n\n/** A path as a list of absolute move / line / quadratic / cubic / close commands. */\nexport type BezierPath = BezierCommand[];\n\n/** Anything the path operations accept as a path: SVG path data, parsed commands or a `BezierPath`. */\nexport type BezierPathInput = string | readonly BezierCommand[] | readonly PathCommand[];\n\n/**\n * One segment as its control points: 2 points for a line, 3 for a quadratic and 4 for a\n * cubic Bézier.\n */\nexport type BezierSegment = readonly Point[];\n\n/** A subpath as a chain of segments (each starts where the previous one ends). */\nexport interface BezierContour {\n\tsegments: BezierSegment[];\n\tclosed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Vector helpers\n// ---------------------------------------------------------------------------\n\nexport const sub = (a: Point, b: Point): Point => [a[0] - b[0], a[1] - b[1]];\nexport const add = (a: Point, b: Point): Point => [a[0] + b[0], a[1] + b[1]];\nexport const scale = (a: Point, s: number): Point => [a[0] * s, a[1] * s];\nexport const dot = (a: Point, b: Point) => a[0] * b[0] + a[1] * b[1];\nexport const cross = (a: Point, b: Point) => a[0] * b[1] - a[1] * b[0];\nexport const dist = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]);\nexport const lerp = (a: Point, b: Point, t: number): Point => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\nexport const samePoint = (a: Point, b: Point) => a[0] === b[0] && a[1] === b[1];\n\nexport function unit(v: Point): Point | null {\n\tconst l = Math.hypot(v[0], v[1]);\n\treturn l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;\n}\n\n// ---------------------------------------------------------------------------\n// Segment math\n// ---------------------------------------------------------------------------\n\n/** Point at parameter `t` (de Casteljau, works for any degree). */\nexport function segmentPoint(seg: BezierSegment, t: number): Point {\n\tconst n = seg.length;\n\tif (n === 2) return lerp(seg[0], seg[1], t);\n\tconst mt = 1 - t;\n\tif (n === 3) {\n\t\tconst a = mt * mt;\n\t\tconst b = 2 * mt * t;\n\t\tconst c = t * t;\n\t\treturn [a * seg[0][0] + b * seg[1][0] + c * seg[2][0], a * seg[0][1] + b * seg[1][1] + c * seg[2][1]];\n\t}\n\tconst a = mt * mt * mt;\n\tconst b = 3 * mt * mt * t;\n\tconst c = 3 * mt * t * t;\n\tconst d = t * t * t;\n\treturn [\n\t\ta * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],\n\t\ta * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]\n\t];\n}\n\n/** First derivative at `t`. */\nexport function segmentDerivative(seg: BezierSegment, t: number): Point {\n\tconst n = seg.length;\n\tif (n === 2) return sub(seg[1], seg[0]);\n\tif (n === 3) {\n\t\tconst a = sub(seg[1], seg[0]);\n\t\tconst b = sub(seg[2], seg[1]);\n\t\treturn [2 * ((1 - t) * a[0] + t * b[0]), 2 * ((1 - t) * a[1] + t * b[1])];\n\t}\n\tconst a = sub(seg[1], seg[0]);\n\tconst b = sub(seg[2], seg[1]);\n\tconst c = sub(seg[3], seg[2]);\n\tconst mt = 1 - t;\n\treturn [\n\t\t3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),\n\t\t3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])\n\t];\n}\n\n/** Second derivative at `t`. */\nexport function segmentSecondDerivative(seg: BezierSegment, t: number): Point {\n\tconst n = seg.length;\n\tif (n === 2) return [0, 0];\n\tif (n === 3) return [2 * (seg[2][0] - 2 * seg[1][0] + seg[0][0]), 2 * (seg[2][1] - 2 * seg[1][1] + seg[0][1])];\n\tconst a: Point = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];\n\tconst b: Point = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];\n\treturn [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];\n}\n\n/**\n * Unit tangent at `t`. Where the derivative vanishes (a handle on its anchor) the direction\n * a hair further along is used, and the chord as the last resort.\n */\nexport function segmentTangent(seg: BezierSegment, t: number): Point {\n\tconst d = unit(segmentDerivative(seg, t));\n\tif (d) return d;\n\tconst h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);\n\tconst e = unit(segmentDerivative(seg, h));\n\tif (e) return e;\n\treturn unit(sub(seg[seg.length - 1], seg[0])) ?? [1, 0];\n}\n\n/** Signed curvature at `t` (positive when turning counter-clockwise in a y-up frame). */\nexport function segmentCurvature(seg: BezierSegment, t: number): number {\n\tif (seg.length === 2) return 0;\n\tlet d = segmentDerivative(seg, t);\n\tlet tt = t;\n\tif (Math.hypot(d[0], d[1]) < 1e-12) {\n\t\ttt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);\n\t\td = segmentDerivative(seg, tt);\n\t}\n\tconst dd = segmentSecondDerivative(seg, tt);\n\tconst len = Math.hypot(d[0], d[1]);\n\tif (!(len > 0)) return 0;\n\treturn cross(d, dd) / (len * len * len);\n}\n\n/** Splits a segment at `t` (de Casteljau). */\nexport function splitSegment(seg: BezierSegment, t: number): [Point[], Point[]] {\n\tconst n = seg.length;\n\tif (n === 2) {\n\t\tconst m = lerp(seg[0], seg[1], t);\n\t\treturn [\n\t\t\t[seg[0], m],\n\t\t\t[m, seg[1]]\n\t\t];\n\t}\n\tif (n === 3) {\n\t\tconst a = lerp(seg[0], seg[1], t);\n\t\tconst b = lerp(seg[1], seg[2], t);\n\t\tconst m = lerp(a, b, t);\n\t\treturn [\n\t\t\t[seg[0], a, m],\n\t\t\t[m, b, seg[2]]\n\t\t];\n\t}\n\tconst a = lerp(seg[0], seg[1], t);\n\tconst b = lerp(seg[1], seg[2], t);\n\tconst c = lerp(seg[2], seg[3], t);\n\tconst ab = lerp(a, b, t);\n\tconst bc = lerp(b, c, t);\n\tconst m = lerp(ab, bc, t);\n\treturn [\n\t\t[seg[0], a, ab, m],\n\t\t[m, bc, c, seg[3]]\n\t];\n}\n\n/** The part of a segment between parameters `t0` and `t1` (reversed when `t1 < t0`). */\nexport function subSegment(seg: BezierSegment, t0: number, t1: number): Point[] {\n\tif (t1 < t0) return subSegment(seg, t1, t0).reverse();\n\tif (t0 <= 0 && t1 >= 1) return [...seg];\n\tlet s: Point[] = [...seg];\n\tif (t1 < 1) s = splitSegment(s, t1)[0];\n\tif (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];\n\treturn s;\n}\n\n/** Whether every control point lies within `tol` of the chord (a line in disguise). */\nexport function isStraightSegment(seg: BezierSegment, tol: number): boolean {\n\tif (seg.length === 2) return true;\n\tconst a = seg[0];\n\tconst b = seg[seg.length - 1];\n\tconst chord = sub(b, a);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tfor (let i = 1; i < seg.length - 1; i++) {\n\t\tconst v = sub(seg[i], a);\n\t\tif (len === 0) {\n\t\t\tif (Math.hypot(v[0], v[1]) > tol) return false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Math.abs(cross(chord, v)) / len > tol) return false;\n\t\tconst along = dot(chord, v) / len;\n\t\tif (along < -tol || along > len + tol) return false;\n\t}\n\treturn true;\n}\n\n/** Real roots in the open interval (0, 1) of `a t² + b t + c`. */\nfunction quadraticRoots(a: number, b: number, c: number, out: number[]) {\n\tconst push = (t: number) => {\n\t\tif (t > 1e-12 && t < 1 - 1e-12) out.push(t);\n\t};\n\tconst scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));\n\tif (scaleAbs === 0) return;\n\tif (Math.abs(a) <= 1e-12 * scaleAbs) {\n\t\tif (b !== 0) push(-c / b);\n\t\treturn;\n\t}\n\tconst disc = b * b - 4 * a * c;\n\tif (disc < 0) return;\n\tconst sq = Math.sqrt(disc);\n\tconst q = -0.5 * (b + (b >= 0 ? sq : -sq));\n\tpush(q / a);\n\tif (q !== 0) push(c / q);\n}\n\n/** Parameters in (0, 1) where the segment's `axis` coordinate has a local extremum. */\nexport function segmentExtrema(seg: BezierSegment, axis: 0 | 1): number[] {\n\tconst out: number[] = [];\n\tif (seg.length === 3) {\n\t\tconst d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];\n\t\tif (d !== 0) {\n\t\t\tconst t = (seg[0][axis] - seg[1][axis]) / d;\n\t\t\tif (t > 1e-12 && t < 1 - 1e-12) out.push(t);\n\t\t}\n\t} else if (seg.length === 4) {\n\t\tconst p0 = seg[0][axis];\n\t\tconst p1 = seg[1][axis];\n\t\tconst p2 = seg[2][axis];\n\t\tconst p3 = seg[3][axis];\n\t\t// B'(t)/3 = a t² + b t + c\n\t\tconst a = -p0 + 3 * p1 - 3 * p2 + p3;\n\t\tconst b = 2 * (p0 - 2 * p1 + p2);\n\t\tconst c = p1 - p0;\n\t\tquadraticRoots(a, b, c, out);\n\t}\n\treturn out.sort((x, y) => x - y);\n}\n\n/** Exact axis-aligned bounds of a segment. */\nexport function segmentBounds(seg: BezierSegment): { minX: number; minY: number; maxX: number; maxY: number } {\n\tconst a = seg[0];\n\tconst b = seg[seg.length - 1];\n\tlet minX = Math.min(a[0], b[0]);\n\tlet maxX = Math.max(a[0], b[0]);\n\tlet minY = Math.min(a[1], b[1]);\n\tlet maxY = Math.max(a[1], b[1]);\n\tfor (const axis of [0, 1] as const) {\n\t\tfor (const t of segmentExtrema(seg, axis)) {\n\t\t\tconst p = segmentPoint(seg, t);\n\t\t\tif (axis === 0) {\n\t\t\t\tminX = Math.min(minX, p[0]);\n\t\t\t\tmaxX = Math.max(maxX, p[0]);\n\t\t\t} else {\n\t\t\t\tminY = Math.min(minY, p[1]);\n\t\t\t\tmaxY = Math.max(maxY, p[1]);\n\t\t\t}\n\t\t}\n\t}\n\treturn { minX, minY, maxX, maxY };\n}\n\n/** Parameters splitting the segment into pieces monotone in x and y. */\nexport function monotoneParams(seg: BezierSegment): number[] {\n\tif (seg.length === 2) return [];\n\tconst ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);\n\tconst out: number[] = [];\n\tfor (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);\n\treturn out;\n}\n\n/**\n * Signed area swept by the segment relative to the origin (½ ∫ x dy − y dx). Exact:\n * the integrand is a polynomial of degree at most 5, which 3-point Gauss–Legendre\n * integrates exactly.\n */\nexport function segmentAreaTerm(seg: BezierSegment): number {\n\tif (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;\n\tconst nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];\n\tconst weights = [5 / 9, 8 / 9, 5 / 9];\n\tlet sum = 0;\n\tfor (let i = 0; i < 3; i++) {\n\t\tconst t = (nodes[i] + 1) / 2;\n\t\tconst p = segmentPoint(seg, t);\n\t\tconst d = segmentDerivative(seg, t);\n\t\tsum += weights[i] * (p[0] * d[1] - p[1] * d[0]);\n\t}\n\t// ½ for the area, ½ for mapping [-1, 1] onto [0, 1].\n\treturn sum / 4;\n}\n\n// Gauss–Legendre nodes / weights (order 8) on [-1, 1].\nconst GL_X = [\n\t-0.9602898564975363, -0.7966664774136267, -0.525532409916329, -0.1834346424956498, 0.1834346424956498,\n\t0.525532409916329, 0.7966664774136267, 0.9602898564975363\n];\nconst GL_W = [\n\t0.1012285362903763, 0.2223810344533745, 0.3137066458778873, 0.362683783378362, 0.362683783378362,\n\t0.3137066458778873, 0.2223810344533745, 0.1012285362903763\n];\n\nfunction glLength(seg: BezierSegment, t0: number, t1: number): number {\n\tconst half = (t1 - t0) / 2;\n\tconst mid = (t0 + t1) / 2;\n\tlet sum = 0;\n\tfor (let i = 0; i < GL_X.length; i++) {\n\t\tconst d = segmentDerivative(seg, mid + half * GL_X[i]);\n\t\tsum += GL_W[i] * Math.hypot(d[0], d[1]);\n\t}\n\treturn sum * half;\n}\n\nfunction adaptiveLength(seg: BezierSegment, t0: number, t1: number, whole: number, tol: number, depth: number): number {\n\tconst m = (t0 + t1) / 2;\n\tconst left = glLength(seg, t0, m);\n\tconst right = glLength(seg, m, t1);\n\tif (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;\n\treturn adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);\n}\n\n/** Arc length of the segment between `t0` and `t1` (default the whole segment). */\nexport function segmentLength(seg: BezierSegment, t0 = 0, t1 = 1, tolerance = 1e-9): number {\n\tif (t1 < t0) return segmentLength(seg, t1, t0, tolerance);\n\tif (seg.length === 2) return dist(seg[0], seg[1]) * (t1 - t0);\n\tconst whole = glLength(seg, t0, t1);\n\treturn adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);\n}\n\n/** Parameter at arc length `length` from the segment start (clamped to [0, 1]). */\nexport function segmentParamAtLength(seg: BezierSegment, length: number, total = segmentLength(seg)): number {\n\tif (!(total > 0) || length <= 0) return 0;\n\tif (length >= total) return 1;\n\tif (seg.length === 2) return length / total;\n\tlet lo = 0;\n\tlet hi = 1;\n\tlet t = length / total;\n\tfor (let i = 0; i < 40; i++) {\n\t\tconst err = segmentLength(seg, 0, t) - length;\n\t\tif (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;\n\t\tif (err > 0) hi = t;\n\t\telse lo = t;\n\t\tconst d = segmentDerivative(seg, t);\n\t\tconst speed = Math.hypot(d[0], d[1]);\n\t\tlet next = speed > 0 ? t - err / speed : (lo + hi) / 2;\n\t\tif (!(next > lo && next < hi)) next = (lo + hi) / 2;\n\t\tt = next;\n\t}\n\treturn t;\n}\n\n/**\n * Nearest point of the segment to `p`: the parameter and the distance. Coarse sampling\n * followed by Newton refinement around the best samples.\n */\nexport function segmentNearest(seg: BezierSegment, p: Point): { t: number; distance: number } {\n\tif (seg.length === 2) {\n\t\tconst d = sub(seg[1], seg[0]);\n\t\tconst len2 = dot(d, d);\n\t\tconst t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot(sub(p, seg[0]), d) / len2));\n\t\treturn { t, distance: dist(p, segmentPoint(seg, t)) };\n\t}\n\tconst samples = 16;\n\tlet best = { t: 0, distance: dist(p, seg[0]) };\n\tconst candidates: number[] = [];\n\tlet prev = Infinity;\n\tlet prevT = 0;\n\tlet descending = false;\n\tfor (let i = 0; i <= samples; i++) {\n\t\tconst t = i / samples;\n\t\tconst d = dist(p, segmentPoint(seg, t));\n\t\tif (d < best.distance) best = { t, distance: d };\n\t\tif (i > 0 && d > prev && descending) candidates.push(prevT);\n\t\tdescending = d < prev;\n\t\tprev = d;\n\t\tprevT = t;\n\t}\n\tcandidates.push(best.t, 1);\n\tfor (const start of candidates) {\n\t\tlet t = start;\n\t\tfor (let k = 0; k < 12; k++) {\n\t\t\tconst q = segmentPoint(seg, t);\n\t\t\tconst d1 = segmentDerivative(seg, t);\n\t\t\tconst d2 = segmentSecondDerivative(seg, t);\n\t\t\tconst diff = sub(q, p);\n\t\t\tconst f = dot(diff, d1);\n\t\t\tconst fp = dot(d1, d1) + dot(diff, d2);\n\t\t\tif (!(Math.abs(fp) > 1e-300)) break;\n\t\t\tconst next = Math.max(0, Math.min(1, t - f / fp));\n\t\t\tif (Math.abs(next - t) < 1e-14) {\n\t\t\t\tt = next;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tt = next;\n\t\t}\n\t\tconst d = dist(p, segmentPoint(seg, t));\n\t\tif (d < best.distance) best = { t, distance: d };\n\t}\n\treturn best;\n}\n\n// ---------------------------------------------------------------------------\n// Arcs\n// ---------------------------------------------------------------------------\n\n/** Converts an SVG elliptical arc (SVG 1.1 F.6.5) to cubic segments, at most 90° each. */\nexport function arcToCubics(\n\tfrom: Point,\n\tcmd: { rx: number; ry: number; rotation: number; largeArc: boolean; sweep: boolean; x: number; y: number }\n): Point[][] {\n\tconst [x0, y0] = from;\n\tconst { x, y } = cmd;\n\tif (x0 === x && y0 === y) return [];\n\tlet rx = Math.abs(cmd.rx);\n\tlet ry = Math.abs(cmd.ry);\n\tif (rx === 0 || ry === 0) return [[from, [x, y]]];\n\tconst phi = (cmd.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst dx = (x0 - x) / 2;\n\tconst dy = (y0 - y) / 2;\n\tconst x1p = cos * dx + sin * dy;\n\tconst y1p = -sin * dx + cos * dy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\tconst s = Math.sqrt(lambda);\n\t\trx *= s;\n\t\try *= s;\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tlet coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));\n\tif (cmd.largeArc === cmd.sweep) coef = -coef;\n\tconst cxp = (coef * rx * y1p) / ry;\n\tconst cyp = (-coef * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x0 + x) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y0 + y) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\tconst theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;\n\telse if (cmd.sweep && delta < 0) delta += 2 * Math.PI;\n\tconst n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));\n\tconst step = delta / n;\n\tconst k = (4 / 3) * Math.tan(step / 4);\n\tconst map = (ux: number, uy: number): Point => {\n\t\tconst ex = rx * ux;\n\t\tconst ey = ry * uy;\n\t\treturn [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];\n\t};\n\tconst out: Point[][] = [];\n\tlet prev: Point = from;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst a0 = theta1 + step * i;\n\t\tconst a1 = a0 + step;\n\t\tconst c0 = Math.cos(a0);\n\t\tconst s0 = Math.sin(a0);\n\t\tconst c1 = Math.cos(a1);\n\t\tconst s1 = Math.sin(a1);\n\t\tconst end: Point = i === n - 1 ? [x, y] : map(c1, s1);\n\t\tout.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);\n\t\tprev = end;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Conversions\n// ---------------------------------------------------------------------------\n\nfunction isBezierCommandList(input: BezierPathInput): input is readonly BezierCommand[] {\n\treturn Array.isArray(input) && !input.some((c: any) => c && c.type === 'A');\n}\n\n/**\n * Converts SVG path data or parsed commands to a `BezierPath` (absolute `M` / `L` / `Q` /\n * `C` / `Z`); elliptical arcs become cubic Béziers (at most 90° each).\n */\nexport function toBezierPath(input: BezierPathInput): BezierPath {\n\tconst commands: readonly (PathCommand | BezierCommand)[] =\n\t\ttypeof input === 'string' ? parseSvgPath(input) : isBezierCommandList(input) ? input : (input as readonly PathCommand[]);\n\tconst out: BezierPath = [];\n\tlet current: Point = [0, 0];\n\tlet start: Point = [0, 0];\n\tlet open = false;\n\tconst ensureMove = () => {\n\t\tif (!open) {\n\t\t\tout.push({ type: 'M', x: start[0], y: start[1] });\n\t\t\tcurrent = start;\n\t\t\topen = true;\n\t\t}\n\t};\n\tfor (const cmd of commands) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tcurrent = start;\n\t\t\t\tout.push({ type: 'M', x: cmd.x, y: cmd.y });\n\t\t\t\topen = true;\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tensureMove();\n\t\t\t\tout.push({ type: 'L', x: cmd.x, y: cmd.y });\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tensureMove();\n\t\t\t\tout.push({ type: 'Q', x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tensureMove();\n\t\t\t\tout.push({ type: 'C', x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'A': {\n\t\t\t\tensureMove();\n\t\t\t\tfor (const c of arcToCubics(current, cmd)) {\n\t\t\t\t\tif (c.length === 2) out.push({ type: 'L', x: c[1][0], y: c[1][1] });\n\t\t\t\t\telse out.push({ type: 'C', x1: c[1][0], y1: c[1][1], x2: c[2][0], y2: c[2][1], x: c[3][0], y: c[3][1] });\n\t\t\t\t}\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Z':\n\t\t\t\tif (open) out.push({ type: 'Z' });\n\t\t\t\topen = false;\n\t\t\t\tcurrent = start;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Splits a path into contours of segments. Zero-length segments are dropped. */\nexport function pathToContours(input: BezierPathInput): BezierContour[] {\n\tconst path = toBezierPath(input);\n\tconst contours: BezierContour[] = [];\n\tlet current: BezierContour | null = null;\n\tlet pen: Point = [0, 0];\n\tlet start: Point = [0, 0];\n\tconst push = (seg: Point[]) => {\n\t\tif (!current) {\n\t\t\tcurrent = { segments: [], closed: false };\n\t\t\tcontours.push(current);\n\t\t}\n\t\tif (seg.every((p) => samePoint(p, seg[0]))) return;\n\t\tcurrent.segments.push(seg);\n\t};\n\tfor (const cmd of path) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tpen = start;\n\t\t\t\tcurrent = { segments: [], closed: false };\n\t\t\t\tcontours.push(current);\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tpush([pen, [cmd.x, cmd.y]]);\n\t\t\t\tpen = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tpush([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);\n\t\t\t\tpen = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tpush([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);\n\t\t\t\tpen = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\tif (current) (current as BezierContour).closed = true;\n\t\t\t\tcurrent = null;\n\t\t\t\tpen = start;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn contours;\n}\n\n/** Builds a path from contours (a closed contour's closing line is left to `Z`). */\nexport function contoursToPath(contours: readonly BezierContour[]): BezierPath {\n\tconst out: BezierPath = [];\n\tfor (const contour of contours) {\n\t\tconst segs = contour.segments;\n\t\tif (segs.length === 0) continue;\n\t\tout.push({ type: 'M', x: segs[0][0][0], y: segs[0][0][1] });\n\t\tlet count = segs.length;\n\t\tif (contour.closed && count > 1) {\n\t\t\tconst last = segs[count - 1];\n\t\t\tif (last.length === 2 && samePoint(last[1], segs[0][0])) count--;\n\t\t}\n\t\tfor (let i = 0; i < count; i++) {\n\t\t\tconst s = segs[i];\n\t\t\tconst end = s[s.length - 1];\n\t\t\tif (s.length === 2) out.push({ type: 'L', x: end[0], y: end[1] });\n\t\t\telse if (s.length === 3) out.push({ type: 'Q', x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });\n\t\t\telse out.push({ type: 'C', x1: s[1][0], y1: s[1][1], x2: s[2][0], y2: s[2][1], x: end[0], y: end[1] });\n\t\t}\n\t\tif (contour.closed) out.push({ type: 'Z' });\n\t}\n\treturn out;\n}\n\nexport interface BezierPathToSvgOptions {\n\t/** Decimal places (default 3). */\n\tprecision?: number;\n\t/** Added to every point before formatting. */\n\toffset?: { x: number; y: number };\n}\n\n/** Writes a path as compact SVG path data. */\nexport function bezierPathToSvg(input: BezierPathInput, options: BezierPathToSvgOptions = {}): string {\n\tconst path = toBezierPath(input);\n\tconst precision = options.precision ?? 3;\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst fx = (n: number) => formatNumber(n + dx, precision);\n\tconst fy = (n: number) => formatNumber(n + dy, precision);\n\tlet d = '';\n\tfor (const c of path) {\n\t\tswitch (c.type) {\n\t\t\tcase 'M':\n\t\t\t\td += `M${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\td += `L${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\td += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\td += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\td += 'Z';\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn d;\n}\n\n/** 2D affine matrix `[a, b, c, d, e, f]` mapping `(x, y)` to `(a x + c y + e, b x + d y + f)`. */\nexport type AffineMatrix = readonly [number, number, number, number, number, number];\n\n/** Applies an affine transform to every point (exact for Bézier curves). */\nexport function transformBezierPath(input: BezierPathInput, m: AffineMatrix): BezierPath {\n\tconst [a, b, c, d, e, f] = m;\n\tconst X = (x: number, y: number) => a * x + c * y + e;\n\tconst Y = (x: number, y: number) => b * x + d * y + f;\n\treturn toBezierPath(input).map((cmd): BezierCommand => {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\tcase 'L':\n\t\t\t\treturn { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };\n\t\t\tcase 'Q':\n\t\t\t\treturn { type: 'Q', x1: X(cmd.x1, cmd.y1), y1: Y(cmd.x1, cmd.y1), x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };\n\t\t\tcase 'C':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1: X(cmd.x1, cmd.y1),\n\t\t\t\t\ty1: Y(cmd.x1, cmd.y1),\n\t\t\t\t\tx2: X(cmd.x2, cmd.y2),\n\t\t\t\t\ty2: Y(cmd.x2, cmd.y2),\n\t\t\t\t\tx: X(cmd.x, cmd.y),\n\t\t\t\t\ty: Y(cmd.x, cmd.y)\n\t\t\t\t};\n\t\t\tcase 'Z':\n\t\t\t\treturn cmd;\n\t\t}\n\t});\n}\n\n/** Number of drawing segments (`L` / `Q` / `C`, plus the closing line of `Z` when it has length). */\nexport function countPathSegments(input: BezierPathInput): { lines: number; quads: number; cubics: number } {\n\tconst out = { lines: 0, quads: 0, cubics: 0 };\n\tfor (const contour of pathToContours(input)) {\n\t\tconst segs = closeContour(contour);\n\t\tfor (const s of segs) {\n\t\t\tif (s.length === 2) out.lines++;\n\t\t\telse if (s.length === 3) out.quads++;\n\t\t\telse out.cubics++;\n\t\t}\n\t}\n\treturn out;\n}\n\n/** The contour's segments, with the closing line added when the contour is closed (or filled). */\nexport function closeContour(contour: BezierContour, force = false): BezierSegment[] {\n\tconst segs = contour.segments;\n\tif (segs.length === 0) return [];\n\tif (!contour.closed && !force) return segs;\n\tconst first = segs[0][0];\n\tconst lastSeg = segs[segs.length - 1];\n\tconst last = lastSeg[lastSeg.length - 1];\n\treturn samePoint(first, last) ? segs : [...segs, [last, first]];\n}\n\n// ---------------------------------------------------------------------------\n// Measures\n// ---------------------------------------------------------------------------\n\n/** Exact bounds of a path, or `null` when it has no points. */\nexport function bezierPathBounds(input: BezierPathInput): { minX: number; minY: number; maxX: number; maxY: number } | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tconst path = toBezierPath(input);\n\tlet pen: Point | null = null;\n\tfor (const contour of pathToContours(path)) {\n\t\tfor (const s of contour.segments) {\n\t\t\tconst b = segmentBounds(s);\n\t\t\tminX = Math.min(minX, b.minX);\n\t\t\tminY = Math.min(minY, b.minY);\n\t\t\tmaxX = Math.max(maxX, b.maxX);\n\t\t\tmaxY = Math.max(maxY, b.maxY);\n\t\t}\n\t}\n\tfor (const c of path) {\n\t\tif (c.type === 'M') pen = [c.x, c.y];\n\t\tif (pen) {\n\t\t\tminX = Math.min(minX, pen[0]);\n\t\t\tminY = Math.min(minY, pen[1]);\n\t\t\tmaxX = Math.max(maxX, pen[0]);\n\t\t\tmaxY = Math.max(maxY, pen[1]);\n\t\t}\n\t}\n\treturn minX === Infinity ? null : { minX, minY, maxX, maxY };\n}\n\n/**\n * Signed area of the path as drawn: every subpath closed, contributions summed (positive\n * for counter-clockwise subpaths in a y-up frame). Exact for curves. Overlapping subpaths\n * count with their multiplicity; use `bezierPathArea` for the filled area.\n */\nexport function bezierPathSignedArea(input: BezierPathInput): number {\n\tlet sum = 0;\n\tfor (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));\n\treturn sum;\n}\n\n/** Signed area of a closed chain of segments. */\nexport function contourSignedArea(segs: readonly BezierSegment[]): number {\n\tlet sum = 0;\n\tfor (const s of segs) sum += segmentAreaTerm(s);\n\treturn sum;\n}\n\n/** Arc length of a path (closing lines of closed subpaths included). */\nexport function bezierPathLength(input: BezierPathInput): number {\n\tlet total = 0;\n\tfor (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);\n\treturn total;\n}\n\n/** Point, unit tangent and angle (radians) at an arc length along the path. */\nexport interface PathPointAtLength {\n\tpoint: Point;\n\ttangent: Point;\n\tangle: number;\n\t/** Subpath index. */\n\tcontour: number;\n\t/** Segment index within the subpath (closing line last). */\n\tsegment: number;\n\t/** Parameter within the segment. */\n\tt: number;\n}\n\n/**\n * The point at arc length `length` from the start of the path (clamped to the path). Arc\n * lengths run through the subpaths in order, as `bezierPathLength` counts them.\n */\nexport function bezierPathPointAtLength(input: BezierPathInput, length: number): PathPointAtLength | null {\n\tconst contours = pathToContours(input);\n\tlet remaining = Math.max(0, length);\n\tlet last: PathPointAtLength | null = null;\n\tfor (let ci = 0; ci < contours.length; ci++) {\n\t\tconst segs = closeContour(contours[ci]);\n\t\tfor (let si = 0; si < segs.length; si++) {\n\t\t\tconst s = segs[si];\n\t\t\tconst len = segmentLength(s);\n\t\t\tif (remaining <= len) {\n\t\t\t\tconst t = segmentParamAtLength(s, remaining, len);\n\t\t\t\tconst tangent = segmentTangent(s, t);\n\t\t\t\treturn { point: segmentPoint(s, t), tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t };\n\t\t\t}\n\t\t\tremaining -= len;\n\t\t\tconst tangent = segmentTangent(s, 1);\n\t\t\tlast = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };\n\t\t}\n\t}\n\treturn last;\n}\n\n/**\n * Splits a segment chain by arc length: the part between `from` and `to` (measured from the\n * chain's start) as new segments.\n */\nexport function trimSegments(segs: readonly BezierSegment[], from: number, to: number, lengths?: number[]): Point[][] {\n\tconst lens = lengths ?? segs.map((s) => segmentLength(s));\n\tconst out: Point[][] = [];\n\tlet at = 0;\n\tfor (let i = 0; i < segs.length; i++) {\n\t\tconst len = lens[i];\n\t\tconst s0 = at;\n\t\tconst s1 = at + len;\n\t\tat = s1;\n\t\tif (s1 <= from || s0 >= to || len <= 0) continue;\n\t\tconst t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;\n\t\tconst t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;\n\t\tif (t1 > t0) out.push(subSegment(segs[i], t0, t1));\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Winding numbers\n// ---------------------------------------------------------------------------\n\n/** Parameter where a y-monotone segment reaches height `y` (it must span `y`). */\nexport function solveMonotone(seg: BezierSegment, axis: 0 | 1, value: number): number {\n\tconst a = seg[0][axis];\n\tconst b = seg[seg.length - 1][axis];\n\tif (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);\n\tconst increasing = b > a;\n\tlet lo = 0;\n\tlet hi = 1;\n\tlet t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));\n\tfor (let i = 0; i < 60; i++) {\n\t\tconst v = segmentPoint(seg, t)[axis] - value;\n\t\tif (v === 0) return t;\n\t\tif (v > 0 === increasing) hi = t;\n\t\telse lo = t;\n\t\tconst d = segmentDerivative(seg, t)[axis];\n\t\tlet next = d !== 0 ? t - v / d : (lo + hi) / 2;\n\t\tif (!(next > lo && next < hi)) next = (lo + hi) / 2;\n\t\tif (Math.abs(next - t) < 1e-15) return next;\n\t\tt = next;\n\t}\n\treturn t;\n}\n\n/** Splits segments at their y extrema (pieces monotone in y). */\nfunction yMonotonePieces(segs: readonly BezierSegment[]): BezierSegment[] {\n\tconst out: BezierSegment[] = [];\n\tfor (const s of segs) {\n\t\tif (s.length === 2) {\n\t\t\tout.push(s);\n\t\t\tcontinue;\n\t\t}\n\t\tconst ts = segmentExtrema(s, 1);\n\t\tlet prev = 0;\n\t\tfor (const t of [...ts, 1]) {\n\t\t\tout.push(subSegment(s, prev, t));\n\t\t\tprev = t;\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * Winding number of a set of closed segment chains around `point` (ray to +x, half-open\n * crossing rule). Points exactly on the outline may report either side.\n */\nexport function windingNumber(chains: readonly (readonly BezierSegment[])[], point: Point): number {\n\tconst [px, py] = point;\n\tlet winding = 0;\n\tfor (const chain of chains) {\n\t\tfor (const s of yMonotonePieces(chain)) {\n\t\t\tconst ay = s[0][1];\n\t\t\tconst by = s[s.length - 1][1];\n\t\t\tlet dir = 0;\n\t\t\tif (ay <= py && by > py) dir = 1;\n\t\t\telse if (by <= py && ay > py) dir = -1;\n\t\t\telse continue;\n\t\t\tlet minX = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tfor (const p of s) {\n\t\t\t\tif (p[0] < minX) minX = p[0];\n\t\t\t\tif (p[0] > maxX) maxX = p[0];\n\t\t\t}\n\t\t\tif (minX > px) {\n\t\t\t\twinding += dir;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (maxX <= px) continue;\n\t\t\tconst t = solveMonotone(s, 1, py);\n\t\t\tif (segmentPoint(s, t)[0] > px) winding += dir;\n\t\t}\n\t}\n\treturn winding;\n}\n\n/** Whether `point` is inside the filled path (every subpath closed for filling). */\nexport function pointInBezierPath(input: BezierPathInput, point: Point, fillRule: PolygonFillRule = 'nonzero'): boolean {\n\tconst chains = pathToContours(input).map((c) => closeContour(c, true));\n\tconst w = windingNumber(chains, point);\n\treturn fillRule === 'evenodd' ? (w & 1) === 1 : w !== 0;\n}\n\n/** Distance from `point` to the path outline (closing lines of closed subpaths included). */\nexport function distanceToBezierPath(input: BezierPathInput | readonly BezierContour[], point: Point): number {\n\tconst contours =\n\t\tArray.isArray(input) && input.length > 0 && (input[0] as BezierContour).segments\n\t\t\t? (input as readonly BezierContour[])\n\t\t\t: pathToContours(input as BezierPathInput);\n\tlet best = Infinity;\n\tfor (const contour of contours) {\n\t\tfor (const s of closeContour(contour)) {\n\t\t\t// Skip segments whose control hull is farther than the best distance so far.\n\t\t\tlet minX = Infinity;\n\t\t\tlet minY = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tlet maxY = -Infinity;\n\t\t\tfor (const p of s) {\n\t\t\t\tminX = Math.min(minX, p[0]);\n\t\t\t\tminY = Math.min(minY, p[1]);\n\t\t\t\tmaxX = Math.max(maxX, p[0]);\n\t\t\t\tmaxY = Math.max(maxY, p[1]);\n\t\t\t}\n\t\t\tconst dx = Math.max(minX - point[0], 0, point[0] - maxX);\n\t\t\tconst dy = Math.max(minY - point[1], 0, point[1] - maxY);\n\t\t\tif (Math.hypot(dx, dy) >= best) continue;\n\t\t\tbest = Math.min(best, segmentNearest(s, point).distance);\n\t\t}\n\t\tif (contour.segments.length === 0) continue;\n\t}\n\treturn best;\n}\n\n/** Flattens a closed chain into a polygon ring (for classification only). */\nexport function flattenChain(segs: readonly BezierSegment[], tolerance: number): Point[] {\n\tconst out: Point[] = [];\n\tfor (const s of segs) {\n\t\tif (out.length === 0) out.push(s[0]);\n\t\tif (s.length === 2) {\n\t\t\tout.push(s[1]);\n\t\t\tcontinue;\n\t\t}\n\t\tlet dd = 0;\n\t\tfor (let i = 0; i + 2 < s.length; i++) {\n\t\t\tdd = Math.max(dd, Math.hypot(s[i][0] - 2 * s[i + 1][0] + s[i + 2][0], s[i][1] - 2 * s[i + 1][1] + s[i + 2][1]));\n\t\t}\n\t\tconst deg = s.length - 1;\n\t\tconst n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt((deg * (deg - 1) * dd) / (8 * tolerance)))));\n\t\tfor (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));\n\t}\n\tif (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();\n\treturn out;\n}\n","/**\n * Curve-preserving boolean operations (union, intersection, difference, xor and divide) on\n * Bézier paths.\n *\n * Algorithm (planar arrangement of curve pieces + winding classification):\n * 1. Every segment of every operand (open subpaths closed for filling) is a *source*; it is\n * cut into pieces monotone in x and y. Each piece remembers its source and its parameter\n * range there, so results can be rebuilt from exact sub-curves of the inputs.\n * 2. Pieces are intersected pairwise (pruned by a sweep on x): lines analytically, curves by\n * recursive subdivision down to flat sub-curves, chord intersection and Newton polishing.\n * Endpoints lying on another piece (T-junctions, tangent touches), collinear / coincident\n * overlaps and clusters of hits along a tangency are resolved first. Points closer than\n * `epsilon` share one vertex. Splitting repeats until no piece crosses another.\n * 3. Coincident pieces (shared edges, identical curves) merge into one edge carrying every\n * operand's signed multiplicity.\n * 4. Each edge is classified by casting a horizontal or vertical ray from its midpoint (the\n * pieces are monotone, so a ray crosses each at most once); the operands' fill rules turn\n * the winding numbers on both sides into inside flags and the operation picks the edges\n * with the result on exactly one side.\n * 5. The kept edges are traced into rings, taking the sharpest left turn at each vertex\n * (ties between tangent curves broken by a short chord along each curve), and consecutive\n * pieces of one source are merged back into one exact sub-curve, so circles stay four\n * cubics and rectangles four lines.\n *\n * When the curve engine fails (non-converging intersections on near-coincident curves,\n * an inconsistent arrangement) the operation falls back to the polygon engine\n * (`polygonBoolean`) on flattened input and reports it (`engine: 'polygon'`).\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/boolean.rs (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../types.js';\nimport {\n\tpolygonBoolean,\n\tdividePolygons,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule\n} from '../polygonBoolean.js';\nimport { polygonToSvgPath } from '../shapeGeometry.js';\nimport {\n\tcloseContour,\n\tcontourSignedArea,\n\tcontoursToPath,\n\tcross,\n\tdist,\n\tdot,\n\tflattenChain,\n\tisStraightSegment,\n\tmonotoneParams,\n\tpathToContours,\n\tsegmentDerivative,\n\tsegmentLength,\n\tsegmentNearest,\n\tsegmentPoint,\n\tsegmentTangent,\n\tsolveMonotone,\n\tsub,\n\tsubSegment,\n\ttoBezierPath,\n\twindingNumber,\n\ttype BezierContour,\n\ttype BezierPath,\n\ttype BezierPathInput,\n\ttype BezierSegment\n} from './bezier.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Boolean operations (same names as `polygonBoolean`). `xor` is Exclude. */\nexport type PathBooleanOperation = PolygonBooleanOperation;\n\n/** A path with the fill rule it is read with (default `nonzero`). */\nexport interface PathOperand {\n\tpath: BezierPathInput;\n\tfillRule?: PolygonFillRule;\n}\n\nexport type PathOperandInput = BezierPathInput | PathOperand;\n\nexport interface PathOpsOptions {\n\t/**\n\t * Distance below which points are treated as equal. Defaults to `1e-9` times the\n\t * largest coordinate magnitude (at least `1e-9`).\n\t */\n\tepsilon?: number;\n\t/** Fall back to the polygon engine when the curve engine fails (default `true`). */\n\tfallback?: boolean;\n\t/** Flattening tolerance used by the polygon fallback (default 0.05). */\n\tfallbackTolerance?: number;\n\t/** Called with the reason whenever the polygon fallback is used. */\n\tonFallback?: (reason: string) => void;\n\t/**\n\t * Work budget of the curve–curve intersection search (flat sub-curve pairs per pair of\n\t * pieces, default 20,000). Exceeding it (nearly coincident curves) falls back.\n\t */\n\tsubdivisionBudget?: number;\n}\n\n/** Which engine produced a result, and why the curve engine was not used. */\nexport interface PathOpsReport {\n\tengine: 'curve' | 'polygon';\n\tfallbackReason?: string;\n\t/** Set when the input cannot be processed at all (non-finite coordinates); the result is empty. */\n\terror?: string;\n}\n\nexport interface PathBooleanResult extends PathOpsReport {\n\tpath: BezierPath;\n}\n\n/** One region of `dividePaths`, with the indices of the operands that cover it. */\nexport interface PathPiece {\n\tpath: BezierPath;\n\toperands: number[];\n}\n\nexport interface DividePathsResult extends PathOpsReport {\n\tpieces: PathPiece[];\n}\n\n/** Raised by the curve engine when it cannot produce a reliable arrangement. */\nexport class PathOpsError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'PathOpsError';\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Operands\n// ---------------------------------------------------------------------------\n\ninterface NormalizedOperand {\n\tchains: BezierSegment[][];\n\tfillRule: PolygonFillRule;\n}\n\nfunction isOperandObject(input: PathOperandInput): input is PathOperand {\n\treturn !!input && typeof input === 'object' && !Array.isArray(input) && 'path' in input;\n}\n\nfunction normalizeOperand(input: PathOperandInput): NormalizedOperand {\n\tconst path = isOperandObject(input) ? input.path : input;\n\tconst fillRule = isOperandObject(input) ? input.fillRule ?? 'nonzero' : 'nonzero';\n\tconst chains: BezierSegment[][] = [];\n\tfor (const contour of pathToContours(path ?? '')) {\n\t\tconst chain = closeContour(contour, true);\n\t\tif (chain.length === 0) continue;\n\t\tfor (const seg of chain) {\n\t\t\tfor (const p of seg) {\n\t\t\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);\n\t\t\t}\n\t\t}\n\t\tchains.push(chain);\n\t}\n\treturn { chains, fillRule };\n}\n\nfunction scaleOf(operands: NormalizedOperand[]): number {\n\tlet max = 0;\n\tfor (const op of operands) {\n\t\tfor (const chain of op.chains) {\n\t\t\tfor (const seg of chain) {\n\t\t\t\tfor (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));\n\t\t\t}\n\t\t}\n\t}\n\treturn Math.max(1, max);\n}\n\n// ---------------------------------------------------------------------------\n// Arrangement data\n// ---------------------------------------------------------------------------\n\ninterface Vertex {\n\tid: number;\n\tx: number;\n\ty: number;\n\tp: Point;\n}\n\n/** Merges points closer than `eps` (grid hash with neighbour lookup). */\nclass VertexPool {\n\treadonly vertices: Vertex[] = [];\n\tprivate readonly cells = new Map<string, number[]>();\n\tconstructor(private readonly eps: number) {}\n\n\tget(x: number, y: number): Vertex {\n\t\tconst cx = Math.floor(x / this.eps);\n\t\tconst cy = Math.floor(y / this.eps);\n\t\tlet best: Vertex | null = null;\n\t\tlet bestDist = Infinity;\n\t\tfor (let dx = -1; dx <= 1; dx++) {\n\t\t\tfor (let dy = -1; dy <= 1; dy++) {\n\t\t\t\tconst ids = this.cells.get(`${cx + dx},${cy + dy}`);\n\t\t\t\tif (!ids) continue;\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst v = this.vertices[id];\n\t\t\t\t\tconst d = Math.hypot(v.x - x, v.y - y);\n\t\t\t\t\tif (d <= this.eps && d < bestDist) {\n\t\t\t\t\t\tbest = v;\n\t\t\t\t\t\tbestDist = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (best) return best;\n\t\tconst vertex: Vertex = { id: this.vertices.length, x, y, p: [x, y] };\n\t\tthis.vertices.push(vertex);\n\t\tconst key = `${cx},${cy}`;\n\t\tconst bucket = this.cells.get(key);\n\t\tif (bucket) bucket.push(vertex.id);\n\t\telse this.cells.set(key, [vertex.id]);\n\t\treturn vertex;\n\t}\n}\n\ninterface Source {\n\tseg: BezierSegment;\n\tline: boolean;\n}\n\ninterface Piece {\n\tpts: Point[];\n\ta: Vertex;\n\tb: Vertex;\n\toperand: number;\n\tsrc: number;\n\t/** Source parameters at `a` and `b` (`s0 < s1`). */\n\ts0: number;\n\ts1: number;\n\tdirty: boolean;\n\tminX: number;\n\tmaxX: number;\n\tminY: number;\n\tmaxY: number;\n}\n\nfunction hull(pts: readonly Point[]) {\n\tlet minX = Infinity;\n\tlet maxX = -Infinity;\n\tlet minY = Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of pts) {\n\t\tif (x < minX) minX = x;\n\t\tif (x > maxX) maxX = x;\n\t\tif (y < minY) minY = y;\n\t\tif (y > maxY) maxY = y;\n\t}\n\treturn { minX, maxX, minY, maxY };\n}\n\n/** Moves the end points of a segment onto two vertices, dragging the handles along. */\nfunction snapEnds(pts: readonly Point[], a: Point, b: Point): Point[] {\n\tconst n = pts.length;\n\tif (n === 2) return [a, b];\n\tconst d0 = sub(a, pts[0]);\n\tconst d1 = sub(b, pts[n - 1]);\n\tif (n === 3) {\n\t\treturn [a, [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2], b];\n\t}\n\treturn [a, [pts[1][0] + d0[0], pts[1][1] + d0[1]], [pts[2][0] + d1[0], pts[2][1] + d1[1]], b];\n}\n\nfunction makePiece(\n\tpts: Point[],\n\ta: Vertex,\n\tb: Vertex,\n\toperand: number,\n\tsrc: number,\n\ts0: number,\n\ts1: number\n): Piece {\n\tconst snapped = snapEnds(pts, a.p, b.p);\n\treturn { pts: snapped, a, b, operand, src, s0, s1, dirty: true, ...hull(snapped) };\n}\n\n// ---------------------------------------------------------------------------\n// Intersections\n// ---------------------------------------------------------------------------\n\ninterface PairEvent {\n\ttp: number;\n\ttq: number;\n\tpoint: Point;\n\t/** Set for shared end points and end points lying on the other piece. */\n\tanchor?: boolean;\n\t/** Shared vertex: nothing to split. */\n\tshared?: boolean;\n}\n\ninterface IntersectContext {\n\teps: number;\n\tleaves: number;\n\tbudget: number;\n}\n\nconst NON_FINITE = 'non-finite coordinates';\n\nfunction flatness(pts: readonly Point[]): number {\n\tif (pts.length === 2) return 0;\n\tconst a = pts[0];\n\tconst b = pts[pts.length - 1];\n\tconst chord = sub(b, a);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tlet worst = 0;\n\tfor (let i = 1; i < pts.length - 1; i++) {\n\t\tconst v = sub(pts[i], a);\n\t\tconst d = len > 0 ? Math.abs(cross(chord, v)) / len : Math.hypot(v[0], v[1]);\n\t\tconst along = len > 0 ? dot(chord, v) / len : 0;\n\t\tconst over = Math.max(0, -along, along - len);\n\t\tworst = Math.max(worst, d, over);\n\t}\n\treturn worst;\n}\n\nfunction split2(pts: readonly Point[]): [Point[], Point[]] {\n\tconst n = pts.length;\n\tconst mid = (p: Point, q: Point): Point => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];\n\tif (n === 2) {\n\t\tconst m = mid(pts[0], pts[1]);\n\t\treturn [\n\t\t\t[pts[0], m],\n\t\t\t[m, pts[1]]\n\t\t];\n\t}\n\tif (n === 3) {\n\t\tconst a = mid(pts[0], pts[1]);\n\t\tconst b = mid(pts[1], pts[2]);\n\t\tconst m = mid(a, b);\n\t\treturn [\n\t\t\t[pts[0], a, m],\n\t\t\t[m, b, pts[2]]\n\t\t];\n\t}\n\tconst a = mid(pts[0], pts[1]);\n\tconst b = mid(pts[1], pts[2]);\n\tconst c = mid(pts[2], pts[3]);\n\tconst ab = mid(a, b);\n\tconst bc = mid(b, c);\n\tconst m = mid(ab, bc);\n\treturn [\n\t\t[pts[0], a, ab, m],\n\t\t[m, bc, c, pts[3]]\n\t];\n}\n\n/** Closest points of two segments: parameters and distance. */\nfunction closestSegmentPoints(a0: Point, a1: Point, b0: Point, b1: Point): { s: number; u: number; d: number } {\n\tconst d1 = sub(a1, a0);\n\tconst d2 = sub(b1, b0);\n\tconst r = sub(a0, b0);\n\tconst a = dot(d1, d1);\n\tconst e = dot(d2, d2);\n\tconst f = dot(d2, r);\n\tlet s = 0;\n\tlet u = 0;\n\tif (a <= 1e-300 && e <= 1e-300) return { s: 0, u: 0, d: dist(a0, b0) };\n\tif (a <= 1e-300) {\n\t\tu = Math.max(0, Math.min(1, f / e));\n\t} else {\n\t\tconst c = dot(d1, r);\n\t\tif (e <= 1e-300) {\n\t\t\ts = Math.max(0, Math.min(1, -c / a));\n\t\t} else {\n\t\t\tconst b = dot(d1, d2);\n\t\t\tconst denom = a * e - b * b;\n\t\t\ts = denom > 0 ? Math.max(0, Math.min(1, (b * f - c * e) / denom)) : 0;\n\t\t\tu = (b * s + f) / e;\n\t\t\tif (u < 0) {\n\t\t\t\tu = 0;\n\t\t\t\ts = Math.max(0, Math.min(1, -c / a));\n\t\t\t} else if (u > 1) {\n\t\t\t\tu = 1;\n\t\t\t\ts = Math.max(0, Math.min(1, (b - c) / a));\n\t\t\t}\n\t\t}\n\t}\n\tconst pa: Point = [a0[0] + d1[0] * s, a0[1] + d1[1] * s];\n\tconst pb: Point = [b0[0] + d2[0] * u, b0[1] + d2[1] * u];\n\treturn { s, u, d: dist(pa, pb) };\n}\n\n/** Newton polishing of P(s) = Q(u). Returns the best parameters found. */\nfunction polish(P: readonly Point[], Q: readonly Point[], s: number, u: number): { s: number; u: number; d: number } {\n\tlet best = { s, u, d: dist(segmentPoint(P, s), segmentPoint(Q, u)) };\n\tfor (let i = 0; i < 10 && best.d > 0; i++) {\n\t\tconst f = sub(segmentPoint(P, s), segmentPoint(Q, u));\n\t\tconst dp = segmentDerivative(P, s);\n\t\tconst dq = segmentDerivative(Q, u);\n\t\t// Solve [dp, -dq] [ds, du]^T = -f\n\t\tconst det = cross(dp, [-dq[0], -dq[1]]);\n\t\tif (!(Math.abs(det) > 1e-300)) break;\n\t\tconst ds = cross([-f[0], -f[1]], [-dq[0], -dq[1]]) / det;\n\t\tconst du = cross(dp, [-f[0], -f[1]]) / det;\n\t\ts = Math.max(0, Math.min(1, s + ds));\n\t\tu = Math.max(0, Math.min(1, u + du));\n\t\tconst d = dist(segmentPoint(P, s), segmentPoint(Q, u));\n\t\tif (d < best.d) best = { s, u, d };\n\t\telse break;\n\t}\n\treturn best;\n}\n\nfunction intersectSubdivided(\n\tP: Point[],\n\tp0: number,\n\tp1: number,\n\tQ: Point[],\n\tq0: number,\n\tq1: number,\n\tfullP: readonly Point[],\n\tfullQ: readonly Point[],\n\tctx: IntersectContext,\n\tout: PairEvent[],\n\tdepth: number\n) {\n\tconst hp = hull(P);\n\tconst hq = hull(Q);\n\tconst e = ctx.eps;\n\tif (hp.minX > hq.maxX + e || hq.minX > hp.maxX + e || hp.minY > hq.maxY + e || hq.minY > hp.maxY + e) return;\n\tconst fp = flatness(P);\n\tconst fq = flatness(Q);\n\tconst flatTol = e * 0.5;\n\tif ((fp <= flatTol && fq <= flatTol) || depth >= 52) {\n\t\tif (++ctx.leaves > ctx.budget) throw new PathOpsError('curve intersection did not converge');\n\t\tconst a0 = P[0];\n\t\tconst a1 = P[P.length - 1];\n\t\tconst b0 = Q[0];\n\t\tconst b1 = Q[Q.length - 1];\n\t\tconst d1 = sub(a1, a0);\n\t\tconst d2 = sub(b1, b0);\n\t\tconst denom = cross(d1, d2);\n\t\tlet s = 0;\n\t\tlet u = 0;\n\t\tlet hit = false;\n\t\tconst l1 = Math.hypot(d1[0], d1[1]);\n\t\tconst l2 = Math.hypot(d2[0], d2[1]);\n\t\tif (Math.abs(denom) > 1e-12 * l1 * l2) {\n\t\t\tconst r = sub(b0, a0);\n\t\t\ts = cross(r, d2) / denom;\n\t\t\tu = cross(r, d1) / denom;\n\t\t\tconst ts = l1 > 0 ? e / l1 : 1;\n\t\t\tconst tu = l2 > 0 ? e / l2 : 1;\n\t\t\thit = s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu;\n\t\t}\n\t\tif (!hit) {\n\t\t\t// No crossing of the chords: a touch when they come within epsilon.\n\t\t\tconst c = closestSegmentPoints(a0, a1, b0, b1);\n\t\t\tif (c.d > e) return;\n\t\t\ts = c.s;\n\t\t\tu = c.u;\n\t\t}\n\t\ts = Math.max(0, Math.min(1, s));\n\t\tu = Math.max(0, Math.min(1, u));\n\t\tconst best = polish(fullP, fullQ, p0 + (p1 - p0) * s, q0 + (q1 - q0) * u);\n\t\tif (best.d > e * 2) return;\n\t\tconst a = segmentPoint(fullP, best.s);\n\t\tconst b = segmentPoint(fullQ, best.u);\n\t\tout.push({ tp: best.s, tq: best.u, point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] });\n\t\treturn;\n\t}\n\tconst splitP = fp > flatTol;\n\tconst splitQ = fq > flatTol;\n\tconst pm = (p0 + p1) / 2;\n\tconst qm = (q0 + q1) / 2;\n\tif (splitP && splitQ) {\n\t\tconst [pa, pb] = split2(P);\n\t\tconst [qa, qb] = split2(Q);\n\t\tintersectSubdivided(pa, p0, pm, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pa, p0, pm, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pb, pm, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pb, pm, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);\n\t} else if (splitP) {\n\t\tconst [pa, pb] = split2(P);\n\t\tintersectSubdivided(pa, p0, pm, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pb, pm, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);\n\t} else {\n\t\tconst [qa, qb] = split2(Q);\n\t\tintersectSubdivided(P, p0, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(P, p0, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);\n\t}\n}\n\nfunction nearestOn(piece: Piece, p: Point, eps: number): { t: number; distance: number } | null {\n\tif (p[0] < piece.minX - eps || p[0] > piece.maxX + eps || p[1] < piece.minY - eps || p[1] > piece.maxY + eps) return null;\n\treturn segmentNearest(piece.pts, p);\n}\n\n/** Whether the two pieces stay within `tol` of each other between two events. */\nfunction staysClose(P: Piece, Q: Piece, e1: PairEvent, e2: PairEvent, tol: number): boolean {\n\tfor (const f of [0.25, 0.5, 0.75]) {\n\t\tconst tp = e1.tp + (e2.tp - e1.tp) * f;\n\t\tconst q = segmentNearest(Q.pts, segmentPoint(P.pts, tp));\n\t\tif (q.distance > tol) return false;\n\t}\n\treturn true;\n}\n\nfunction pairEvents(P: Piece, Q: Piece, ctx: IntersectContext): PairEvent[] {\n\tconst eps = ctx.eps;\n\tconst anchors: PairEvent[] = [];\n\t// Shared vertices.\n\tfor (const [va, tp] of [\n\t\t[P.a, 0],\n\t\t[P.b, 1]\n\t] as const) {\n\t\tif (va === Q.a) anchors.push({ tp, tq: 0, point: va.p, anchor: true, shared: true });\n\t\telse if (va === Q.b) anchors.push({ tp, tq: 1, point: va.p, anchor: true, shared: true });\n\t}\n\t// End points lying on the other piece (T-junctions, overlaps).\n\tfor (const [v, tq] of [\n\t\t[Q.a, 0],\n\t\t[Q.b, 1]\n\t] as const) {\n\t\tif (v === P.a || v === P.b) continue;\n\t\tconst near = nearestOn(P, v.p, eps);\n\t\tif (near && near.distance <= eps) anchors.push({ tp: near.t, tq, point: v.p, anchor: true });\n\t}\n\tfor (const [v, tp] of [\n\t\t[P.a, 0],\n\t\t[P.b, 1]\n\t] as const) {\n\t\tif (v === Q.a || v === Q.b) continue;\n\t\tconst near = nearestOn(Q, v.p, eps);\n\t\tif (near && near.distance <= eps) anchors.push({ tp, tq: near.t, point: v.p, anchor: true });\n\t}\n\n\t// Coincident overlap: two anchors with the pieces running together between them.\n\tif (anchors.length >= 2) {\n\t\tconst sorted = [...anchors].sort((x, y) => x.tp - y.tp);\n\t\tconst first = sorted[0];\n\t\tconst last = sorted[sorted.length - 1];\n\t\tif (dist(first.point, last.point) > eps && staysClose(P, Q, first, last, eps * 4)) {\n\t\t\treturn anchors.filter((a) => !a.shared);\n\t\t}\n\t}\n\n\tconst found: PairEvent[] = [];\n\tif (P.pts.length === 2 && Q.pts.length === 2) {\n\t\tconst a0 = P.pts[0];\n\t\tconst d1 = sub(P.pts[1], a0);\n\t\tconst b0 = Q.pts[0];\n\t\tconst d2 = sub(Q.pts[1], b0);\n\t\tconst denom = cross(d1, d2);\n\t\tconst l1 = Math.hypot(d1[0], d1[1]);\n\t\tconst l2 = Math.hypot(d2[0], d2[1]);\n\t\tif (Math.abs(denom) > 1e-12 * l1 * l2) {\n\t\t\tconst r = sub(b0, a0);\n\t\t\tconst s = cross(r, d2) / denom;\n\t\t\tconst u = cross(r, d1) / denom;\n\t\t\tconst ts = eps / l1;\n\t\t\tconst tu = eps / l2;\n\t\t\tif (s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu) {\n\t\t\t\tconst sc = Math.max(0, Math.min(1, s));\n\t\t\t\tconst uc = Math.max(0, Math.min(1, u));\n\t\t\t\tfound.push({ tp: sc, tq: uc, point: [a0[0] + d1[0] * sc, a0[1] + d1[1] * sc] });\n\t\t\t}\n\t\t}\n\t} else {\n\t\tctx.leaves = 0;\n\t\tintersectSubdivided(P.pts, 0, 1, Q.pts, 0, 1, P.pts, Q.pts, ctx, found, 0);\n\t}\n\tif (found.length === 0) return anchors.filter((a) => !a.shared);\n\n\t// Cluster hits along tangencies: consecutive events between which the pieces never\n\t// separate by more than a few epsilon are one contact.\n\tconst all = [...anchors, ...found].sort((x, y) => x.tp - y.tp || x.tq - y.tq);\n\tconst clusters: PairEvent[][] = [];\n\tfor (const ev of all) {\n\t\tconst cluster = clusters[clusters.length - 1];\n\t\tconst prev = cluster?.[cluster.length - 1];\n\t\tif (prev && (dist(prev.point, ev.point) <= eps * 2 || staysClose(P, Q, prev, ev, eps * 2))) cluster.push(ev);\n\t\telse clusters.push([ev]);\n\t}\n\tconst out: PairEvent[] = [];\n\tfor (const cluster of clusters) {\n\t\tconst shared = cluster.find((e) => e.shared);\n\t\tif (shared) continue;\n\t\tconst anchor = cluster.find((e) => e.anchor);\n\t\tout.push(anchor ?? cluster[Math.floor(cluster.length / 2)]);\n\t}\n\treturn out;\n}\n\ninterface SplitPoint {\n\tt: number;\n\tv: Vertex;\n}\n\nfunction addSplit(map: Map<Piece, SplitPoint[]>, piece: Piece, t: number, v: Vertex) {\n\tif (v === piece.a || v === piece.b) return;\n\tconst list = map.get(piece);\n\tif (list) list.push({ t, v });\n\telse map.set(piece, [{ t, v }]);\n}\n\n/** Resolves an event to a vertex, preferring the pieces' own end points. */\nfunction eventVertex(P: Piece, Q: Piece, ev: PairEvent, pool: VertexPool, eps: number): Vertex {\n\tfor (const v of [P.a, P.b, Q.a, Q.b]) if (dist(v.p, ev.point) <= eps) return v;\n\treturn pool.get(ev.point[0], ev.point[1]);\n}\n\nfunction splitPass(pieces: Piece[], pool: VertexPool, ctx: IntersectContext): { pieces: Piece[]; changed: boolean } {\n\tconst eps = ctx.eps;\n\tconst order = pieces.map((_, i) => i).sort((i, j) => pieces[i].minX - pieces[j].minX);\n\tconst splits = new Map<Piece, SplitPoint[]>();\n\tfor (let oi = 0; oi < order.length; oi++) {\n\t\tconst P = pieces[order[oi]];\n\t\tfor (let oj = oi + 1; oj < order.length; oj++) {\n\t\t\tconst Q = pieces[order[oj]];\n\t\t\tif (Q.minX > P.maxX + eps) break;\n\t\t\tif (!P.dirty && !Q.dirty) continue;\n\t\t\tif (Q.minY > P.maxY + eps || Q.maxY < P.minY - eps) continue;\n\t\t\tif (P.a === Q.a && P.b === Q.b && P.pts.length === 2 && Q.pts.length === 2) continue;\n\t\t\tif (P.a === Q.b && P.b === Q.a && P.pts.length === 2 && Q.pts.length === 2) continue;\n\t\t\tfor (const ev of pairEvents(P, Q, ctx)) {\n\t\t\t\tconst v = eventVertex(P, Q, ev, pool, eps);\n\t\t\t\taddSplit(splits, P, ev.tp, v);\n\t\t\t\taddSplit(splits, Q, ev.tq, v);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const p of pieces) p.dirty = false;\n\tif (splits.size === 0) return { pieces, changed: false };\n\tconst out: Piece[] = [];\n\tfor (const piece of pieces) {\n\t\tconst list = splits.get(piece);\n\t\tif (!list) {\n\t\t\tout.push(piece);\n\t\t\tcontinue;\n\t\t}\n\t\tlist.sort((x, y) => x.t - y.t);\n\t\tlet prevT = 0;\n\t\tlet prevV = piece.a;\n\t\tconst range = piece.s1 - piece.s0;\n\t\tconst emit = (t: number, v: Vertex) => {\n\t\t\tif (v === prevV || t <= prevT) return;\n\t\t\tconst pts = subSegment(piece.pts, prevT, t);\n\t\t\tout.push(makePiece(pts, prevV, v, piece.operand, piece.src, piece.s0 + range * prevT, piece.s0 + range * t));\n\t\t\tprevT = t;\n\t\t\tprevV = v;\n\t\t};\n\t\tconst seen = new Set<Vertex>();\n\t\tfor (const { t, v } of list) {\n\t\t\tif (seen.has(v) || t <= 0 || t >= 1) continue;\n\t\t\tseen.add(v);\n\t\t\temit(t, v);\n\t\t}\n\t\tif (prevV !== piece.b) {\n\t\t\tconst pts = subSegment(piece.pts, prevT, 1);\n\t\t\tout.push(makePiece(pts, prevV, piece.b, piece.operand, piece.src, piece.s0 + range * prevT, piece.s1));\n\t\t}\n\t}\n\treturn { pieces: out, changed: true };\n}\n\n// ---------------------------------------------------------------------------\n// Edges and classification\n// ---------------------------------------------------------------------------\n\ninterface Edge {\n\ta: Vertex;\n\tb: Vertex;\n\t/** Control points from `a` to `b`. */\n\tpts: Point[];\n\tmult: number[];\n\tsrc: number;\n\t/** Source parameters at `a` and at `b`. */\n\tsa: number;\n\tsb: number;\n\tminX: number;\n\tmaxX: number;\n\tminY: number;\n\tmaxY: number;\n}\n\ninterface Arrangement {\n\tedges: Edge[];\n\tleft: number[][];\n\tright: number[][];\n\tfillRules: PolygonFillRule[];\n\tsources: Source[];\n\teps: number;\n\tscale: number;\n}\n\nfunction buildArrangement(inputs: PathOperandInput[], options: PathOpsOptions): Arrangement {\n\tconst operands = inputs.map(normalizeOperand);\n\tconst scale = scaleOf(operands);\n\tconst eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale;\n\tconst pool = new VertexPool(eps);\n\tconst sources: Source[] = [];\n\tlet pieces: Piece[] = [];\n\toperands.forEach((op, operand) => {\n\t\tfor (const chain of op.chains) {\n\t\t\tfor (const raw of chain) {\n\t\t\t\tconst straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);\n\t\t\t\tconst seg: BezierSegment = straight ? [raw[0], raw[raw.length - 1]] : raw;\n\t\t\t\tconst src = sources.length;\n\t\t\t\tsources.push({ seg, line: seg.length === 2 });\n\t\t\t\tconst ts = [0, ...monotoneParams(seg), 1];\n\t\t\t\tfor (let i = 0; i + 1 < ts.length; i++) {\n\t\t\t\t\tconst pts = subSegment(seg, ts[i], ts[i + 1]);\n\t\t\t\t\tconst a = pool.get(pts[0][0], pts[0][1]);\n\t\t\t\t\tconst b = pool.get(pts[pts.length - 1][0], pts[pts.length - 1][1]);\n\t\t\t\t\tif (a === b) continue;\n\t\t\t\t\tpieces.push(makePiece(pts, a, b, operand, src, ts[i], ts[i + 1]));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tconst ctx: IntersectContext = { eps, leaves: 0, budget: options.subdivisionBudget ?? 20000 };\n\tlet settled = false;\n\tfor (let pass = 0; pass < 6; pass++) {\n\t\tconst result = splitPass(pieces, pool, ctx);\n\t\tpieces = result.pieces;\n\t\tif (!result.changed) {\n\t\t\tsettled = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!settled) throw new PathOpsError('intersections did not settle');\n\n\t// Merge coincident pieces.\n\tconst count = operands.length;\n\tconst groups = new Map<string, Edge[]>();\n\tconst edges: Edge[] = [];\n\tfor (const piece of pieces) {\n\t\tconst forward = piece.a.id < piece.b.id;\n\t\tconst a = forward ? piece.a : piece.b;\n\t\tconst b = forward ? piece.b : piece.a;\n\t\tconst pts = forward ? piece.pts : [...piece.pts].reverse();\n\t\tconst key = `${a.id},${b.id}`;\n\t\tconst group = groups.get(key) ?? [];\n\t\tif (!groups.has(key)) groups.set(key, group);\n\t\tconst mid = segmentPoint(pts, 0.5);\n\t\tlet edge = group.find((e) => {\n\t\t\tif (e.pts.length === 2 && pts.length === 2) return true;\n\t\t\treturn (\n\t\t\t\tsegmentNearest(e.pts, mid).distance <= eps * 4 &&\n\t\t\t\tsegmentNearest(pts, segmentPoint(e.pts, 0.5)).distance <= eps * 4\n\t\t\t);\n\t\t});\n\t\tif (!edge) {\n\t\t\tedge = {\n\t\t\t\ta,\n\t\t\t\tb,\n\t\t\t\tpts,\n\t\t\t\tmult: new Array(count).fill(0),\n\t\t\t\tsrc: piece.src,\n\t\t\t\tsa: forward ? piece.s0 : piece.s1,\n\t\t\t\tsb: forward ? piece.s1 : piece.s0,\n\t\t\t\t...hull(pts)\n\t\t\t};\n\t\t\tgroup.push(edge);\n\t\t\tedges.push(edge);\n\t\t}\n\t\tedge.mult[piece.operand] += forward ? 1 : -1;\n\t}\n\tconst live = edges.filter((e) => e.mult.some((m) => m !== 0));\n\n\t// Winding numbers on both sides of every edge. Rays only meet edges whose y range (x\n\t// range for vertical rays) holds the ray, so edges are bucketed into bands on each axis.\n\tconst byY = new BandIndex(live, (e) => e.minY, (e) => e.maxY);\n\tconst byX = new BandIndex(live, (e) => e.minX, (e) => e.maxX);\n\tconst right: number[][] = [];\n\tconst left: number[][] = [];\n\tfor (let i = 0; i < live.length; i++) {\n\t\tconst edge = live[i];\n\t\tconst mid = segmentPoint(edge.pts, 0.5);\n\t\tconst tan = segmentTangent(edge.pts, 0.5);\n\t\tconst horizontal = Math.abs(tan[1]) < Math.abs(tan[0]);\n\t\tconst winding = new Array(count).fill(0);\n\t\tconst [mx, my] = mid;\n\t\tfor (const j of horizontal ? byX.at(mx) : byY.at(my)) {\n\t\t\tif (j === i) continue;\n\t\t\tconst o = live[j];\n\t\t\tlet dir = 0;\n\t\t\tif (!horizontal) {\n\t\t\t\t// Ray towards +x.\n\t\t\t\tif (o.maxX <= mx) continue;\n\t\t\t\tconst ay = o.a.y;\n\t\t\t\tconst by = o.b.y;\n\t\t\t\tif (ay <= my && by > my) dir = 1;\n\t\t\t\telse if (by <= my && ay > my) dir = -1;\n\t\t\t\telse continue;\n\t\t\t\tif (o.minX <= mx) {\n\t\t\t\t\tconst t = solveMonotone(o.pts, 1, my);\n\t\t\t\t\tif (!(segmentPoint(o.pts, t)[0] > mx)) continue;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Ray towards +y, in the frame rotated by −90° ((x, y) → (y, −x)).\n\t\t\t\tif (o.maxY <= my) continue;\n\t\t\t\tconst ax = o.a.x;\n\t\t\t\tconst bx = o.b.x;\n\t\t\t\tif (bx < mx && mx <= ax) dir = 1;\n\t\t\t\telse if (ax < mx && mx <= bx) dir = -1;\n\t\t\t\telse continue;\n\t\t\t\tif (o.minY <= my) {\n\t\t\t\t\tconst t = solveMonotone(o.pts, 0, mx);\n\t\t\t\t\tif (!(segmentPoint(o.pts, t)[1] > my)) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let k = 0; k < count; k++) if (o.mult[k] !== 0) winding[k] += dir * o.mult[k];\n\t\t}\n\t\tconst sideIsRight = horizontal ? edge.b.x < edge.a.x : edge.b.y > edge.a.y;\n\t\tconst r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);\n\t\tright.push(r);\n\t\tleft.push(r.map((w, k) => w + edge.mult[k]));\n\t}\n\treturn { edges: live, left, right, fillRules: operands.map((o) => o.fillRule), sources, eps, scale };\n}\n\n/** Edge indices bucketed into equal bands of one coordinate. */\nclass BandIndex {\n\tprivate readonly bands: number[][];\n\tprivate readonly min: number;\n\tprivate readonly size: number;\n\tconstructor(edges: Edge[], lo: (e: Edge) => number, hi: (e: Edge) => number) {\n\t\tlet min = Infinity;\n\t\tlet max = -Infinity;\n\t\tfor (const e of edges) {\n\t\t\tmin = Math.min(min, lo(e));\n\t\t\tmax = Math.max(max, hi(e));\n\t\t}\n\t\tconst count = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length) * 2)));\n\t\tthis.min = min;\n\t\tthis.size = max > min ? (max - min) / count : 1;\n\t\tthis.bands = Array.from({ length: count }, () => []);\n\t\tedges.forEach((e, i) => {\n\t\t\tconst from = this.index(lo(e));\n\t\t\tconst to = this.index(hi(e));\n\t\t\tfor (let b = from; b <= to; b++) this.bands[b].push(i);\n\t\t});\n\t}\n\tprivate index(v: number): number {\n\t\tconst k = Math.floor((v - this.min) / this.size);\n\t\treturn Math.max(0, Math.min(this.bands.length - 1, Number.isFinite(k) ? k : 0));\n\t}\n\tat(v: number): readonly number[] {\n\t\treturn this.bands[this.index(v)] ?? [];\n\t}\n}\n\nfunction insideFlags(windings: number[], fillRules: PolygonFillRule[]): boolean[] {\n\treturn windings.map((w, k) => (fillRules[k] === 'evenodd' ? (w & 1) === 1 : w !== 0));\n}\n\n// ---------------------------------------------------------------------------\n// Tracing and reconstruction\n// ---------------------------------------------------------------------------\n\ninterface DirectedEdge {\n\tfrom: Vertex;\n\tto: Vertex;\n\tpts: Point[];\n\tsrc: number;\n\tsFrom: number;\n\tsTo: number;\n\toutAngle: number;\n\tbackAngle: number;\n\tlength: number;\n}\n\nconst TWO_PI = Math.PI * 2;\nconst TIE = 1e-3;\n\nfunction directed(edge: Edge, forward: boolean): DirectedEdge {\n\tconst pts = forward ? edge.pts : [...edge.pts].reverse();\n\tconst out = segmentTangent(pts, 0);\n\tconst end = segmentTangent(pts, 1);\n\treturn {\n\t\tfrom: forward ? edge.a : edge.b,\n\t\tto: forward ? edge.b : edge.a,\n\t\tpts,\n\t\tsrc: edge.src,\n\t\tsFrom: forward ? edge.sa : edge.sb,\n\t\tsTo: forward ? edge.sb : edge.sa,\n\t\toutAngle: Math.atan2(out[1], out[0]),\n\t\tbackAngle: Math.atan2(-end[1], -end[0]),\n\t\tlength: dist(pts[0], pts[pts.length - 1]) + (pts.length > 2 ? flatness(pts) : 0)\n\t};\n}\n\n/** Angle of the chord from the start of `pts` to the point at fraction `t` (by parameter). */\nfunction chordAngle(pts: readonly Point[], t: number): number {\n\tconst p = segmentPoint(pts, t);\n\treturn Math.atan2(p[1] - pts[0][1], p[0] - pts[0][0]);\n}\n\nfunction mod2pi(a: number): number {\n\tconst r = a - Math.floor(a / TWO_PI) * TWO_PI;\n\treturn r >= TWO_PI ? 0 : r;\n}\n\n/** Traces the kept directed edges (result on the left) into rings. */\nfunction traceRings(edges: DirectedEdge[], scale: number): DirectedEdge[][] {\n\tconst outgoing = new Map<number, DirectedEdge[]>();\n\tfor (const e of edges) {\n\t\tconst list = outgoing.get(e.from.id);\n\t\tif (list) list.push(e);\n\t\telse outgoing.set(e.from.id, [e]);\n\t}\n\tconst next = (incoming: DirectedEdge): DirectedEdge | undefined => {\n\t\tconst candidates = outgoing.get(incoming.to.id)?.filter((c) => !used.has(c) || c === start);\n\t\tif (!candidates || candidates.length === 0) return undefined;\n\t\tif (candidates.length === 1) return candidates[0];\n\t\tconst turns = candidates.map((c) => mod2pi(incoming.backAngle - c.outAngle));\n\t\tconst tied = (x: number) => x < TIE || x > TWO_PI - TIE;\n\t\tlet ambiguous = turns.some(tied);\n\t\tif (!ambiguous) {\n\t\t\tconst sorted = [...turns].sort((a, b) => a - b);\n\t\t\tfor (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < TIE) ambiguous = true;\n\t\t}\n\t\tif (ambiguous) {\n\t\t\t// Tangent directions tie (tangent curves): compare chords a short way along each\n\t\t\t// curve instead, which separates curves by their curvature.\n\t\t\tconst back = [...incoming.pts].reverse();\n\t\t\tlet delta = Math.min(incoming.length, ...candidates.map((c) => c.length));\n\t\t\tdelta = Math.min(delta * 0.25, scale * 0.02);\n\t\t\tconst frac = (e: DirectedEdge) => Math.min(1, Math.max(1e-6, delta / Math.max(e.length, 1e-300)));\n\t\t\tconst backChord = chordAngle(back, frac(incoming));\n\t\t\tfor (let i = 0; i < candidates.length; i++) {\n\t\t\t\tturns[i] = mod2pi(backChord - chordAngle(candidates[i].pts, frac(candidates[i])));\n\t\t\t\tif (turns[i] < 1e-12) turns[i] = TWO_PI;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (let i = 0; i < turns.length; i++) if (turns[i] < 1e-15) turns[i] = TWO_PI;\n\t\t}\n\t\tlet best = 0;\n\t\tfor (let i = 1; i < candidates.length; i++) if (turns[i] < turns[best]) best = i;\n\t\treturn candidates[best];\n\t};\n\n\tconst used = new Set<DirectedEdge>();\n\tconst rings: DirectedEdge[][] = [];\n\tlet start: DirectedEdge | null = null;\n\tfor (const first of edges) {\n\t\tif (used.has(first)) continue;\n\t\tstart = first;\n\t\tconst ring: DirectedEdge[] = [];\n\t\tlet edge: DirectedEdge | undefined = first;\n\t\tlet guard = edges.length + 1;\n\t\tlet closed = false;\n\t\twhile (edge && guard-- > 0) {\n\t\t\tused.add(edge);\n\t\t\tring.push(edge);\n\t\t\tedge = next(edge);\n\t\t\tif (edge === first) {\n\t\t\t\tclosed = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!closed) throw new PathOpsError('result outline does not close');\n\t\trings.push(ring);\n\t}\n\treturn rings;\n}\n\nfunction mergeable(a: DirectedEdge, b: DirectedEdge, sources: Source[], eps: number): boolean {\n\tif (a.src === b.src && Math.abs(a.sTo - b.sFrom) < 1e-9 && a.sTo - a.sFrom > 0 === b.sTo - b.sFrom > 0) return true;\n\tif (a.pts.length === 2 && b.pts.length === 2) {\n\t\t// Collinear lines from different sources (e.g. shared rectangle sides).\n\t\tconst p = a.pts[0];\n\t\tconst q = b.pts[1];\n\t\tconst m = a.pts[1];\n\t\tconst chord = sub(q, p);\n\t\tconst len = Math.hypot(chord[0], chord[1]);\n\t\tif (len === 0) return false;\n\t\tconst off = Math.abs(cross(chord, sub(m, p))) / len;\n\t\tconst along = dot(chord, sub(m, p));\n\t\treturn off <= eps && along > 0 && along < len * len;\n\t}\n\tvoid sources;\n\treturn false;\n}\n\n/** Rebuilds a ring as segments: pieces of one source become one exact sub-curve. */\nfunction rebuildRing(ring: DirectedEdge[], sources: Source[], eps: number): BezierSegment[] {\n\tconst n = ring.length;\n\tlet startIndex = 0;\n\tfor (let k = 0; k < n; k++) {\n\t\tif (!mergeable(ring[(k + n - 1) % n], ring[k], sources, eps)) {\n\t\t\tstartIndex = k;\n\t\t\tbreak;\n\t\t}\n\t}\n\tconst ordered = [...ring.slice(startIndex), ...ring.slice(0, startIndex)];\n\tconst out: BezierSegment[] = [];\n\tlet i = 0;\n\twhile (i < ordered.length) {\n\t\tconst first = ordered[i];\n\t\tlet j = i + 1;\n\t\twhile (j < ordered.length && mergeable(ordered[j - 1], ordered[j], sources, eps)) j++;\n\t\tconst last = ordered[j - 1];\n\t\tif (j - i === 1) {\n\t\t\tout.push(first.pts);\n\t\t} else if (first.pts.length === 2 && ordered.slice(i, j).every((e) => e.pts.length === 2)) {\n\t\t\tout.push([first.from.p, last.to.p]);\n\t\t} else {\n\t\t\tconst source = sources[first.src];\n\t\t\tout.push(snapEnds(subSegment(source.seg, first.sFrom, last.sTo), first.from.p, last.to.p));\n\t\t}\n\t\ti = j;\n\t}\n\treturn out;\n}\n\ninterface Ring {\n\tsegs: BezierSegment[];\n\tarea: number;\n}\n\nfunction extractRings(arr: Arrangement, isInside: (flags: boolean[]) => boolean): Ring[] {\n\tconst kept: DirectedEdge[] = [];\n\tarr.edges.forEach((edge, i) => {\n\t\tconst l = isInside(insideFlags(arr.left[i], arr.fillRules));\n\t\tconst r = isInside(insideFlags(arr.right[i], arr.fillRules));\n\t\tif (l === r) return;\n\t\tkept.push(directed(edge, l));\n\t});\n\tconst rings: Ring[] = [];\n\tconst sliver = Math.max(arr.eps * 10, 1e-7 * arr.scale);\n\tfor (const ring of traceRings(kept, arr.scale)) {\n\t\tconst segs = rebuildRing(ring, arr.sources, arr.eps);\n\t\tconst area = contourSignedArea(segs);\n\t\tlet perimeter = 0;\n\t\tfor (const s of segs) perimeter += s.length === 2 ? dist(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);\n\t\tif (!(perimeter > 0) || (2 * Math.abs(area)) / perimeter < sliver) continue;\n\t\trings.push({ segs, area });\n\t}\n\treturn rings;\n}\n\n/** A point inside the region just left of a ring (for hole assignment). */\nfunction ringSample(ring: Ring): Point {\n\t// The midpoint of the longest segment: never a vertex shared with another ring.\n\tlet best = ring.segs[0];\n\tlet bestLen = -1;\n\tfor (const s of ring.segs) {\n\t\tconst l = dist(s[0], s[s.length - 1]);\n\t\tif (l > bestLen) {\n\t\t\tbest = s;\n\t\t\tbestLen = l;\n\t\t}\n\t}\n\treturn segmentPoint(best, 0.5);\n}\n\n/** Groups rings into outer rings with their holes (counter-clockwise outers in a y-up frame). */\nfunction groupRings(rings: Ring[]): Ring[][] {\n\tconst outers = rings.filter((r) => r.area > 0).sort((a, b) => b.area - a.area);\n\tconst holes = rings.filter((r) => r.area < 0);\n\tconst groups: Ring[][] = outers.map((o) => [o]);\n\tfor (const hole of holes) {\n\t\tconst sample = ringSample(hole);\n\t\tfor (let i = outers.length - 1; i >= 0; i--) {\n\t\t\tif (outers[i].area >= -hole.area && windingNumber([outers[i].segs], sample) !== 0) {\n\t\t\t\tgroups[i].push(hole);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn groups;\n}\n\nfunction ringsToPath(rings: Ring[]): BezierPath {\n\treturn contoursToPath(rings.map((r) => ({ segments: r.segs, closed: true })));\n}\n\nfunction predicate(operation: PathBooleanOperation): (flags: boolean[]) => boolean {\n\tswitch (operation) {\n\t\tcase 'union':\n\t\t\treturn (f) => f.some(Boolean);\n\t\tcase 'intersection':\n\t\t\treturn (f) => f.length > 0 && f.every(Boolean);\n\t\tcase 'difference':\n\t\t\treturn (f) => f[0] === true && !f.slice(1).some(Boolean);\n\t\tcase 'xor':\n\t\t\treturn (f) => f.filter(Boolean).length % 2 === 1;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Polygon fallback\n// ---------------------------------------------------------------------------\n\nfunction flattenOperand(input: PathOperandInput, tolerance: number) {\n\tconst path = isOperandObject(input) ? input.path : input;\n\tconst fillRule = isOperandObject(input) ? input.fillRule ?? 'nonzero' : 'nonzero';\n\tconst rings = pathToContours(path ?? '')\n\t\t.map((c) => flattenChain(closeContour(c, true), tolerance))\n\t\t.filter((r) => r.length >= 3 && r.every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1])));\n\treturn { rings, fillRule };\n}\n\nfunction multiToPath(multi: MultiPolygon, tolerance: number): BezierPath {\n\tif (multi.length === 0) return [];\n\treturn toBezierPath(polygonToSvgPath(multi, { curves: 'fit', curveTolerance: tolerance, precision: 9 }));\n}\n\nfunction reasonOf(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\n/**\n * Applies a boolean operation to any number of path operands, keeping curves:\n * - `union`: covered by any operand.\n * - `intersection`: covered by every operand.\n * - `difference`: the first operand minus all others.\n * - `xor`: covered by an odd number of operands (Exclude).\n *\n * Open subpaths are closed for filling. Non-finite input gives an empty path (`error`); with\n * `fallback: false` a failure of the curve engine throws a `PathOpsError`. The result is a set of simple closed subpaths that\n * never cross (they may touch at points): outer subpaths run counter-clockwise in a y-up\n * frame (clockwise on screen), holes the other way, so it fills the same with `nonzero`\n * and `evenodd`. Segments of the inputs are kept as exact sub-curves.\n */\nexport function pathBoolean(\n\toperation: PathBooleanOperation,\n\toperands: PathOperandInput[],\n\toptions: PathOpsOptions = {}\n): PathBooleanResult {\n\tif (operands.length === 0) return { path: [], engine: 'curve' };\n\ttry {\n\t\tconst arr = buildArrangement(operands, options);\n\t\treturn { path: ringsToPath(extractRings(arr, predicate(operation))), engine: 'curve' };\n\t} catch (error) {\n\t\tconst reason = reasonOf(error);\n\t\tif (reason === NON_FINITE) return { path: [], engine: 'curve', error: reason };\n\t\tif (options.fallback === false) throw error;\n\t\toptions.onFallback?.(reason);\n\t\tconst tolerance = options.fallbackTolerance ?? 0.05;\n\t\tconst multi = polygonBoolean(\n\t\t\toperation,\n\t\t\toperands.map((o) => flattenOperand(o, tolerance)),\n\t\t\t{ epsilon: options.epsilon }\n\t\t);\n\t\treturn { path: multiToPath(multi, tolerance), engine: 'polygon', fallbackReason: reason };\n\t}\n}\n\nexport const unitePaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('union', [a, b], options).path;\nexport const intersectPaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('intersection', [a, b], options).path;\nexport const subtractPaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('difference', [a, b], options).path;\nexport const excludePaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('xor', [a, b], options).path;\n\n/**\n * Splits the arrangement of the operands into its regions (Divide): every connected area\n * covered by the same set of operands becomes one piece (an outer subpath with its holes).\n * Pieces are ordered by operand set, then by area (largest first).\n */\nexport function dividePaths(operands: PathOperandInput[], options: PathOpsOptions = {}): DividePathsResult {\n\tif (operands.length === 0) return { pieces: [], engine: 'curve' };\n\ttry {\n\t\tconst arr = buildArrangement(operands, options);\n\t\tconst signatures = new Map<string, number[]>();\n\t\tconst note = (windings: number[]) => {\n\t\t\tconst flags = insideFlags(windings, arr.fillRules);\n\t\t\tif (!flags.some(Boolean)) return;\n\t\t\tconst key = flags.map((f) => (f ? '1' : '0')).join('');\n\t\t\tif (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => (f ? [k] : [])));\n\t\t};\n\t\tarr.edges.forEach((_, i) => {\n\t\t\tnote(arr.left[i]);\n\t\t\tnote(arr.right[i]);\n\t\t});\n\t\tconst pieces: PathPiece[] = [];\n\t\tfor (const key of [...signatures.keys()].sort()) {\n\t\t\tconst rings = extractRings(arr, (flags) => flags.map((f) => (f ? '1' : '0')).join('') === key);\n\t\t\tconst groups = groupRings(rings).sort((a, b) => b[0].area - a[0].area);\n\t\t\tfor (const group of groups) pieces.push({ path: ringsToPath(group), operands: signatures.get(key)! });\n\t\t}\n\t\treturn { pieces, engine: 'curve' };\n\t} catch (error) {\n\t\tconst reason = reasonOf(error);\n\t\tif (reason === NON_FINITE) return { pieces: [], engine: 'curve', error: reason };\n\t\tif (options.fallback === false) throw error;\n\t\toptions.onFallback?.(reason);\n\t\tconst tolerance = options.fallbackTolerance ?? 0.05;\n\t\tconst pieces = dividePolygons(\n\t\t\toperands.map((o) => flattenOperand(o, tolerance)),\n\t\t\t{ epsilon: options.epsilon }\n\t\t).map((p) => ({ path: multiToPath([p.polygon], tolerance), operands: p.operands }));\n\t\treturn { pieces, engine: 'polygon', fallbackReason: reason };\n\t}\n}\n\n/**\n * Resolves self-intersections and overlaps of one filled path into simple, consistently\n * oriented subpaths (outer subpaths and holes) under its fill rule.\n */\nexport function normalizeBezierPath(\n\tpath: BezierPathInput,\n\tfillRule: PolygonFillRule = 'nonzero',\n\toptions: PathOpsOptions = {}\n): BezierPath {\n\treturn pathBoolean('union', [{ path, fillRule }], options).path;\n}\n\n/** Filled area of a path under its fill rule (overlaps counted once; exact for curves). */\nexport function bezierPathArea(path: BezierPathInput, fillRule: PolygonFillRule = 'nonzero', options: PathOpsOptions = {}): number {\n\tconst normalized = normalizeBezierPath(path, fillRule, options);\n\tlet sum = 0;\n\tfor (const contour of pathToContours(normalized)) sum += contourSignedArea(closeContour(contour, true));\n\treturn Math.abs(sum);\n}\n\n/**\n * Normalised rings of a filled path with their nesting: used by offset / outline stroke to\n * drop spurious contours. `parent` is the index of the smallest enclosing ring.\n */\nexport interface NestedRing {\n\tsegments: BezierSegment[];\n\tarea: number;\n\tparent: number;\n}\n\n/** Internal: rings of a boolean result with their containment tree. */\nexport function pathBooleanRings(\n\toperation: PathBooleanOperation | ((flags: boolean[]) => boolean),\n\toperands: PathOperandInput[],\n\toptions: PathOpsOptions = {}\n): NestedRing[] {\n\tconst arr = buildArrangement(operands, options);\n\tconst rings = extractRings(arr, typeof operation === 'function' ? operation : predicate(operation));\n\tconst order = rings.map((_, i) => i).sort((a, b) => Math.abs(rings[a].area) - Math.abs(rings[b].area));\n\treturn rings.map((ring, i) => {\n\t\tconst sample = ringSample(ring);\n\t\tlet parent = -1;\n\t\tfor (const j of order) {\n\t\t\tif (j === i || Math.abs(rings[j].area) <= Math.abs(ring.area)) continue;\n\t\t\tif (windingNumber([rings[j].segs], sample) !== 0) {\n\t\t\t\tparent = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn { segments: ring.segs, area: ring.area, parent };\n\t});\n}\n\n/** Contours of nested rings, keeping the ones `keep` accepts. */\nexport function nestedRingsToPath(rings: NestedRing[], keep: (index: number) => boolean = () => true): BezierPath {\n\tconst contours: BezierContour[] = [];\n\trings.forEach((r, i) => {\n\t\tif (keep(i)) contours.push({ segments: r.segments, closed: true });\n\t});\n\treturn contoursToPath(contours);\n}\n","/**\n * Offset Path and Outline Stroke on Bézier paths.\n *\n * The stroker offsets every segment by ± half the width (lines exactly, curves by cubic\n * approximations refined until they stay within `tolerance` of the true offset), joins the\n * pieces (miter with a miter limit, round or bevel on the outer side of a turn; through the\n * vertex on the inner side) and adds caps (butt, round, square) to open subpaths. Dashes\n * follow arc length, restart on every subpath and join across the start of a closed\n * subpath. The raw outline overlaps itself, so it is filled with the non-zero rule and\n * normalised with the curve boolean engine.\n *\n * When the stroke is wider than a curve's radius of curvature the inner offset turns inside\n * out and its loops cancel winding, leaving holes that are really covered. Every face of\n * the true result is bounded by the arrangement, so a face is spurious exactly when a point\n * deep inside it is closer to the path than the offset distance (holes when growing,\n * islands when insetting); such faces are dropped.\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/offset.rs,\n * vectorcraft-main/crates/pathops/src/boolean.rs (`try_merge`) and\n * vectorcraft-main/crates/effects/src/stroke/dash.rs (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../types.js';\nimport type { PolygonFillRule } from '../polygonBoolean.js';\nimport { fitCubicRun } from '../curveFit.js';\nimport {\n\tadd,\n\tcloseContour,\n\tcontoursToPath,\n\tcross,\n\tdist,\n\tdistanceToBezierPath,\n\tdot,\n\tflattenChain,\n\tpathToContours,\n\tsamePoint,\n\tscale,\n\tsegmentCurvature,\n\tsegmentLength,\n\tsegmentNearest,\n\tsegmentPoint,\n\tsegmentTangent,\n\tsplitSegment,\n\tsub,\n\ttrimSegments,\n\ttype BezierContour,\n\ttype BezierPath,\n\ttype BezierPathInput,\n\ttype BezierSegment\n} from './bezier.js';\nimport {\n\tnestedRingsToPath,\n\tpathBoolean,\n\tpathBooleanRings,\n\ttype NestedRing,\n\ttype PathOperandInput,\n\ttype PathOpsOptions\n} from './boolean.js';\n\n/** Line join (as SVG `stroke-linejoin`). */\nexport type StrokeJoin = 'miter' | 'round' | 'bevel';\n/** Line cap (as SVG `stroke-linecap`). */\nexport type StrokeCap = 'butt' | 'round' | 'square';\n\nexport interface OutlineStrokeOptions extends PathOpsOptions {\n\t/** Stroke width. Non-positive or non-finite widths give an empty result. */\n\twidth: number;\n\t/** Default `miter` (as SVG). */\n\tjoin?: StrokeJoin;\n\t/** Default `butt` (as SVG). */\n\tcap?: StrokeCap;\n\t/** Miter length / stroke width above which a miter join is bevelled (default 4, as SVG). */\n\tmiterLimit?: number;\n\t/** Dash pattern (as SVG `stroke-dasharray`); odd patterns repeat twice. */\n\tdash?: readonly number[];\n\t/** Distance into the dash pattern at the start of each subpath (as `stroke-dashoffset`). */\n\tdashOffset?: number;\n\t/** Maximum distance between the approximated and the true offset curve (default 0.01). */\n\ttolerance?: number;\n}\n\nexport interface OffsetPathOptions extends PathOpsOptions {\n\t/** Corner style of the grown / shrunk outline (default `miter`). */\n\tjoin?: StrokeJoin;\n\t/** Miter limit (default 4). */\n\tmiterLimit?: number;\n\t/** Fill rule of the input (default `nonzero`). */\n\tfillRule?: PolygonFillRule;\n\t/** Maximum distance between the approximated and the true offset curve (default 0.01). */\n\ttolerance?: number;\n}\n\nconst DEFAULT_TOLERANCE = 0.01;\n\nconst normal = (t: Point): Point => [-t[1], t[0]];\n\nfunction raise(seg: BezierSegment): Point[] {\n\tif (seg.length !== 3) return [...seg];\n\tconst [p0, p1, p2] = seg;\n\treturn [p0, [p0[0] + (2 / 3) * (p1[0] - p0[0]), p0[1] + (2 / 3) * (p1[1] - p0[1])], [p2[0] + (2 / 3) * (p1[0] - p2[0]), p2[1] + (2 / 3) * (p1[1] - p2[1])], p2];\n}\n\nfunction isDegenerate(seg: BezierSegment, tol: number): boolean {\n\treturn seg.every((p) => dist(p, seg[0]) <= tol);\n}\n\n// ---------------------------------------------------------------------------\n// Offsetting segments\n// ---------------------------------------------------------------------------\n\n/** Appends cubic / line approximations of the curve offset by `d` (left in a y-up frame). */\nfunction offsetSegment(seg: BezierSegment, d: number, tol: number, out: Point[][], depth = 0) {\n\tif (seg.length === 2) {\n\t\tconst n = normal(segmentTangent(seg, 0));\n\t\tout.push([add(seg[0], scale(n, d)), add(seg[1], scale(n, d))]);\n\t\treturn;\n\t}\n\tconst c = raise(seg);\n\tconst t0 = segmentTangent(c, 0);\n\tconst t1 = segmentTangent(c, 1);\n\tconst q0 = add(c[0], scale(normal(t0), d));\n\tconst q3 = add(c[3], scale(normal(t1), d));\n\tconst k0 = segmentCurvature(c, 0);\n\tconst k1 = segmentCurvature(c, 1);\n\tconst f0 = Number.isFinite(k0) ? 1 - d * k0 : 1;\n\tconst f1 = Number.isFinite(k1) ? 1 - d * k1 : 1;\n\tconst q1 = add(q0, scale(sub(c[1], c[0]), f0));\n\tconst q2 = add(q3, scale(sub(c[2], c[3]), f1));\n\tconst approx: Point[] = [q0, q1, q2, q3];\n\tif (depth < 10) {\n\t\tlet err = 0;\n\t\tfor (const t of [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]) {\n\t\t\tconst target = add(segmentPoint(c, t), scale(normal(segmentTangent(c, t)), d));\n\t\t\terr = Math.max(err, segmentNearest(approx, target).distance);\n\t\t\tif (err > tol) break;\n\t\t}\n\t\t// The approximation must not wander off either: check its midpoint against the curve.\n\t\tif (err <= tol) {\n\t\t\tconst m = segmentPoint(approx, 0.5);\n\t\t\tconst back = segmentNearest(c, m).distance;\n\t\t\terr = Math.max(err, Math.abs(back - Math.abs(d)));\n\t\t}\n\t\tif (err > tol) {\n\t\t\tconst [a, b] = splitSegment(c, 0.5);\n\t\t\toffsetSegment(a, d, tol, out, depth + 1);\n\t\t\toffsetSegment(b, d, tol, out, depth + 1);\n\t\t\treturn;\n\t\t}\n\t}\n\tout.push(approx);\n}\n\n/** Appends a circular arc (center, radius, start angle, sweep) as cubics, at most 90° each. */\nfunction pushArc(out: Point[][], center: Point, radius: number, start: number, sweep: number, from: Point, to: Point) {\n\tconst n = Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2) - 1e-9));\n\tconst step = sweep / n;\n\tconst k = (4 / 3) * Math.tan(step / 4);\n\tlet prev = from;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst a0 = start + step * i;\n\t\tconst a1 = a0 + step;\n\t\tconst c0 = Math.cos(a0);\n\t\tconst s0 = Math.sin(a0);\n\t\tconst c1 = Math.cos(a1);\n\t\tconst s1 = Math.sin(a1);\n\t\tconst end: Point = i === n - 1 ? to : [center[0] + radius * c1, center[1] + radius * s1];\n\t\tout.push([\n\t\t\tprev,\n\t\t\t[center[0] + radius * (c0 - k * s0), center[1] + radius * (s0 + k * c0)],\n\t\t\t[center[0] + radius * (c1 + k * s1), center[1] + radius * (s1 - k * c1)],\n\t\t\tend\n\t\t]);\n\t\tprev = end;\n\t}\n}\n\nfunction lineTo(out: Point[][], from: Point, to: Point) {\n\tif (!samePoint(from, to)) out.push([from, to]);\n}\n\ninterface JoinStyle {\n\tjoin: StrokeJoin;\n\tmiterLimit: number;\n}\n\n/** Joins the offset at vertex `p` from the incoming tangent `tin` to the outgoing `tout`. */\nfunction addJoin(out: Point[][], p: Point, tin: Point, tout: Point, d: number, style: JoinStyle) {\n\tconst from = add(p, scale(normal(tin), d));\n\tconst to = add(p, scale(normal(tout), d));\n\tconst turn = cross(tin, tout);\n\tconst cos = dot(tin, tout);\n\tif (Math.abs(turn) < 1e-9 && cos > 0) {\n\t\tlineTo(out, from, to);\n\t\treturn;\n\t}\n\tconst reversal = Math.abs(turn) < 1e-9 && cos < 0;\n\tconst inner = !reversal && turn > 0 === d > 0;\n\tif (inner) {\n\t\tlineTo(out, from, p);\n\t\tlineTo(out, p, to);\n\t\treturn;\n\t}\n\tconst theta = reversal ? (d > 0 ? -Math.PI : Math.PI) : Math.atan2(turn, cos);\n\tif (style.join === 'round') {\n\t\tconst v = sub(from, p);\n\t\tpushArc(out, p, Math.abs(d), Math.atan2(v[1], v[0]), theta, from, to);\n\t\treturn;\n\t}\n\tif (style.join === 'miter' && !reversal) {\n\t\t// Miter length / width = 1 / sin(φ / 2), φ the angle between the segments.\n\t\tconst phi = Math.PI - Math.abs(theta);\n\t\tconst ratio = 1 / Math.max(Math.sin(phi / 2), 1e-12);\n\t\tif (ratio <= style.miterLimit) {\n\t\t\tconst nin = normal(tin);\n\t\t\tconst nout = normal(tout);\n\t\t\tconst s = d / (1 + dot(nin, nout));\n\t\t\tconst miter = add(p, scale(add(nin, nout), s));\n\t\t\tlineTo(out, from, miter);\n\t\t\tlineTo(out, miter, to);\n\t\t\treturn;\n\t\t}\n\t}\n\tlineTo(out, from, to);\n}\n\n/** One side of a segment chain offset by `d`, with joins; closed chains join their ends too. */\nfunction offsetChain(segs: BezierSegment[], d: number, closed: boolean, style: JoinStyle, tol: number): Point[][] {\n\tconst out: Point[][] = [];\n\tconst pen = () => (out.length > 0 ? out[out.length - 1][out[out.length - 1].length - 1] : null);\n\tfor (let i = 0; i < segs.length; i++) {\n\t\tif (i > 0) addJoin(out, segs[i][0], segmentTangent(segs[i - 1], 1), segmentTangent(segs[i], 0), d, style);\n\t\tconst pieces: Point[][] = [];\n\t\toffsetSegment(segs[i], d, tol, pieces);\n\t\tfor (const piece of pieces) {\n\t\t\tconst at = pen();\n\t\t\tif (at && !samePoint(at, piece[0])) lineTo(out, at, piece[0]);\n\t\t\tout.push(piece);\n\t\t}\n\t}\n\tif (closed && segs.length > 0) {\n\t\tconst last = segs[segs.length - 1];\n\t\taddJoin(out, segs[0][0], segmentTangent(last, 1), segmentTangent(segs[0], 0), d, style);\n\t\tconst at = pen();\n\t\tif (at && out.length > 0 && !samePoint(at, out[0][0])) lineTo(out, at, out[0][0]);\n\t}\n\treturn out;\n}\n\nfunction reverseChain(chain: Point[][]): Point[][] {\n\treturn chain.map((s) => [...s].reverse()).reverse();\n}\n\nfunction addCap(out: Point[][], p: Point, t: Point, h: number, cap: StrokeCap) {\n\t// From p + h·n (left) round the end to p − h·n (right), t pointing out of the stroke.\n\tconst n = normal(t);\n\tconst from = add(p, scale(n, h));\n\tconst to = add(p, scale(n, -h));\n\tif (cap === 'round') {\n\t\tpushArc(out, p, h, Math.atan2(n[1], n[0]), -Math.PI, from, to);\n\t} else if (cap === 'square') {\n\t\tconst a = add(from, scale(t, h));\n\t\tconst b = add(to, scale(t, h));\n\t\tlineTo(out, from, a);\n\t\tlineTo(out, a, b);\n\t\tlineTo(out, b, to);\n\t} else {\n\t\tlineTo(out, from, to);\n\t}\n}\n\n/** Closed outline rings painting a dot (zero-length dash or subpath) with a cap. */\nfunction dotRings(p: Point, t: Point, h: number, cap: StrokeCap): Point[][][] {\n\tif (cap === 'butt') return [];\n\tconst ring: Point[][] = [];\n\taddCap(ring, p, t, h, cap);\n\taddCap(ring, p, [-t[0], -t[1]], h, cap);\n\treturn [ring];\n}\n\ninterface StrokeStyle extends JoinStyle {\n\tcap: StrokeCap;\n}\n\n/** Raw outline rings (overlapping, to be filled with `nonzero`) of one subpath. */\nfunction strokeContour(contour: BezierContour, h: number, style: StrokeStyle, tol: number): Point[][][] {\n\tconst tiny = Math.max(1e-12, tol * 1e-6);\n\tconst all = contour.closed ? closeContour(contour) : contour.segments;\n\tconst segs = all.filter((s) => !isDegenerate(s, tiny));\n\tif (segs.length === 0) {\n\t\tconst p = contour.segments[0]?.[0];\n\t\treturn p ? dotRings(p, [1, 0], h, style.cap) : [];\n\t}\n\tif (contour.closed) {\n\t\tconst left = offsetChain(segs, h, true, style, tol);\n\t\tconst right = offsetChain(segs, -h, true, style, tol);\n\t\treturn [left, reverseChain(right)];\n\t}\n\tconst left = offsetChain(segs, h, false, style, tol);\n\tconst right = reverseChain(offsetChain(segs, -h, false, style, tol));\n\tconst ring: Point[][] = [...left];\n\tconst last = segs[segs.length - 1];\n\taddCap(ring, last[last.length - 1], segmentTangent(last, 1), h, style.cap);\n\tconst joinTo = (p: Point) => {\n\t\tconst at = ring[ring.length - 1][ring[ring.length - 1].length - 1];\n\t\tlineTo(ring, at, p);\n\t};\n\tif (right.length > 0) joinTo(right[0][0]);\n\tring.push(...right);\n\tconst first = segs[0];\n\tconst t0 = segmentTangent(first, 0);\n\taddCap(ring, first[0], [-t0[0], -t0[1]], h, style.cap);\n\tif (ring.length > 0) joinTo(ring[0][0]);\n\treturn [ring];\n}\n\nfunction ringsToPath(rings: Point[][][]): BezierPath {\n\treturn contoursToPath(rings.filter((r) => r.length > 0).map((segments) => ({ segments, closed: true })));\n}\n\n// ---------------------------------------------------------------------------\n// Dashes\n// ---------------------------------------------------------------------------\n\n/** More dashes than this draws the line solid (a tiny pattern on a long path). */\nconst MAX_DASHES = 200_000;\nconst MIN_ENTRY = 1e-6;\n\nexport interface DashedPath {\n\t/** The dashes, as open subpaths. */\n\tcontours: BezierContour[];\n\t/** Zero-length dashes: where they sit and the path direction there. */\n\tdots: { point: Point; tangent: Point }[];\n}\n\n/**\n * Applies a dash pattern (SVG `stroke-dasharray` / `stroke-dashoffset` semantics) to the\n * subpaths. Returns `null` when the pattern is invalid (negative or non-finite entries),\n * has no positive entry, or would make more than 200,000 dashes: the line is solid.\n */\nexport function dashContours(contours: readonly BezierContour[], pattern: readonly number[], offset = 0): DashedPath | null {\n\tif (pattern.length === 0 || pattern.some((v) => !Number.isFinite(v) || v < 0) || !Number.isFinite(offset)) return null;\n\tif (!pattern.some((v) => v > MIN_ENTRY)) return null;\n\tconst pat = pattern.length % 2 === 1 ? [...pattern, ...pattern] : [...pattern];\n\tconst period = pat.reduce((s, v) => s + v, 0);\n\tconst chains = contours.map((c) => {\n\t\tconst segs = closeContour(c);\n\t\tconst lengths = segs.map((s) => segmentLength(s));\n\t\treturn { segs, lengths, total: lengths.reduce((s, v) => s + v, 0), closed: c.closed };\n\t});\n\tconst totalLength = chains.reduce((s, c) => s + c.total, 0);\n\tif ((totalLength / period) * pat.length > MAX_DASHES) return null;\n\tlet off = ((offset % period) + period) % period;\n\tlet ix0 = 0;\n\twhile (off > pat[ix0]) {\n\t\toff -= pat[ix0];\n\t\tix0 = (ix0 + 1) % pat.length;\n\t}\n\tconst out: DashedPath = { contours: [], dots: [] };\n\tfor (const chain of chains) {\n\t\tif (chain.segs.length === 0 || !(chain.total > 0)) continue;\n\t\tlet ix = ix0;\n\t\tlet rem = pat[ix0] - off;\n\t\tlet pos = 0;\n\t\tconst dashes: { from: number; to: number }[] = [];\n\t\tlet guard = 0;\n\t\twhile (pos < chain.total && guard++ < MAX_DASHES * 2) {\n\t\t\tconst step = Math.min(rem, chain.total - pos);\n\t\t\tconst on = ix % 2 === 0;\n\t\t\tif (on) dashes.push({ from: pos, to: pos + step });\n\t\t\tpos += step;\n\t\t\trem -= step;\n\t\t\tif (rem <= 1e-12) {\n\t\t\t\tix = (ix + 1) % pat.length;\n\t\t\t\trem = pat[ix];\n\t\t\t}\n\t\t}\n\t\t// A closed subpath's last dash runs on into its first one.\n\t\tif (chain.closed && dashes.length > 1) {\n\t\t\tconst first = dashes[0];\n\t\t\tconst last = dashes[dashes.length - 1];\n\t\t\tif (first.from <= 1e-9 && last.to >= chain.total - 1e-9 && first.to > first.from && last.to > last.from) {\n\t\t\t\tdashes.pop();\n\t\t\t\tconst tail = trimSegments(chain.segs, last.from, chain.total, chain.lengths);\n\t\t\t\tconst head = trimSegments(chain.segs, 0, first.to, chain.lengths);\n\t\t\t\tdashes.shift();\n\t\t\t\tout.contours.push({ segments: [...tail, ...head], closed: false });\n\t\t\t}\n\t\t}\n\t\tfor (const dash of dashes) {\n\t\t\tif (dash.to - dash.from <= 1e-12) {\n\t\t\t\tconst at = locate(chain.segs, chain.lengths, dash.from);\n\t\t\t\tout.dots.push(at);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst segments = trimSegments(chain.segs, dash.from, dash.to, chain.lengths);\n\t\t\tif (segments.length > 0) out.contours.push({ segments, closed: false });\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction locate(segs: BezierSegment[], lengths: number[], at: number): { point: Point; tangent: Point } {\n\tlet acc = 0;\n\tfor (let i = 0; i < segs.length; i++) {\n\t\tif (at <= acc + lengths[i] || i === segs.length - 1) {\n\t\t\tconst piece = trimSegments([segs[i]], 0, Math.max(0, at - acc), [lengths[i]]);\n\t\t\tconst t = piece.length > 0 ? 1 : 0;\n\t\t\tconst point = piece.length > 0 ? piece[piece.length - 1][piece[piece.length - 1].length - 1] : segs[i][0];\n\t\t\tconst tangent = segmentTangent(piece.length > 0 ? piece[piece.length - 1] : segs[i], t);\n\t\t\treturn { point, tangent };\n\t\t}\n\t\tacc += lengths[i];\n\t}\n\treturn { point: segs[0][0], tangent: segmentTangent(segs[0], 0) };\n}\n\n// ---------------------------------------------------------------------------\n// Result cleanup\n// ---------------------------------------------------------------------------\n\n/** A point deep inside a face (ring plus its direct child rings): the middle of its widest span. */\nfunction deepPoint(rings: BezierSegment[][]): Point | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const p of rings[0].flat()) {\n\t\tminX = Math.min(minX, p[0]);\n\t\tmaxX = Math.max(maxX, p[0]);\n\t\tminY = Math.min(minY, p[1]);\n\t\tmaxY = Math.max(maxY, p[1]);\n\t}\n\tconst w = maxX - minX;\n\tconst h = maxY - minY;\n\tif (!(w > 0 && h > 0)) return null;\n\tconst tolerance = Math.max(1e-6, 0.002 * Math.min(w, h));\n\tconst polys = rings.map((r) => flattenChain(r, tolerance));\n\tlet best: { span: number; p: Point } | null = null;\n\tfor (const f of [0.5, 0.3, 0.7, 0.15, 0.85]) {\n\t\tconst y = minY + h * f;\n\t\tconst xs: number[] = [];\n\t\tfor (const poly of polys) {\n\t\t\tfor (let i = 0; i < poly.length; i++) {\n\t\t\t\tconst a = poly[i];\n\t\t\t\tconst b = poly[(i + 1) % poly.length];\n\t\t\t\tif (a[1] <= y !== b[1] <= y) xs.push(a[0] + ((y - a[1]) / (b[1] - a[1])) * (b[0] - a[0]));\n\t\t\t}\n\t\t}\n\t\txs.sort((p, q) => p - q);\n\t\tfor (let i = 0; i + 1 < xs.length; i += 2) {\n\t\t\tconst span = xs[i + 1] - xs[i];\n\t\t\tif (!best || span > best.span) best = { span, p: [(xs[i] + xs[i + 1]) / 2, y] };\n\t\t}\n\t}\n\treturn best?.p ?? null;\n}\n\n/** Keeps the faces that `spurious` does not flag (a flagged face drops its descendants too). */\nfunction dropSpurious(rings: NestedRing[], spurious: (face: BezierSegment[][], ring: NestedRing) => boolean): BezierPath {\n\tconst children = rings.map(() => [] as number[]);\n\trings.forEach((r, i) => {\n\t\tif (r.parent >= 0) children[r.parent].push(i);\n\t});\n\tconst marked = rings.map((r, i) => spurious([r.segments, ...children[i].map((c) => rings[c].segments)], r));\n\tconst hasMarkedAncestor = (i: number): boolean => {\n\t\tfor (let k = i; k >= 0; k = rings[k].parent) if (marked[k]) return true;\n\t\treturn false;\n\t};\n\treturn nestedRingsToPath(rings, (i) => !hasMarkedAncestor(i));\n}\n\n/**\n * Merges runs of tangent-continuous curve segments that one cubic fits within `tolerance`\n * (the stroker's many small offset pieces), keeping corners.\n */\nfunction mergeSmoothRuns(path: BezierPath, tolerance: number): BezierPath {\n\tconst contours = pathToContours(path).map((contour) => {\n\t\tconst segs = closeContour(contour, true).map((s) => (s.length === 3 ? raise(s) : [...s]));\n\t\tconst n = segs.length;\n\t\tif (n < 2) return { segments: segs, closed: true };\n\t\tconst smooth = (a: BezierSegment, b: BezierSegment) => {\n\t\t\tif (a.length !== 4 || b.length !== 4) return false;\n\t\t\tconst ta = segmentTangent(a, 1);\n\t\t\tconst tb = segmentTangent(b, 0);\n\t\t\treturn dot(ta, tb) > 0.9998 && Math.abs(cross(ta, tb)) < 0.02;\n\t\t};\n\t\tlet start = 0;\n\t\tfor (let k = 0; k < n; k++) {\n\t\t\tif (!smooth(segs[(k + n - 1) % n], segs[k])) {\n\t\t\t\tstart = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst ordered = [...segs.slice(start), ...segs.slice(0, start)];\n\t\tconst out: BezierSegment[] = [];\n\t\tlet i = 0;\n\t\twhile (i < ordered.length) {\n\t\t\tlet current = ordered[i];\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < ordered.length && smooth(ordered[j - 1], ordered[j])) {\n\t\t\t\tconst run = ordered.slice(i, j + 1);\n\t\t\t\tconst pts: Point[] = [];\n\t\t\t\trun.forEach((s, k) => {\n\t\t\t\t\tfor (let q = k === 0 ? 0 : 1; q <= 12; q++) pts.push(segmentPoint(s, q / 12));\n\t\t\t\t});\n\t\t\t\tconst fit = fitCubicRun(pts, tolerance, segmentTangent(run[0], 0), segmentTangent(run[run.length - 1], 1));\n\t\t\t\tif (fit.length !== 1) break;\n\t\t\t\tconst c = fit[0];\n\t\t\t\tcurrent = [run[0][0], c.p1, c.p2, run[run.length - 1][3]];\n\t\t\t\tj++;\n\t\t\t}\n\t\t\tout.push(current);\n\t\t\ti = j;\n\t\t}\n\t\treturn { segments: out, closed: true };\n\t});\n\treturn contoursToPath(contours);\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\nfunction joinStyle(join: StrokeJoin | undefined, miterLimit: number | undefined): JoinStyle {\n\treturn {\n\t\tjoin: join ?? 'miter',\n\t\tmiterLimit: miterLimit !== undefined && Number.isFinite(miterLimit) ? Math.max(1, miterLimit) : 4\n\t};\n}\n\n/**\n * The raw (overlapping) outline of a stroke as closed subpaths to fill with `nonzero`.\n * `outlineStroke` normalises it; this is useful for previews.\n */\nexport function strokeOutlineRings(input: BezierPathInput, options: OutlineStrokeOptions): BezierPath {\n\tconst h = options.width / 2;\n\tif (!(h > 0) || !Number.isFinite(h)) return [];\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;\n\tconst style: StrokeStyle = { ...joinStyle(options.join, options.miterLimit), cap: options.cap ?? 'butt' };\n\tlet contours = pathToContours(input);\n\tconst rings: Point[][][] = [];\n\tif (options.dash && options.dash.length > 0) {\n\t\tconst dashed = dashContours(contours, options.dash, options.dashOffset ?? 0);\n\t\tif (dashed) {\n\t\t\tcontours = dashed.contours;\n\t\t\tfor (const dot of dashed.dots) rings.push(...dotRings(dot.point, dot.tangent, h, style.cap));\n\t\t}\n\t}\n\tfor (const contour of contours) rings.push(...strokeContour(contour, h, style, tol));\n\treturn ringsToPath(rings);\n}\n\n/**\n * Outline Stroke: the filled area painted by stroking `path`, as one clean path (overlaps\n * removed, consistently oriented). Joins, caps, miter limit and dashes follow SVG.\n */\nexport function outlineStroke(input: BezierPathInput, options: OutlineStrokeOptions): BezierPath {\n\tconst h = options.width / 2;\n\tif (!(h > 0) || !Number.isFinite(h)) return [];\n\tlet source: BezierPath;\n\ttry {\n\t\tsource = contoursToPath(pathToContours(input));\n\t} catch {\n\t\treturn [];\n\t}\n\tif (source.some((c) => c.type !== 'Z' && Object.values(c).some((v) => typeof v === 'number' && !Number.isFinite(v)))) return [];\n\tconst raw = strokeOutlineRings(source, options);\n\tif (raw.length === 0) return [];\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;\n\tconst sourceContours = pathToContours(source);\n\tconst operand: PathOperandInput = { path: raw, fillRule: 'nonzero' };\n\ttry {\n\t\tconst rings = pathBooleanRings('union', [operand], options);\n\t\tconst cleaned = dropSpurious(rings, (face, ring) => {\n\t\t\tif (ring.area >= 0) return false;\n\t\t\tconst p = deepPoint(face);\n\t\t\treturn !!p && distanceToBezierPath(sourceContours, p) < h * (1 - 1e-4) - 1e-6;\n\t\t});\n\t\treturn mergeSmoothRuns(cleaned, tol);\n\t} catch {\n\t\treturn pathBoolean('union', [operand], options).path;\n\t}\n}\n\n/**\n * Offset Path: grows (`distance > 0`) or shrinks (`distance < 0`) the filled area by\n * `distance`. Closed subpaths are a filled region (read with `fillRule`); open subpaths are\n * outlined with a stroke of width `2·|distance|` and butt caps (when growing). Loops of the\n * offset curves are removed.\n */\nexport function offsetPath(input: BezierPathInput, distance: number, options: OffsetPathOptions = {}): BezierPath {\n\tif (!Number.isFinite(distance)) return [];\n\tconst contours = pathToContours(input);\n\tif (contours.some((c) => c.segments.some((s) => s.some((p) => !Number.isFinite(p[0]) || !Number.isFinite(p[1]))))) return [];\n\tconst closed = contours.filter((c) => c.closed && c.segments.length > 0);\n\tconst open = contours.filter((c) => !c.closed && c.segments.length > 0);\n\tconst fillRule = options.fillRule ?? 'nonzero';\n\tconst fill: PathOperandInput = { path: contoursToPath(closed), fillRule };\n\tconst d = Math.abs(distance);\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;\n\tif (d === 0) return pathBoolean('union', [fill], options).path;\n\tconst strokeOptions: OutlineStrokeOptions = { width: 2 * d, join: options.join ?? 'miter', miterLimit: options.miterLimit, cap: 'butt', tolerance: tol };\n\tconst ring: PathOperandInput = { path: strokeOutlineRings(contoursToPath(closed), strokeOptions), fillRule: 'nonzero' };\n\tconst openRing: PathOperandInput = {\n\t\tpath: distance > 0 ? strokeOutlineRings(contoursToPath(open), strokeOptions) : [],\n\t\tfillRule: 'nonzero'\n\t};\n\tconst grow = distance > 0;\n\tconst operands = [fill, ring, openRing];\n\ttry {\n\t\tconst rings = pathBooleanRings(grow ? (m) => m[0] || m[1] || m[2] : (m) => m[0] && !m[1], operands, options);\n\t\tconst sourceContours = contours;\n\t\tconst cleaned = dropSpurious(rings, (face, r) => {\n\t\t\tif (grow ? r.area >= 0 : r.area <= 0) return false;\n\t\t\tconst p = deepPoint(face);\n\t\t\treturn !!p && distanceToBezierPath(sourceContours, p) < d * (1 - 1e-4) - 1e-6;\n\t\t});\n\t\treturn mergeSmoothRuns(cleaned, tol);\n\t} catch {\n\t\treturn grow\n\t\t\t? pathBoolean('union', operands, options).path\n\t\t\t: pathBoolean('difference', [fill, ring], options).path;\n\t}\n}\n","/**\n * Shape nodes and Freeform-style shape boolean operations (Unite / Intersect / Subtract /\n * Exclude / Divide) on canvas nodes.\n *\n * A shape node is any node whose `data.shape` is a `ShapeDescription`. Its geometry is\n * relative to the node box, so moving or resizing the node moves / resizes the shape.\n */\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\twithStyleSize,\n\tLOCKED_DATA_KEY,\n\tLOCKED_PREVIOUS_FLAGS_KEY,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tdividePolygons,\n\tmultiPolygonBounds,\n\tpolygonBoolean,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule,\n\ttype PolygonRing\n} from './polygonBoolean.js';\nimport {\n\tgetShapeSvg,\n\tisShapeDescription,\n\tpolygonToSvgPath,\n\tshapeToPolygon,\n\ttype PathShape,\n\ttype ShapeDescription,\n\ttype ShapeSize\n} from './shapeGeometry.js';\nimport {\n\tbezierPathBounds,\n\tbezierPathToSvg,\n\ttoBezierPath,\n\ttransformBezierPath,\n\ttype AffineMatrix,\n\ttype BezierPath\n} from './pathops/bezier.js';\nimport { dividePaths, pathBoolean, type PathOpsReport } from './pathops/boolean.js';\nimport { offsetPath, outlineStroke, type StrokeCap, type StrokeJoin } from './pathops/stroke.js';\nimport type { CanvasNode, Point } from './types.js';\n\n/** Data key holding a node's shape description. */\nexport const SHAPE_DATA_KEY = 'shape';\n\n/** Node type suggested for shape nodes (register `ShapeNode` under it). */\nexport const SHAPE_NODE_TYPE = 'shape';\n\n/** Freeform's shape operations. `exclude` is xor; `divide` splits into every region. */\nexport type ShapeOperation = 'unite' | 'intersect' | 'subtract' | 'exclude' | 'divide';\n\nexport const SHAPE_OPERATIONS: readonly ShapeOperation[] = ['unite', 'intersect', 'subtract', 'exclude', 'divide'];\n\nconst BOOLEAN_FOR: Record<Exclude<ShapeOperation, 'divide'>, PolygonBooleanOperation> = {\n\tunite: 'union',\n\tintersect: 'intersection',\n\tsubtract: 'difference',\n\texclude: 'xor'\n};\n\n/** Whether a node carries a supported `data.shape`. */\nexport function isShapeNode(node: CanvasNode | undefined | null): boolean {\n\treturn !!node && isShapeDescription(node.data?.[SHAPE_DATA_KEY]);\n}\n\n/** The node's shape description, or `null`. */\nexport function getNodeShape(node: CanvasNode | undefined | null): ShapeDescription | null {\n\tconst shape = node?.data?.[SHAPE_DATA_KEY];\n\treturn isShapeDescription(shape) ? shape : null;\n}\n\nexport interface ShapeNodePolygonOptions extends NodeRectOptions {\n\t/** Curve flattening tolerance in flow units (default 0.25). */\n\ttolerance?: number;\n}\n\n/**\n * A shape node's outline in absolute flow coordinates (parent offsets, size and rotation\n * applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToPolygon(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: ShapeNodePolygonOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });\n\treturn {\n\t\trings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y] as Point)),\n\t\tfillRule\n\t};\n}\n\n/**\n * A shape as an exact Bézier path in node-local pixels (rotation and path view boxes\n * applied; arcs become cubics), with the fill rule it is read with.\n */\nexport function shapeToBezierPath(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { path: BezierPath; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { path: [], fillRule: 'nonzero' };\n\tconst svg = getShapeSvg({ ...shape, rotation: 0 }, size);\n\tlet path = toBezierPath(svg.d);\n\tif (shape.kind === 'path' && shape.viewBox) {\n\t\tconst vb = shape.viewBox;\n\t\tif (vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\tpath = transformBezierPath(path, [sx, 0, 0, sy, -vb[0] * sx, -vb[1] * sy]);\n\t\t}\n\t}\n\tconst deg = shape.rotation ?? 0;\n\tif (deg && Number.isFinite(deg)) {\n\t\tconst rad = (deg * Math.PI) / 180;\n\t\tconst cos = Math.cos(rad);\n\t\tconst sin = Math.sin(rad);\n\t\tconst cx = w / 2;\n\t\tconst cy = h / 2;\n\t\tconst m: AffineMatrix = [cos, sin, -sin, cos, cx - cx * cos + cy * sin, cy - cx * sin - cy * cos];\n\t\tpath = transformBezierPath(path, m);\n\t}\n\treturn { path, fillRule: svg.fillRule };\n}\n\n/**\n * A shape node's outline as an exact Bézier path in absolute flow coordinates (parent\n * offsets, size and rotation applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToBezierPath(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): { path: BezierPath; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { path, fillRule } = shapeToBezierPath(shape, size);\n\treturn { path: transformBezierPath(path, [1, 0, 0, 1, origin.x, origin.y]), fillRule };\n}\n\n/** Why `combineShapeNodes` refused. */\nexport type CombineShapeNodesRefusal =\n\t/** A selected shape is locked; locked shapes are never consumed. */\n\t| 'locked'\n\t/** Fewer than two shape nodes were selected. */\n\t| 'too-few'\n\t/** The operation leaves nothing (e.g. intersecting shapes that do not overlap). */\n\t| 'empty';\n\nexport interface CombineShapeNodesOptions extends ShapeNodePolygonOptions {\n\t/**\n\t * Which shape's node style and data the result inherits: `bottom` (default, like\n\t * Freeform: the back-most shape), `top`, `first` (first id passed), or a node id.\n\t * Divide pieces always take the style of the top-most shape covering them.\n\t */\n\tstyleFrom?: 'bottom' | 'top' | 'first' | { id: string };\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the style source's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker simplification tolerance for the result (default 0, off). */\n\tsimplify?: number;\n\t/**\n\t * Geometry engine. `'curve'` (default) computes the operation on the exact Bézier\n\t * outlines and keeps the inputs' curves (circles stay cubic arcs, rectangles stay four\n\t * lines); it falls back to `'polygon'` when it cannot resolve the input (reported as\n\t * `engine` / `fallbackReason` on the result). `'polygon'` flattens the shapes and uses\n\t * `polygonBoolean` (the behaviour before curve booleans). `curves: 'lines'` implies\n\t * `'polygon'` unless an engine is named.\n\t */\n\tengine?: 'curve' | 'polygon';\n\t/** Called with the reason when the curve engine falls back to polygons. */\n\tonFallback?: (reason: string) => void;\n\t/**\n\t * Polygon engine only: how the result's outline is written: `'fit'` (default) fits\n\t * smooth cubic curves to the flattened result (sharp corners and straight edges are\n\t * kept); `'lines'` writes the polygon as straight segments.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in flow units for `curves: 'fit'` (default: `tolerance`, 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n}\n\nexport type CombineShapeNodesResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapeOperation;\n\t\t\t/** Ids of the shape nodes consumed by the operation. */\n\t\t\tremoveIds: string[];\n\t\t\t/** New path shape node(s): one, or one per region for `divide`. */\n\t\t\tnodes: TNode[];\n\t\t\t/** Engine that produced the geometry (`polygon` after a fallback). */\n\t\t\tengine: 'curve' | 'polygon';\n\t\t\t/** Why the curve engine fell back to polygons. */\n\t\t\tfallbackReason?: string;\n\t }\n\t| { ok: false; operation: ShapeOperation; reason: CombineShapeNodesRefusal };\n\nlet idCounter = 0;\nfunction defaultId(index: number): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\tidCounter += 1;\n\treturn `shape-${Date.now().toString(36)}-${idCounter}-${index}`;\n}\n\n/** Back-to-front order: `zIndex` (missing = 0), then array order, as xyflow stacks nodes. */\nfunction stackOrder<TNode extends CanvasNode>(nodes: TNode[], ids: Set<string>): TNode[] {\n\treturn nodes\n\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t.filter((entry) => ids.has(entry.node.id))\n\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t.map((entry) => entry.node);\n}\n\n/**\n * Checks whether `combineShapeNodes` can run, without computing geometry (it cannot\n * predict an `empty` result). Use it to enable menu items.\n */\nexport function canCombineShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: Exclude<CombineShapeNodesRefusal, 'empty'> } {\n\tconst idSet = new Set(ids);\n\tconst shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length < 2) return { ok: false, reason: 'too-few' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/**\n * Freeform-style shape operations on canvas nodes.\n *\n * - Only shape nodes (`data.shape`) among `ids` take part; other selected nodes are ignored.\n * - Refuses (`ok: false`) when a selected shape is locked, when fewer than two shapes are\n * selected, or when the result is empty. Nothing should be changed on refusal.\n * - `subtract` removes every other shape from the back-most one; the other operations are\n * symmetric. `divide` returns one node per distinct region of the overlay.\n * - Each new node is a `path` shape: its position / size is the result's bounding box and\n * its path data is node-local with `viewBox = [0, 0, width, height]`, so it scales with\n * the node. Rotation is baked into the path. Node fields and `data` (other than the\n * shape geometry, lock state and size) come from the style source.\n * - When all shapes share a parent, the new nodes keep it; otherwise they are top-level.\n *\n * Pure: returns the change; apply it with `applyShapeCombination` (or the arrange\n * controller's `combineShapes`, which also records one undo step).\n */\nexport function combineShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toperation: ShapeOperation,\n\toptions: CombineShapeNodesOptions = {}\n): CombineShapeNodesResult<TNode> {\n\tconst idList = [...ids];\n\tconst check = canCombineShapeNodes(nodes, idList);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\n\tconst lookup = createNodeLookup(nodes);\n\tconst shapeIds = new Set(check.shapeIds);\n\tconst ordered = stackOrder(nodes, shapeIds);\n\n\tconst parentIds = new Set(ordered.map((n) => n.parentId ?? ''));\n\tconst sharedParent = parentIds.size === 1 ? ordered[0].parentId : undefined;\n\tconst parentOrigin =\n\t\tsharedParent && lookup.get(sharedParent)\n\t\t\t? getAbsolutePosition(lookup.get(sharedParent)!, lookup)\n\t\t\t: { x: 0, y: 0 };\n\n\tconst pickSource = (): TNode => {\n\t\tconst style = options.styleFrom ?? 'bottom';\n\t\tif (style === 'top') return ordered[ordered.length - 1];\n\t\tif (style === 'first') {\n\t\t\tconst first = idList.find((id) => shapeIds.has(id));\n\t\t\treturn (first && lookup.get(first)) || ordered[0];\n\t\t}\n\t\tif (typeof style === 'object') return (shapeIds.has(style.id) && lookup.get(style.id)) || ordered[0];\n\t\treturn ordered[0];\n\t};\n\n\tconst createId = options.createId ?? defaultId;\n\tconst buildOptions: BuildPathNodeOptions = {\n\t\tprecision: options.precision,\n\t\tnodeType: options.nodeType,\n\t\tselect: options.select,\n\t\tparentOrigin,\n\t\tsharedParent\n\t};\n\tconst engine = options.engine ?? (options.curves === 'lines' ? 'polygon' : 'curve');\n\n\tconst created: TNode[] = [];\n\tlet report: PathOpsReport = { engine: 'curve' };\n\tif (engine === 'curve') {\n\t\tconst operands = ordered.map((node) => shapeNodeToBezierPath(node, lookup, options)!);\n\t\tconst pathOptions = { epsilon: options.epsilon, onFallback: options.onFallback, fallbackTolerance: options.tolerance };\n\t\tif (operation === 'divide') {\n\t\t\tconst result = dividePaths(operands, pathOptions);\n\t\t\treport = result;\n\t\t\tfor (const piece of result.pieces) {\n\t\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\t\tconst node = buildPathNode(piece.path, source, createId(created.length), buildOptions);\n\t\t\t\tif (node) created.push(node);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = pathBoolean(BOOLEAN_FOR[operation], operands, pathOptions);\n\t\t\treport = result;\n\t\t\tconst node = buildPathNode(result.path, pickSource(), createId(0), buildOptions);\n\t\t\tif (node) created.push(node);\n\t\t}\n\t} else {\n\t\treport = { engine: 'polygon' };\n\t\tconst operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options)!);\n\t\tconst write = (polygon: MultiPolygon): BezierPath =>\n\t\t\ttoBezierPath(\n\t\t\t\tpolygonToSvgPath(polygon, {\n\t\t\t\t\tprecision: 9,\n\t\t\t\t\tsimplify: options.simplify,\n\t\t\t\t\tcurves: options.curves ?? 'fit',\n\t\t\t\t\tcurveTolerance: options.curveTolerance ?? options.tolerance ?? 0.25,\n\t\t\t\t\tcornerAngleDeg: options.cornerAngleDeg\n\t\t\t\t})\n\t\t\t);\n\t\tif (operation === 'divide') {\n\t\t\tfor (const piece of dividePolygons(operands, { epsilon: options.epsilon })) {\n\t\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\t\tconst node = buildPathNode(write([piece.polygon]), source, createId(created.length), buildOptions);\n\t\t\t\tif (node) created.push(node);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });\n\t\t\tconst node = buildPathNode(write(result), pickSource(), createId(0), buildOptions);\n\t\t\tif (node) created.push(node);\n\t\t}\n\t}\n\tif (created.length === 0) return { ok: false, operation, reason: 'empty' };\n\tconst done: CombineShapeNodesResult<TNode> = {\n\t\tok: true,\n\t\toperation,\n\t\tremoveIds: ordered.map((n) => n.id),\n\t\tnodes: created,\n\t\tengine: report.engine\n\t};\n\tif (report.fallbackReason) done.fallbackReason = report.fallbackReason;\n\treturn done;\n}\n\ninterface BuildPathNodeOptions {\n\tprecision?: number;\n\tnodeType?: string;\n\tselect?: boolean;\n\tparentOrigin: { x: number; y: number };\n\tsharedParent?: string;\n\t/** Paint overrides; `null` removes the key (e.g. an outlined stroke has no stroke). */\n\tpaint?: Partial<Record<'fill' | 'stroke' | 'strokeWidth' | 'opacity', string | number | null>>;\n}\n\n/**\n * A new `path` shape node for an absolute path: positioned at the path's bounding box with\n * node-local path data and `viewBox = [0, 0, width, height]`; node fields, data and paint\n * come from `source`. `null` when the path is empty or flat.\n */\nfunction buildPathNode<TNode extends CanvasNode>(\n\tpath: BezierPath,\n\tsource: TNode,\n\tid: string,\n\toptions: BuildPathNodeOptions\n): TNode | null {\n\tconst bounds = bezierPathBounds(path);\n\tif (!bounds) return null;\n\tconst width = bounds.maxX - bounds.minX;\n\tconst height = bounds.maxY - bounds.minY;\n\tif (!(width > 0) || !(height > 0)) return null;\n\tconst d = bezierPathToSvg(path, { precision: options.precision ?? 3, offset: { x: -bounds.minX, y: -bounds.minY } });\n\tif (!d) return null;\n\tconst sourceShape = getNodeShape(source)!;\n\tconst shape: PathShape = {\n\t\tkind: 'path',\n\t\td,\n\t\tviewBox: [0, 0, roundSize(width), roundSize(height)],\n\t\tfillRule: 'nonzero'\n\t};\n\tfor (const key of ['fill', 'stroke', 'strokeWidth', 'opacity'] as const) {\n\t\tif (sourceShape[key] !== undefined) (shape as any)[key] = sourceShape[key];\n\t}\n\tfor (const [key, value] of Object.entries(options.paint ?? {})) {\n\t\tif (value === null) delete (shape as any)[key];\n\t\telse if (value !== undefined) (shape as any)[key] = value;\n\t}\n\tconst {\n\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,\n\t\t...data\n\t} = source.data ?? {};\n\tconst {\n\t\tmeasured: _measured,\n\t\tselected: _selected,\n\t\tdragging: _dragging,\n\t\tpositionAbsolute: _positionAbsolute,\n\t\tinternals: _internals,\n\t\tparentId: _parentId,\n\t\t...rest\n\t} = source as CanvasNode;\n\tconst node: CanvasNode = {\n\t\t...rest,\n\t\tid,\n\t\tposition: { x: bounds.minX - options.parentOrigin.x, y: bounds.minY - options.parentOrigin.y },\n\t\twidth,\n\t\theight,\n\t\tdata: { ...data, [SHAPE_DATA_KEY]: shape }\n\t};\n\tif (options.sharedParent) node.parentId = options.sharedParent;\n\tif (options.nodeType !== undefined) node.type = options.nodeType;\n\tif (source.style) node.style = withStyleSize(source.style, { width, height });\n\tif (options.select !== false) node.selected = true;\n\treturn node as TNode;\n}\n\nfunction roundSize(value: number): number {\n\treturn Math.round(value * 1000) / 1000;\n}\n\n/**\n * Applies a successful `combineShapeNodes` result: removes the consumed nodes and inserts\n * the new ones at the array index of the first consumed node.\n * With `deselect` (default `true`) other nodes are deselected when the result is selected.\n */\nexport function applyShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst remove = new Set(change.removeIds);\n\tconst insertAt = nodes.findIndex((n) => remove.has(n.id));\n\tconst deselect = options.deselect !== false && change.nodes.some((n) => n.selected);\n\tconst out: TNode[] = [];\n\tnodes.forEach((node, index) => {\n\t\tif (index === insertAt) out.push(...change.nodes);\n\t\tif (remove.has(node.id)) return;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t});\n\tif (insertAt === -1) out.push(...change.nodes);\n\treturn out;\n}\n\n/**\n * Reverses `applyShapeCombination`: removes the created nodes and puts the consumed nodes\n * back at their original array indices (`previous` is the node array before the change).\n */\nexport function revertShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\tprevious: TNode[]\n): TNode[] {\n\tconst created = new Set(change.nodes.map((n) => n.id));\n\tconst removed = new Set(change.removeIds);\n\tconst out = nodes.filter((n) => !created.has(n.id));\n\tprevious.forEach((node, index) => {\n\t\tif (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;\n\t\tout.splice(Math.min(index, out.length), 0, node);\n\t});\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Offset Path and Outline Stroke on shape nodes\n// ---------------------------------------------------------------------------\n\n/** Per-node path edits: `offset` (Offset Path) and `outlineStroke` (Outline Stroke). */\nexport type ShapePathEditOperation = 'offset' | 'outlineStroke';\n\n/** Why `offsetShapeNodes` / `outlineShapeNodeStrokes` refused. */\nexport type ShapePathEditRefusal =\n\t/** A selected shape is locked. */\n\t| 'locked'\n\t/** No shape node was selected. */\n\t| 'no-shapes'\n\t/** Outline Stroke: no selected shape has a visible stroke. */\n\t| 'no-stroke'\n\t/** Every result is empty (e.g. an inset larger than the shapes). */\n\t| 'empty'\n\t/** The offset distance or stroke width is not a finite number. */\n\t| 'invalid';\n\nexport type ShapePathEditResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapePathEditOperation;\n\t\t\t/** Ids of the replaced nodes. */\n\t\t\tremoveIds: string[];\n\t\t\t/** All new nodes (a replaced node may come back with the same id). */\n\t\t\tnodes: TNode[];\n\t\t\t/** Each replaced node with the nodes that take its place, in stacking order. */\n\t\t\treplacements: { id: string; nodes: TNode[] }[];\n\t }\n\t| { ok: false; operation: ShapePathEditOperation; reason: ShapePathEditRefusal };\n\ninterface ShapePathEditBaseOptions extends NodeRectOptions {\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the source node's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n\t/** Maximum error of the approximated offset curves in flow units (default 0.01). */\n\ttolerance?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Called with the reason when the curve engine falls back to polygons. */\n\tonFallback?: (reason: string) => void;\n}\n\nexport interface OffsetShapeNodesOptions extends ShapePathEditBaseOptions {\n\t/** Corner style of the offset outline (default `miter`). */\n\tjoin?: StrokeJoin;\n\t/** Miter limit (default 4). */\n\tmiterLimit?: number;\n\t/** Keep the original shape above the offset one (Illustrator style; default `false`: replace it). */\n\tkeepOriginal?: boolean;\n}\n\nexport interface OutlineShapeStrokesOptions extends ShapePathEditBaseOptions {\n\t/** Stroke width in flow units (default: the shape's `strokeWidth`, 1 when unset). */\n\twidth?: number;\n\t/** Default `miter`, as SVG. */\n\tjoin?: StrokeJoin;\n\t/** Default `butt`, as SVG. */\n\tcap?: StrokeCap;\n\t/** Default 4, as SVG. */\n\tmiterLimit?: number;\n\t/** Dash pattern (as `stroke-dasharray`). */\n\tdash?: readonly number[];\n\tdashOffset?: number;\n\t/**\n\t * Keep the shape's fill as the original node with its stroke removed, below the outlined\n\t * stroke (default `true`). With `false`, or when the shape has no visible fill, the node is\n\t * replaced by the outlined stroke alone.\n\t */\n\tkeepFill?: boolean;\n}\n\nconst INVISIBLE_PAINT = new Set(['none', 'transparent']);\n\n/** Whether a shape draws a stroke (a stroke colour other than `none` and a positive width). */\nexport function hasVisibleStroke(shape: ShapeDescription | null): boolean {\n\tif (!shape || typeof shape.stroke !== 'string' || INVISIBLE_PAINT.has(shape.stroke.trim().toLowerCase())) return false;\n\tconst width = shape.strokeWidth ?? 1;\n\treturn Number.isFinite(width) && width > 0;\n}\n\nfunction hasVisibleFill(shape: ShapeDescription): boolean {\n\treturn shape.fill === undefined || !INVISIBLE_PAINT.has(String(shape.fill).trim().toLowerCase());\n}\n\nfunction shapeTargets(nodes: CanvasNode[], ids: Iterable<string>) {\n\tconst idSet = new Set(ids);\n\treturn nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n}\n\n/** Checks whether `offsetShapeNodes` can run (it cannot predict an `empty` result). */\nexport function canOffsetShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: 'locked' | 'no-shapes' } {\n\tconst shapes = shapeTargets(nodes, ids);\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length === 0) return { ok: false, reason: 'no-shapes' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/** Checks whether `outlineShapeNodeStrokes` can run. */\nexport function canOutlineShapeNodeStrokes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: 'locked' | 'no-shapes' | 'no-stroke' } {\n\tconst shapes = shapeTargets(nodes, ids);\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length === 0) return { ok: false, reason: 'no-shapes' };\n\tconst stroked = shapes.filter((n) => hasVisibleStroke(getNodeShape(n)));\n\tif (stroked.length === 0) return { ok: false, reason: 'no-stroke' };\n\treturn { ok: true, shapeIds: stroked.map((n) => n.id) };\n}\n\nfunction editPlacement(node: CanvasNode, lookup: Map<string, CanvasNode>) {\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\treturn {\n\t\tparentOrigin: parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 },\n\t\tsharedParent: parent ? node.parentId : undefined\n\t};\n}\n\nfunction finishEdit<TNode extends CanvasNode>(\n\toperation: ShapePathEditOperation,\n\treplacements: { id: string; nodes: TNode[] }[]\n): ShapePathEditResult<TNode> {\n\tif (replacements.length === 0) return { ok: false, operation, reason: 'empty' };\n\treturn {\n\t\tok: true,\n\t\toperation,\n\t\tremoveIds: replacements.map((r) => r.id),\n\t\tnodes: replacements.flatMap((r) => r.nodes),\n\t\treplacements\n\t};\n}\n\n/**\n * Offset Path on shape nodes: each selected shape grows (`distance > 0`) or shrinks\n * (`distance < 0`) by `distance` flow units, keeping curves. The result replaces the shape\n * (or sits below it with `keepOriginal`) as a `path` shape node with the same paint. Shapes\n * whose offset is empty are left unchanged; refuses when every result is empty, a selected\n * shape is locked or no shape is selected. Pure: apply with `applyShapeReplacement`.\n */\nexport function offsetShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\tdistance: number,\n\toptions: OffsetShapeNodesOptions = {}\n): ShapePathEditResult<TNode> {\n\tconst operation = 'offset';\n\tif (!Number.isFinite(distance)) return { ok: false, operation, reason: 'invalid' };\n\tconst check = canOffsetShapeNodes(nodes, ids);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\tconst lookup = createNodeLookup(nodes);\n\tconst createId = options.createId ?? defaultId;\n\tlet index = 0;\n\tconst replacements: { id: string; nodes: TNode[] }[] = [];\n\tfor (const id of check.shapeIds) {\n\t\tconst node = lookup.get(id) as TNode;\n\t\tconst outline = shapeNodeToBezierPath(node, lookup, options)!;\n\t\tconst path = offsetPath(outline.path, distance, {\n\t\t\tjoin: options.join,\n\t\t\tmiterLimit: options.miterLimit,\n\t\t\tfillRule: outline.fillRule,\n\t\t\ttolerance: options.tolerance,\n\t\t\tepsilon: options.epsilon,\n\t\t\tonFallback: options.onFallback\n\t\t});\n\t\tconst created = buildPathNode(path, node, createId(index), {\n\t\t\t...editPlacement(node, lookup),\n\t\t\tprecision: options.precision,\n\t\t\tnodeType: options.nodeType,\n\t\t\tselect: options.select\n\t\t});\n\t\tif (!created) continue;\n\t\tindex++;\n\t\tconst original = { ...node, selected: false } as TNode;\n\t\treplacements.push({ id, nodes: options.keepOriginal ? [created, original] : [created] });\n\t}\n\treturn finishEdit(operation, replacements);\n}\n\n/**\n * Outline Stroke on shape nodes: each selected shape with a visible stroke gets its stroke\n * converted to a filled `path` shape node (filled with the stroke colour, no stroke), using\n * the stroke width in flow units (strokes do not scale with the node) and SVG's default\n * miter join, miter limit 4 and butt caps unless overridden. The shape's fill stays as the\n * original node without its stroke, below the outline (`keepFill`). Pure: apply with\n * `applyShapeReplacement`.\n */\nexport function outlineShapeNodeStrokes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toptions: OutlineShapeStrokesOptions = {}\n): ShapePathEditResult<TNode> {\n\tconst operation = 'outlineStroke';\n\tif (options.width !== undefined && !(Number.isFinite(options.width) && options.width > 0)) {\n\t\treturn { ok: false, operation, reason: 'invalid' };\n\t}\n\tconst check = canOutlineShapeNodeStrokes(nodes, ids);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\tconst lookup = createNodeLookup(nodes);\n\tconst createId = options.createId ?? defaultId;\n\tlet index = 0;\n\tconst replacements: { id: string; nodes: TNode[] }[] = [];\n\tfor (const id of check.shapeIds) {\n\t\tconst node = lookup.get(id) as TNode;\n\t\tconst shape = getNodeShape(node)!;\n\t\tconst outline = shapeNodeToBezierPath(node, lookup, options)!;\n\t\tconst path = outlineStroke(outline.path, {\n\t\t\twidth: options.width ?? shape.strokeWidth ?? 1,\n\t\t\tjoin: options.join,\n\t\t\tcap: options.cap,\n\t\t\tmiterLimit: options.miterLimit,\n\t\t\tdash: options.dash,\n\t\t\tdashOffset: options.dashOffset,\n\t\t\ttolerance: options.tolerance,\n\t\t\tepsilon: options.epsilon,\n\t\t\tonFallback: options.onFallback\n\t\t});\n\t\tconst created = buildPathNode(path, node, createId(index), {\n\t\t\t...editPlacement(node, lookup),\n\t\t\tprecision: options.precision,\n\t\t\tnodeType: options.nodeType,\n\t\t\tselect: options.select,\n\t\t\tpaint: { fill: shape.stroke as string, stroke: null, strokeWidth: null }\n\t\t});\n\t\tif (!created) continue;\n\t\tindex++;\n\t\tconst replacement: TNode[] = [];\n\t\tif (options.keepFill !== false && hasVisibleFill(shape)) {\n\t\t\tconst { stroke: _stroke, strokeWidth: _width, ...rest } = shape;\n\t\t\treplacement.push({\n\t\t\t\t...node,\n\t\t\t\tselected: options.select === false ? node.selected : false,\n\t\t\t\tdata: { ...node.data, [SHAPE_DATA_KEY]: rest }\n\t\t\t} as TNode);\n\t\t}\n\t\treplacement.push(created);\n\t\treplacements.push({ id, nodes: replacement });\n\t}\n\treturn finishEdit(operation, replacements);\n}\n\n/**\n * Applies a successful `offsetShapeNodes` / `outlineShapeNodeStrokes` result: each replaced\n * node is swapped in place for its replacement nodes, so the stacking order of everything\n * else is kept. With `deselect` (default `true`) other nodes are deselected when the\n * result is selected. Undo with `revertShapeCombination(nodes, result, previous)`.\n */\nexport function applyShapeReplacement<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { replacements: { id: string; nodes: TNode[] }[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst byId = new Map(change.replacements.map((r) => [r.id, r.nodes]));\n\tconst created = new Set(change.replacements.flatMap((r) => r.nodes.map((n) => n.id)));\n\tconst deselect = options.deselect !== false && change.replacements.some((r) => r.nodes.some((n) => n.selected));\n\tconst out: TNode[] = [];\n\tfor (const node of nodes) {\n\t\tconst replacement = byId.get(node.id);\n\t\tif (replacement) {\n\t\t\tout.push(...replacement);\n\t\t\tcontinue;\n\t\t}\n\t\tif (created.has(node.id)) continue;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t}\n\treturn out;\n}\n","/**\n * Resize snapping: the moving edges of a box being resized land on other shapes' edges and\n * centres (or on a grid) while the opposite edge / corner stays put.\n *\n * Portions ported from vectorcraft-main/crates/tools/src/guides.rs (`snap_scale`) and\n * vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\ttype CanvasNodeUpdate,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tcreateGuideIndex,\n\tcreateNodeGuideIndex,\n\tsnapToGrid,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions\n} from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** A resize handle, named like xyflow's `ControlPosition`. */\nexport type ResizeHandle =\n\t| 'top-left'\n\t| 'top'\n\t| 'top-right'\n\t| 'right'\n\t| 'bottom-right'\n\t| 'bottom'\n\t| 'bottom-left'\n\t| 'left';\n\nexport const RESIZE_HANDLES: readonly ResizeHandle[] = [\n\t'top-left',\n\t'top',\n\t'top-right',\n\t'right',\n\t'bottom-right',\n\t'bottom',\n\t'bottom-left',\n\t'left'\n];\n\n/** Which edge of each axis a handle moves: -1 the start (left / top), 1 the end, 0 none. */\nexport function resizeHandleAxes(handle: ResizeHandle): { x: -1 | 0 | 1; y: -1 | 0 | 1 } {\n\tconst x = handle.includes('left') ? -1 : handle.includes('right') ? 1 : 0;\n\tconst y = handle.includes('top') ? -1 : handle.includes('bottom') ? 1 : 0;\n\treturn { x, y };\n}\n\n/**\n * The handle a resize from `start` to `next` was made with, from the edges that moved, or\n * `null` when no edge moved.\n */\nexport function inferResizeHandle(start: Rect, next: Rect, eps = 1e-6): ResizeHandle | null {\n\tconst moved = (a: number, b: number) => Math.abs(a - b) > eps;\n\tconst left = moved(start.x, next.x);\n\tconst right = moved(start.x + start.width, next.x + next.width);\n\tconst top = moved(start.y, next.y);\n\tconst bottom = moved(start.y + start.height, next.y + next.height);\n\tconst h = left ? 'left' : right ? 'right' : '';\n\tconst v = top ? 'top' : bottom ? 'bottom' : '';\n\tif (h && v) return `${v}-${h}` as ResizeHandle;\n\treturn (h || v || null) as ResizeHandle | null;\n}\n\nexport interface SnapResizeOptions extends Omit<SnapGuidesOptions, 'spacing'> {\n\t/**\n\t * Keep the input box's aspect ratio. A corner snaps whichever axis is closer to a target\n\t * and the other follows; a side scales the other axis about its centre.\n\t */\n\tkeepAspectRatio?: boolean;\n\t/** Smallest width a snap may leave (default: anything above 0). */\n\tminWidth?: number;\n\t/** Smallest height a snap may leave (default: anything above 0). */\n\tminHeight?: number;\n}\n\nexport interface SnapResizeResult {\n\t/** The snapped box. */\n\trect: Rect;\n\t/** Guide segments for the shape snaps that were applied (grid snaps draw none). */\n\tguides: GuideLine[];\n\t/** Whether the moving x edge snapped to a shape. */\n\tsnappedX: boolean;\n\t/** Whether the moving y edge snapped to a shape. */\n\tsnappedY: boolean;\n\t/** Whether `rect` differs from the input. */\n\tchanged: boolean;\n}\n\ninterface Hit {\n\tvalue: number;\n\tkind: 'edge' | 'centre' | 'grid';\n}\n\ntype Axis = GuideIndex['edgeX'];\n\nfunction within(axis: Axis, v: number, tolerance: number): number[] {\n\tconst { values } = axis;\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v - tolerance) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\tconst out: number[] = [];\n\tfor (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);\n\treturn out;\n}\n\nfunction matching(axis: Axis, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);\n\treturn out;\n}\n\n/**\n * Snaps a resize of the axis-aligned box `rect` made with `handle` against `targets`. The\n * opposite edge (side handles) or corner (corner handles) stays fixed:\n *\n * - A **side** handle snaps its one moving edge to the nearest target edge or centre within\n * `threshold` screen pixels.\n * - A **corner** handle snaps x and y independently (unless `keepAspectRatio`).\n * - A snap that would collapse or flip the box (or go below `minWidth` / `minHeight`) is\n * rejected; the next nearest target is tried, else the edge stays where it was dragged.\n * - With `gridSize`, an axis without a shape snap snaps its moving edge to the grid.\n *\n * Returns the snapped box and the guides to draw (same shape as drag snapping).\n */\nexport function snapResize(\n\trect: Rect,\n\thandle: ResizeHandle,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapResizeOptions = {}\n): SnapResizeResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst eps = 0.5 / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst grid = options.gridSize ?? 0;\n\tconst axes = resizeHandleAxes(handle);\n\tconst unchanged: SnapResizeResult = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };\n\tif (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;\n\n\tconst x0 = rect.x;\n\tconst x1 = rect.x + rect.width;\n\tconst y0 = rect.y;\n\tconst y1 = rect.y + rect.height;\n\n\t/** Best valid target for one moving edge, or `null`. */\n\tconst bestHit = (horizontal: boolean): Hit | null => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (dir === 0) return null;\n\t\tconst moving = horizontal ? (dir < 0 ? x0 : x1) : dir < 0 ? y0 : y1;\n\t\tconst fixed = horizontal ? (dir < 0 ? x1 : x0) : dir < 0 ? y1 : y0;\n\t\tconst min = (horizontal ? options.minWidth : options.minHeight) ?? 0;\n\t\tconst valid = (t: number) => {\n\t\t\tconst size = dir < 0 ? fixed - t : t - fixed;\n\t\t\treturn Number.isFinite(size) && size > 1e-9 && size >= min;\n\t\t};\n\t\tlet best: Hit | null = null;\n\t\tconst consider = (values: number[], kind: Hit['kind']) => {\n\t\t\tfor (const t of values) {\n\t\t\t\tif (!valid(t)) continue;\n\t\t\t\tif (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };\n\t\t\t}\n\t\t};\n\t\tif (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), 'edge');\n\t\tif (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), 'centre');\n\t\tif (!best && grid > 0) {\n\t\t\tconst origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;\n\t\t\tconsider([snapToGrid(moving, grid, origin)], 'grid');\n\t\t}\n\t\treturn best;\n\t};\n\n\tlet nx0 = x0;\n\tlet nx1 = x1;\n\tlet ny0 = y0;\n\tlet ny1 = y1;\n\tconst setEdge = (horizontal: boolean, value: number) => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (horizontal) {\n\t\t\tif (dir < 0) nx0 = value;\n\t\t\telse nx1 = value;\n\t\t} else if (dir < 0) ny0 = value;\n\t\telse ny1 = value;\n\t};\n\n\tlet hitX = bestHit(true);\n\tlet hitY = bestHit(false);\n\tconst ratio = rect.height > 0 ? rect.width / rect.height : 0;\n\n\tif (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {\n\t\tif (axes.x !== 0 && axes.y !== 0) {\n\t\t\t// Corner: the closer snap wins; the other axis follows from the fixed corner.\n\t\t\tconst dist = (h: Hit | null, horizontal: boolean) =>\n\t\t\t\th ? Math.abs(h.value - (horizontal ? (axes.x < 0 ? x0 : x1) : axes.y < 0 ? y0 : y1)) : Infinity;\n\t\t\tconst useX = dist(hitX, true) <= dist(hitY, false);\n\t\t\tif (useX && hitX) {\n\t\t\t\tsetEdge(true, hitX.value);\n\t\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\t\tif (axes.y < 0) ny0 = ny1 - height;\n\t\t\t\telse ny1 = ny0 + height;\n\t\t\t\thitY = null;\n\t\t\t} else if (hitY) {\n\t\t\t\tsetEdge(false, hitY.value);\n\t\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\t\tif (axes.x < 0) nx0 = nx1 - width;\n\t\t\t\telse nx1 = nx0 + width;\n\t\t\t\thitX = null;\n\t\t\t}\n\t\t\tconst min = (v: number | undefined) => v ?? 0;\n\t\t\tif (nx1 - nx0 < min(options.minWidth) || ny1 - ny0 < min(options.minHeight) || !(nx1 > nx0 && ny1 > ny0)) {\n\t\t\t\treturn unchanged;\n\t\t\t}\n\t\t} else if (axes.x !== 0 && hitX) {\n\t\t\tsetEdge(true, hitX.value);\n\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\tconst cy = (y0 + y1) / 2;\n\t\t\tny0 = cy - height / 2;\n\t\t\tny1 = cy + height / 2;\n\t\t} else if (axes.y !== 0 && hitY) {\n\t\t\tsetEdge(false, hitY.value);\n\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\tconst cx = (x0 + x1) / 2;\n\t\t\tnx0 = cx - width / 2;\n\t\t\tnx1 = cx + width / 2;\n\t\t}\n\t} else {\n\t\tif (hitX) setEdge(true, hitX.value);\n\t\tif (hitY) setEdge(false, hitY.value);\n\t}\n\n\tconst out: Rect = { x: nx0, y: ny0, width: nx1 - nx0, height: ny1 - ny0 };\n\tconst guides: GuideLine[] = [];\n\tconst guideFor = (horizontal: boolean, hit: Hit | null) => {\n\t\tif (!hit || hit.kind === 'grid') return;\n\t\tconst axis =\n\t\t\thit.kind === 'edge' ? (horizontal ? index.edgeX : index.edgeY) : horizontal ? index.centreX : index.centreY;\n\t\tconst aligned = matching(axis, hit.value, eps);\n\t\tconst span = boundsOf([out, ...aligned])!;\n\t\tguides.push(\n\t\t\thorizontal\n\t\t\t\t? { orientation: 'vertical', kind: hit.kind, x1: hit.value, y1: span.y, x2: hit.value, y2: span.y + span.height }\n\t\t\t\t: { orientation: 'horizontal', kind: hit.kind, x1: span.x, y1: hit.value, x2: span.x + span.width, y2: hit.value }\n\t\t);\n\t};\n\tguideFor(true, hitX);\n\tguideFor(false, hitY);\n\tconst changed = out.x !== rect.x || out.y !== rect.y || out.width !== rect.width || out.height !== rect.height;\n\treturn {\n\t\trect: out,\n\t\tguides,\n\t\tsnappedX: !!hitX && hitX.kind !== 'grid',\n\t\tsnappedY: !!hitY && hitY.kind !== 'grid',\n\t\tchanged\n\t};\n}\n\nexport interface SnapNodeResizeOptions extends SnapResizeOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex(nodes, [id])`, reused across resize frames. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeResizeResult extends SnapResizeResult {\n\t/** Position / size update for the node (parent-relative position), or none. */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a node that is being resized. `rect` is the node's proposed absolute box (e.g. from\n * a resize event) and `handle` the handle being dragged. Locked nodes are never snapped.\n * The node itself and its descendants are not snap targets.\n */\nexport function snapNodeResize(\n\tnodes: CanvasNode[],\n\tid: string,\n\trect: Rect,\n\thandle: ResizeHandle,\n\toptions: SnapNodeResizeOptions = {}\n): SnapNodeResizeResult {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node || isLocked(node)) {\n\t\treturn { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };\n\t}\n\tconst index = options.index ?? createNodeGuideIndex(nodes, [id], options);\n\tconst result = snapResize(rect, handle, index, options);\n\tif (!result.changed) return { ...result, updates: [] };\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\tconst origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };\n\treturn {\n\t\t...result,\n\t\tupdates: [\n\t\t\t{\n\t\t\t\tid,\n\t\t\t\tposition: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },\n\t\t\t\twidth: result.rect.width,\n\t\t\t\theight: result.rect.height\n\t\t\t}\n\t\t]\n\t};\n}\n\n/**\n * Absolute box of a node while it is being resized: flow libraries write the new size to\n * `width` / `height` before the DOM is re-measured, so those win over `measured` here.\n */\nexport function getResizingNodeRect(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): Rect {\n\tconst { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;\n\tconst size = getNodeSize(node, options);\n\treturn { x, y, width: node.width ?? size.width, height: node.height ?? size.height };\n}\n","// Portions ported from figcraft/crates/layout/src/lib.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { createNodeLookup, getAbsolutePosition, getNodeSize, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/**\n * Figma-style auto layout for xyflow sub-flows.\n *\n * A *frame* is any node with `data.layout` (an {@link AutoLayout}); its children are the nodes\n * whose `parentId` is the frame, in node-array order. Children may carry `data.childLayout`\n * (an {@link AutoLayoutChild}) for their own sizing, min / max, absolute positioning,\n * constraints and grid cell. The functions here are pure: they return `CanvasNodeUpdate`s\n * (positions relative to the parent, sizes) for `applyNodeUpdates`.\n */\n\n/** Data key holding a frame's {@link AutoLayout}. */\nexport const AUTO_LAYOUT_DATA_KEY = 'layout';\n/** Data key holding a child's {@link AutoLayoutChild}. */\nexport const CHILD_LAYOUT_DATA_KEY = 'childLayout';\n\nexport type AutoLayoutMode = 'horizontal' | 'vertical' | 'grid';\n/** `fixed` keeps the size, `hug` fits the content (frames and text), `fill` shares the parent's free space. */\nexport type AutoLayoutSizing = 'fixed' | 'hug' | 'fill';\nexport type AutoLayoutAlignMain = 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';\nexport type AutoLayoutAlignCross = 'start' | 'center' | 'end' | 'stretch' | 'baseline';\n/** How wrapped lines share the free cross space: packed by `alignCross` (`auto`) or spread (`space-between`). */\nexport type AutoLayoutAlignContent = 'auto' | 'space-between';\n/** `number` (all sides), `[vertical, horizontal]`, `[top, right, bottom, left]` or per side. */\nexport type AutoLayoutPadding =\n\t| number\n\t| [number, number]\n\t| [number, number, number, number]\n\t| { top?: number; right?: number; bottom?: number; left?: number };\n\nexport interface AutoLayoutSize {\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface AutoLayoutSizingPair {\n\twidth?: AutoLayoutSizing;\n\theight?: AutoLayoutSizing;\n}\n\n/** A grid track: pixels, `'hug'` (fits its content), `'2fr'` or `{ fr: 2 }` (shares the rest like CSS `fr`). */\nexport type AutoLayoutGridTrack = number | 'hug' | `${number}fr` | { fr: number };\n\nexport interface AutoLayoutGrid {\n\t/** Column count (all `1fr`) or the column tracks (default 2 columns). */\n\tcolumns?: number | AutoLayoutGridTrack[];\n\t/** Minimum row count or the row tracks (default 1); rows are added to fit the children. Missing tracks are `1fr`. */\n\trows?: number | AutoLayoutGridTrack[];\n}\n\n/** A frame's auto layout (`node.data.layout`). */\nexport interface AutoLayout {\n\tmode: AutoLayoutMode;\n\t/** Main-axis gap (grid: column gap). Negative gaps overlap. Default 0. */\n\tgap?: number;\n\t/** Gap between wrapped lines (grid: row gap). Default: `gap`. */\n\trowGap?: number;\n\t/** Default 0. */\n\tpadding?: AutoLayoutPadding;\n\t/** Horizontal / vertical: wrap into lines when the main axis runs out (ignored while hugging it). */\n\twrap?: boolean;\n\t/** Default `start`. Grid: the cell alignment along x (`start`, `center`, `end`). */\n\talignMain?: AutoLayoutAlignMain;\n\t/** Default `start`. `baseline` applies to horizontal layouts. Grid: the cell alignment along y. */\n\talignCross?: AutoLayoutAlignCross;\n\talignContent?: AutoLayoutAlignContent;\n\t/** The frame's own sizing (`childLayout.sizing` wins when both are set). Default `fixed`. */\n\tsizing?: AutoLayoutSizingPair;\n\t/** The frame's own limits (`childLayout.min` / `max` win when set). */\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\tgrid?: AutoLayoutGrid;\n}\n\nexport type AutoLayoutHorizontalConstraint = 'left' | 'right' | 'leftRight' | 'center' | 'scale';\nexport type AutoLayoutVerticalConstraint = 'top' | 'bottom' | 'topBottom' | 'center' | 'scale';\n\nexport interface AutoLayoutConstraints {\n\thorizontal?: AutoLayoutHorizontalConstraint;\n\tvertical?: AutoLayoutVerticalConstraint;\n}\n\nexport type AutoLayoutCellAlign = 'start' | 'center' | 'end';\n\n/** A grid child's cell. Column / row are 0-based anchors; unanchored children flow in reading order. */\nexport interface AutoLayoutGridArea {\n\tcolumn?: number;\n\trow?: number;\n\tcolumnSpan?: number;\n\trowSpan?: number;\n\t/** The child's own alignment in its cell (wins over the frame's). */\n\talignX?: AutoLayoutCellAlign;\n\talignY?: AutoLayoutCellAlign;\n}\n\n/** A child's layout properties (`node.data.childLayout`). */\nexport interface AutoLayoutChild {\n\tsizing?: AutoLayoutSizingPair;\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\t/** Ignore auto layout: keep its place and follow `constraints` when the frame resizes. */\n\tabsolute?: boolean;\n\t/** How the child follows its parent's resizes (default left / top). */\n\tconstraints?: AutoLayoutConstraints;\n\tgridArea?: AutoLayoutGridArea;\n}\n\n/**\n * Measuring hooks. `size` defaults to `getNodeSize` (xyflow `measured` size, then `width` /\n * `height`, then style). Nodes for which `textSize` returns a size are *text-like*: they can\n * hug, and wrap (`textHeight`) when their width is set by the layout.\n */\nexport interface AutoLayoutMeasure {\n\tsize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Natural (unwrapped) size of a text-like node. */\n\ttextSize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Height of a text-like node wrapped at `width`. */\n\ttextHeight?: (node: CanvasNode, width: number) => number | undefined;\n\t/** Distance from the node's top to its first text baseline (for `alignCross: 'baseline'`). */\n\ttextBaseline?: (node: CanvasNode) => number | undefined;\n}\n\nexport interface AutoLayoutOptions {\n\tmeasure?: AutoLayoutMeasure;\n\t/** Size resolution for unmeasured nodes when `measure.size` is not given. */\n\trectOptions?: NodeRectOptions;\n}\n\ntype Axis = 'width' | 'height';\n\nconst MODES = new Set(['horizontal', 'vertical', 'grid']);\nconst EPS = 1e-6;\nconst MAX_TRACKS = 1000;\nconst MAX_GRID_CELLS = 1_000_000;\n\n/** Clamps a size to `[min, max]`; non-finite or negative sizes become 0 and min wins over a smaller max. */\nexport function clampLayoutSize(value: number, min?: number, max?: number): number {\n\tlet v = Number.isFinite(value) ? Math.max(value, 0) : 0;\n\tif (typeof max === 'number' && Number.isFinite(max)) v = Math.min(v, max);\n\tif (typeof min === 'number' && Number.isFinite(min)) v = Math.max(v, min);\n\treturn v;\n}\n\nconst finite = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0);\n\n/** The node's auto layout, or `undefined` when it is not an auto layout frame. */\nexport function getAutoLayout(node: CanvasNode | undefined | null): AutoLayout | undefined {\n\tconst layout = node?.data?.[AUTO_LAYOUT_DATA_KEY];\n\treturn layout && typeof layout === 'object' && MODES.has(layout.mode) ? (layout as AutoLayout) : undefined;\n}\n\n/** Whether the node is an auto layout frame. */\nexport function isAutoLayoutFrame(node: CanvasNode | undefined | null): boolean {\n\treturn getAutoLayout(node) !== undefined;\n}\n\n/** The node's child layout properties (empty when unset). */\nexport function getChildLayout(node: CanvasNode | undefined | null): AutoLayoutChild {\n\tconst child = node?.data?.[CHILD_LAYOUT_DATA_KEY];\n\treturn child && typeof child === 'object' ? (child as AutoLayoutChild) : {};\n}\n\n/** Effective sizing of a node on one axis: `childLayout.sizing`, then `layout.sizing`, then `fixed`. */\nexport function getLayoutSizing(node: CanvasNode, axis: Axis): AutoLayoutSizing {\n\tconst value = getChildLayout(node).sizing?.[axis] ?? getAutoLayout(node)?.sizing?.[axis];\n\treturn value === 'hug' || value === 'fill' ? value : 'fixed';\n}\n\nfunction limitsOf(node: CanvasNode) {\n\tconst child = getChildLayout(node);\n\tconst layout = getAutoLayout(node);\n\tconst min = child.min ?? layout?.min ?? {};\n\tconst max = child.max ?? layout?.max ?? {};\n\treturn { minW: min.width, maxW: max.width, minH: min.height, maxH: max.height };\n}\n\n/** Padding as `[top, right, bottom, left]`, sanitised (non-finite and negative values become 0). */\nexport function resolvePadding(padding: AutoLayoutPadding | undefined): [number, number, number, number] {\n\tlet sides: [unknown, unknown, unknown, unknown];\n\tif (typeof padding === 'number') sides = [padding, padding, padding, padding];\n\telse if (Array.isArray(padding)) {\n\t\tsides = padding.length === 2 ? [padding[0], padding[1], padding[0], padding[1]] : [padding[0], padding[1], padding[2], padding[3]];\n\t} else if (padding && typeof padding === 'object') sides = [padding.top, padding.right, padding.bottom, padding.left];\n\telse sides = [0, 0, 0, 0];\n\treturn sides.map((p) => Math.max(finite(p), 0)) as [number, number, number, number];\n}\n\ninterface Box {\n\tid: string;\n\tnode: CanvasNode;\n\tparent?: string;\n\tx: number;\n\ty: number;\n\tw: number;\n\th: number;\n\tkids: string[];\n}\n\n/** Working copy of the node geometry the layout passes mutate. */\nclass LayoutState {\n\treadonly boxes = new Map<string, Box>();\n\treadonly measure: AutoLayoutMeasure;\n\tprivate readonly visiting = new Set<string>();\n\tprivate readonly original = new Map<string, { x: number; y: number; w: number; h: number }>();\n\n\tconstructor(nodes: CanvasNode[], options: AutoLayoutOptions = {}) {\n\t\tthis.measure = options.measure ?? {};\n\t\tfor (const node of nodes) {\n\t\t\tconst size = this.measure.size?.(node) ?? getNodeSize(node, options.rectOptions);\n\t\t\tconst box: Box = {\n\t\t\t\tid: node.id,\n\t\t\t\tnode,\n\t\t\t\tparent: node.parentId,\n\t\t\t\tx: finite(node.position?.x),\n\t\t\t\ty: finite(node.position?.y),\n\t\t\t\tw: finite(size.width),\n\t\t\t\th: finite(size.height),\n\t\t\t\tkids: []\n\t\t\t};\n\t\t\tthis.boxes.set(node.id, box);\n\t\t\tthis.original.set(node.id, { x: box.x, y: box.y, w: box.w, h: box.h });\n\t\t}\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && node.parentId !== node.id) this.boxes.get(node.parentId)?.kids.push(node.id);\n\t\t}\n\t}\n\n\tupdates(): CanvasNodeUpdate[] {\n\t\tconst out: CanvasNodeUpdate[] = [];\n\t\tfor (const [id, box] of this.boxes) {\n\t\t\tconst o = this.original.get(id)!;\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (Math.abs(box.x - o.x) > EPS || Math.abs(box.y - o.y) > EPS) update.position = { x: box.x, y: box.y };\n\t\t\tif (Math.abs(box.w - o.w) > EPS) update.width = box.w;\n\t\t\tif (Math.abs(box.h - o.h) > EPS) update.height = box.h;\n\t\t\tif (update.position || update.width !== undefined || update.height !== undefined) out.push(update);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Runs `fn` for `id` unless it is already on the stack (guards `parentId` cycles). */\n\tguard(id: string, fn: () => void) {\n\t\tif (this.visiting.has(id)) return;\n\t\tthis.visiting.add(id);\n\t\ttry {\n\t\t\tfn();\n\t\t} finally {\n\t\t\tthis.visiting.delete(id);\n\t\t}\n\t}\n\n\tancestors(id: string): string[] {\n\t\tconst out: string[] = [];\n\t\tconst seen = new Set([id]);\n\t\tlet parent = this.boxes.get(id)?.parent;\n\t\twhile (parent && !seen.has(parent) && this.boxes.has(parent)) {\n\t\t\tout.push(parent);\n\t\t\tseen.add(parent);\n\t\t\tparent = this.boxes.get(parent)!.parent;\n\t\t}\n\t\treturn out;\n\t}\n}\n\nfunction setSize(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.w = Number.isFinite(w) ? Math.max(w, 0) : b.w;\n\tb.h = Number.isFinite(h) ? Math.max(h, 0) : b.h;\n}\n\nfunction place(s: LayoutState, id: string, x: number, y: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.x = x;\n\tb.y = y;\n}\n\nconst isText = (s: LayoutState, b: Box) => !!s.measure.textSize && s.measure.textSize(b.node) !== undefined;\n\n/** The children an auto layout frame places: visible and not absolute. */\nfunction flowChildren(s: LayoutState, frame: string): string[] {\n\tconst f = s.boxes.get(frame);\n\tif (!f) return [];\n\treturn f.kids.filter((k) => {\n\t\tconst n = s.boxes.get(k)!.node;\n\t\treturn n.hidden !== true && !getChildLayout(n).absolute;\n\t});\n}\n\n/** Text that hugs its width refreshes its natural size (wrapping at its max width); a hug-height text gets its wrapped height. */\nfunction fitHugText(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b || !isText(s, b)) return;\n\tconst natural = s.measure.textSize!(b.node)!;\n\tconst { minW, maxW } = limitsOf(b.node);\n\tif (getLayoutSizing(b.node, 'width') === 'hug') {\n\t\tconst w = clampLayoutSize(natural.width, minW, maxW);\n\t\tconst limited = Math.abs(w - natural.width) > EPS;\n\t\tconst h = limited ? s.measure.textHeight?.(b.node, w) ?? natural.height : natural.height;\n\t\tsetSize(s, id, w, h);\n\t} else if (getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, b.w, s.measure.textHeight?.(b.node, b.w) ?? b.h);\n\t}\n}\n\n/** Clamps an in-flow child's own size to its min / max. */\nfunction enforceLimits(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tconst { minW, maxW, minH, maxH } = limitsOf(b.node);\n\tif (minW === undefined && maxW === undefined && minH === undefined && maxH === undefined) return;\n\tconst nw = clampLayoutSize(b.w, minW, maxW);\n\tconst nh = clampLayoutSize(b.h, minH, maxH);\n\tif (Math.abs(nw - b.w) < 1e-9 && Math.abs(nh - b.h) < 1e-9) return;\n\tif (isText(s, b)) setSize(s, id, nw, nh);\n\telse resizeChild(s, id, nw, nh);\n}\n\nfunction itemBaseline(s: LayoutState, id: string, depth: number): number | undefined {\n\tconst b = s.boxes.get(id);\n\tif (!b) return undefined;\n\tif (isText(s, b)) {\n\t\tconst v = s.measure.textBaseline?.(b.node);\n\t\treturn typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\t}\n\tconst layout = getAutoLayout(b.node);\n\tif (!layout || layout.mode === 'grid' || depth >= 32) return undefined;\n\tfor (const k of flowChildren(s, id)) {\n\t\tconst base = itemBaseline(s, k, depth + 1);\n\t\tif (base !== undefined) return s.boxes.get(k)!.y + base;\n\t}\n\treturn undefined;\n}\n\n/** Resizes a child; text with hugging height re-wraps, frames apply constraints and re-run their layout. */\nfunction resizeChild(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tif (isText(s, b) && getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, w, s.measure.textHeight?.(b.node, w) ?? h);\n\t\treturn;\n\t}\n\tconst ow = b.w;\n\tconst oh = b.h;\n\tsetSize(s, id, w, h);\n\tif (b.kids.length > 0) constrain(s, id, ow, oh);\n\tif (getAutoLayout(b.node)) layout(s, id);\n}\n\ninterface Item {\n\tid: string;\n\tmain: number;\n\tcross: number;\n\tfillMain: boolean;\n\tfillCross: boolean;\n\tminMain?: number;\n\tmaxMain?: number;\n\tminCross?: number;\n\tmaxCross?: number;\n\tbaseline?: number;\n\tbaseOff: number;\n}\n\n/** Where the first child starts and the step between children along the main axis. */\nfunction mainOffsets(align: AutoLayoutAlignMain, inner: number, total: number, n: number, gap: number): [number, number] {\n\tconst gaps = gap * Math.max(n - 1, 0);\n\tconst free = inner - total;\n\tconst k = Math.max(n, 1);\n\tswitch (align) {\n\t\tcase 'center':\n\t\t\treturn [(free - gaps) / 2, gap];\n\t\tcase 'end':\n\t\t\treturn [free - gaps, gap];\n\t\tcase 'space-between':\n\t\t\treturn [0, n > 1 ? Math.max(free / (n - 1), 0) : 0];\n\t\tcase 'space-around': {\n\t\t\tconst g = Math.max(free / k, 0);\n\t\t\treturn [g / 2, g];\n\t\t}\n\t\tcase 'space-evenly': {\n\t\t\tconst g = Math.max(free / (k + 1), 0);\n\t\t\treturn [g, g];\n\t\t}\n\t\tdefault:\n\t\t\treturn [0, gap];\n\t}\n}\n\n/**\n * Shares `free` main-axis space among the fill items like CSS flexbox: equal shares within\n * min / max; when clamping adds more than it removes, the items held at their min keep it\n * (otherwise those held at their max do) and the rest share what is left again.\n */\nfunction distribute(items: Item[], open: number[], free: number) {\n\tfree = Math.max(free, 0);\n\twhile (open.length > 0) {\n\t\tconst share = free / open.length;\n\t\tconst sized = open.map((i) => [i, clampLayoutSize(share, items[i].minMain, items[i].maxMain)] as const);\n\t\tconst violation = sized.reduce((sum, [, v]) => sum + (v - share), 0);\n\t\tconst frozen = (v: number) =>\n\t\t\tMath.abs(violation) < 1e-9 || (violation > 0 && v > share + 1e-9) || (violation < 0 && v < share - 1e-9);\n\t\tfor (const [i, v] of sized) {\n\t\t\tif (frozen(v)) {\n\t\t\t\titems[i].main = v;\n\t\t\t\tfree -= v;\n\t\t\t}\n\t\t}\n\t\tfree = Math.max(free, 0);\n\t\topen = sized.filter(([, v]) => !frozen(v)).map(([i]) => i);\n\t}\n}\n\nfunction layout(s: LayoutState, frame: string) {\n\ts.guard(frame, () => layoutFrameInner(s, frame));\n}\n\nfunction layoutFrameInner(s: LayoutState, frame: string) {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al) return;\n\tconst horizontal = al.mode !== 'vertical';\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ow = f.w;\n\tconst oh = f.h;\n\tconst fl = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst fw = Math.max(clampLayoutSize(ow, fl.minW, fl.maxW), pl + pr);\n\tconst fh = Math.max(clampLayoutSize(oh, fl.minH, fl.maxH), pt + pb);\n\tconst kids = flowChildren(s, frame);\n\n\tfor (const c of kids) {\n\t\tfitHugText(s, c);\n\t\tenforceLimits(s, c);\n\t}\n\n\tif (al.mode === 'grid') {\n\t\tlayoutGrid(s, frame, al);\n\t\treturn;\n\t}\n\n\tconst innerMainFixed = horizontal ? fw - pl - pr : fh - pt - pb;\n\tconst innerCrossFixed = horizontal ? fh - pt - pb : fw - pl - pr;\n\tconst hugMain = horizontal ? hugW : hugH;\n\tconst hugCross = horizontal ? hugH : hugW;\n\tconst alignCross = al.alignCross ?? 'start';\n\tconst baseline = horizontal && alignCross === 'baseline';\n\tconst stretch = alignCross === 'stretch';\n\n\tconst items: Item[] = [];\n\tfor (const c of kids) {\n\t\tconst b = s.boxes.get(c)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tconst sm = getLayoutSizing(b.node, horizontal ? 'width' : 'height');\n\t\tconst sc = getLayoutSizing(b.node, horizontal ? 'height' : 'width');\n\t\titems.push({\n\t\t\tid: c,\n\t\t\tmain: horizontal ? b.w : b.h,\n\t\t\tcross: horizontal ? b.h : b.w,\n\t\t\t// Fill needs room to fill: on an axis the frame hugs it reads as fixed.\n\t\t\tfillMain: sm === 'fill' && !hugMain,\n\t\t\tfillCross: (sc === 'fill' || (stretch && sc !== 'hug')) && !hugCross,\n\t\t\tminMain: horizontal ? lim.minW : lim.minH,\n\t\t\tmaxMain: horizontal ? lim.maxW : lim.maxH,\n\t\t\tminCross: horizontal ? lim.minH : lim.minW,\n\t\t\tmaxCross: horizontal ? lim.maxH : lim.maxW,\n\t\t\tbaseOff: 0\n\t\t});\n\t}\n\n\tconst gap = finite(al.gap);\n\tconst alignMain = al.alignMain ?? 'start';\n\tconst autoGap = alignMain === 'space-between' || alignMain === 'space-around' || alignMain === 'space-evenly';\n\tconst wraps = !!al.wrap && !hugMain;\n\tconst sizeOf = (id: string) => {\n\t\tconst b = s.boxes.get(id)!;\n\t\treturn horizontal ? [b.w, b.h] : [b.h, b.w];\n\t};\n\tconst resize = (it: Item, main: number, cross: number) =>\n\t\tresizeChild(s, it.id, horizontal ? main : cross, horizontal ? cross : main);\n\n\t// Fill across a single line: resize those children first so their main size is final.\n\tif (!wraps) {\n\t\tfor (const it of items) {\n\t\t\tif (!it.fillCross || it.fillMain) continue;\n\t\t\tresize(it, it.main, clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross));\n\t\t\tit.main = sizeOf(it.id)[0];\n\t\t}\n\t}\n\n\t// Split into lines along the main axis.\n\tconst lines: number[][] = [];\n\tif (wraps) {\n\t\tlet cur: number[] = [];\n\t\tlet used = 0;\n\t\titems.forEach((it, i) => {\n\t\t\tconst sz = it.fillMain ? it.minMain ?? 0 : it.main;\n\t\t\tconst add = cur.length === 0 ? sz : Math.max(gap, 0) + sz;\n\t\t\tif (cur.length > 0 && used + add > innerMainFixed + 1e-6) {\n\t\t\t\tlines.push(cur);\n\t\t\t\tcur = [];\n\t\t\t\tused = sz;\n\t\t\t} else {\n\t\t\t\tused += add;\n\t\t\t}\n\t\t\tcur.push(i);\n\t\t});\n\t\tif (cur.length > 0) lines.push(cur);\n\t} else {\n\t\tlines.push(items.map((_, i) => i));\n\t}\n\n\t// Share each line's free space among its fill children.\n\tfor (const line of lines) {\n\t\tconst fixed = line.filter((i) => !items[i].fillMain).reduce((sum, i) => sum + items[i].main, 0);\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst fills = line.filter((i) => items[i].fillMain);\n\t\tif (fills.length > 0) distribute(items, fills, innerMainFixed - fixed - (autoGap ? 0 : gaps));\n\t}\n\tfor (const it of items) {\n\t\tif (!it.fillMain) continue;\n\t\tconst cross = it.fillCross && !wraps ? clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross) : it.cross;\n\t\tresize(it, it.main, cross);\n\t\tit.cross = sizeOf(it.id)[1];\n\t}\n\tif (baseline) {\n\t\tfor (const it of items) if (!it.fillCross) it.baseline = itemBaseline(s, it.id, 0);\n\t}\n\n\t// Line extents: [main used, cross size].\n\tconst extents: [number, number][] = [];\n\tfor (const line of lines) {\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst lineBase = line.reduce((m, i) => Math.max(m, items[i].baseline ?? 0), 0);\n\t\tfor (const i of line) items[i].baseOff = items[i].baseline !== undefined ? lineBase - items[i].baseline! : 0;\n\t\tconst inLine = line.map((i) => items[i]);\n\t\tconst mainUsed = inLine.reduce((sum, it) => sum + it.main, 0) + gaps;\n\t\t// A line of nothing but fill-across children keeps their own size.\n\t\tconst allFill = inLine.every((it) => it.fillCross);\n\t\tconst cross = inLine.filter((it) => allFill || !it.fillCross).reduce((m, it) => Math.max(m, it.baseOff + it.cross), 0);\n\t\textents.push([mainUsed, cross]);\n\t}\n\n\t// Container size.\n\tconst contentMain = extents.reduce((m, l) => Math.max(m, l[0]), 0);\n\tlet crossGap = al.rowGap !== undefined ? finite(al.rowGap) : gap;\n\tconst contentCross = extents.reduce((sum, l) => sum + l[1], 0) + crossGap * Math.max(lines.length - 1, 0);\n\tlet nw = fw;\n\tlet nh = fh;\n\tif (hugMain) {\n\t\tconst v = contentMain + (horizontal ? pl + pr : pt + pb);\n\t\tif (horizontal) nw = v;\n\t\telse nh = v;\n\t}\n\tif (hugCross) {\n\t\tconst v = contentCross + (horizontal ? pt + pb : pl + pr);\n\t\tif (horizontal) nh = v;\n\t\telse nw = v;\n\t}\n\tnw = Math.max(clampLayoutSize(nw, fl.minW, fl.maxW), pl + pr);\n\tnh = Math.max(clampLayoutSize(nh, fl.minH, fl.maxH), pt + pb);\n\tsetSize(s, frame, nw, nh);\n\tconstrain(s, frame, ow, oh);\n\tconst innerMain = horizontal ? nw - pl - pr : nh - pt - pb;\n\tconst innerCross = horizontal ? nh - pt - pb : nw - pl - pr;\n\n\t// Position. Several lines share the free space across the flow.\n\tlet crossPos = horizontal ? pt : pl;\n\tconst single = lines.length === 1;\n\tif (lines.length > 1) {\n\t\tconst linesCross = extents.reduce((sum, l) => sum + l[1], 0);\n\t\tconst between = lines.length - 1;\n\t\tconst spread = (innerCross - linesCross) / between;\n\t\tconst spaceBetween = al.alignContent === 'space-between';\n\t\tif (spaceBetween && spread > 0) {\n\t\t\tcrossGap = spread;\n\t\t} else {\n\t\t\tif (spaceBetween) crossGap = 0;\n\t\t\tconst free = innerCross - linesCross - crossGap * between;\n\t\t\tif (free > 0 && items.every((it) => it.fillCross)) {\n\t\t\t\tconst extra = free / lines.length;\n\t\t\t\tfor (const l of extents) l[1] += extra;\n\t\t\t} else if (Number.isFinite(free)) {\n\t\t\t\tcrossPos += alignCross === 'center' ? free / 2 : alignCross === 'end' ? free : 0;\n\t\t\t}\n\t\t}\n\t}\n\tlines.forEach((line, li) => {\n\t\tconst [used, ownCross] = extents[li];\n\t\tconst lineCross = single ? innerCross : ownCross;\n\t\tconst total = used - gap * Math.max(line.length - 1, 0);\n\t\tlet [pos, step] = mainOffsets(alignMain, innerMain, total, line.length, gap);\n\t\tpos += horizontal ? pl : pt;\n\t\tfor (const i of line) {\n\t\t\tconst it = items[i];\n\t\t\tconst crossSize = it.fillCross ? clampLayoutSize(lineCross, it.minCross, it.maxCross) : it.cross;\n\t\t\tlet off = 0;\n\t\t\tif (alignCross === 'baseline') off = it.baseOff;\n\t\t\telse if (alignCross === 'center') off = (lineCross - crossSize) / 2;\n\t\t\telse if (alignCross === 'end') off = lineCross - crossSize;\n\t\t\t// Fill across a wrapped line: its size is known only now.\n\t\t\tif (it.fillCross && wraps) resize(it, it.main, crossSize);\n\t\t\tconst x = horizontal ? pos : crossPos + off;\n\t\t\tconst y = horizontal ? crossPos + off : pos;\n\t\t\tplace(s, it.id, finite(x), finite(y));\n\t\t\tpos += it.main + step;\n\t\t}\n\t\tcrossPos += lineCross + crossGap;\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Grid\n\ninterface TrackSpec {\n\tsizing: AutoLayoutSizing;\n\tvalue: number;\n}\n\nfunction parseTrack(track: AutoLayoutGridTrack | undefined): TrackSpec {\n\tif (typeof track === 'number') return { sizing: 'fixed', value: track };\n\tif (track === 'hug') return { sizing: 'hug', value: 0 };\n\tif (typeof track === 'string') {\n\t\tconst match = /^\\s*(-?\\d*\\.?\\d+)\\s*fr\\s*$/i.exec(track);\n\t\tif (match) return { sizing: 'fill', value: Number(match[1]) };\n\t}\n\tif (track && typeof track === 'object' && 'fr' in track) return { sizing: 'fill', value: track.fr };\n\treturn { sizing: 'fill', value: 1 };\n}\n\nfunction trackList(spec: number | AutoLayoutGridTrack[] | undefined, fallback: number): { count: number; tracks: TrackSpec[] } {\n\tif (Array.isArray(spec)) return { count: Math.min(Math.max(spec.length, 1), MAX_TRACKS), tracks: spec.map(parseTrack) };\n\tconst n = typeof spec === 'number' && Number.isFinite(spec) ? Math.floor(spec) : fallback;\n\treturn { count: Math.min(Math.max(n, 1), MAX_TRACKS), tracks: [] };\n}\n\nfunction trackSpec(tracks: TrackSpec[], i: number): TrackSpec {\n\tconst t = tracks[i] ?? { sizing: 'fill', value: 1 };\n\tconst value = Number.isFinite(t.value) ? Math.min(Math.max(t.value, 0), 1e6) : 0;\n\treturn { sizing: t.sizing, value };\n}\n\n/** A grid child's cell (0-based column / row and spans). */\nexport interface AutoLayoutGridCell {\n\tid: string;\n\tcolumn: number;\n\trow: number;\n\tcolumnSpan: number;\n\trowSpan: number;\n}\n\n/** A grid frame's resolved geometry (frame-local): column x ranges, row y ranges, cells and the size it wants. */\nexport interface AutoLayoutGridGeometry {\n\tcolumns: [number, number][];\n\trows: [number, number][];\n\tcells: AutoLayoutGridCell[];\n\twidth: number;\n\theight: number;\n}\n\nfunction cellRect(g: AutoLayoutGridGeometry, c: AutoLayoutGridCell) {\n\tconst x0 = g.columns[c.column]?.[0] ?? 0;\n\tconst x1 = g.columns[c.column + c.columnSpan - 1]?.[1] ?? x0;\n\tconst y0 = g.rows[c.row]?.[0] ?? 0;\n\tconst y1 = g.rows[c.row + c.rowSpan - 1]?.[1] ?? y0;\n\treturn { x: x0, y: y0, width: Math.max(x1, x0) - x0, height: Math.max(y1, y0) - y0 };\n}\n\nconst toIndex = (v: unknown): number | undefined =>\n\ttypeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.floor(v) : undefined;\n\n/** Places grid children: anchored ones first, then the rest in reading order into the next free cells. */\nfunction gridPlace(s: LayoutState, al: AutoLayout, kids: string[]): { cells: AutoLayoutGridCell[]; rows: number } {\n\tconst cols = trackList(al.grid?.columns, 2).count;\n\tconst minRows = trackList(al.grid?.rows, 1).count;\n\tconst maxRows = Math.min(Math.max(Math.floor(MAX_GRID_CELLS / cols), 1), MAX_TRACKS);\n\tconst occ: boolean[][] = [];\n\tconst free = (c: number, r: number, cs: number, rs: number) => {\n\t\tfor (let ri = r; ri < r + rs; ri++) {\n\t\t\tconst row = occ[ri];\n\t\t\tif (!row) continue;\n\t\t\tfor (let ci = c; ci < c + cs; ci++) if (row[ci]) return false;\n\t\t}\n\t\treturn true;\n\t};\n\tconst mark = (c: number, r: number, cs: number, rs: number) => {\n\t\twhile (occ.length < r + rs) occ.push(new Array(cols).fill(false));\n\t\tfor (let ri = r; ri < r + rs; ri++) for (let ci = c; ci < c + cs; ci++) occ[ri][ci] = true;\n\t};\n\tconst spans = (id: string) => {\n\t\tconst area = getChildLayout(s.boxes.get(id)!.node).gridArea ?? {};\n\t\tconst cs = Math.min(Math.max(toIndex(area.columnSpan) ?? 1, 1), cols);\n\t\tconst rs = Math.min(Math.max(toIndex(area.rowSpan) ?? 1, 1), maxRows);\n\t\tconst col = toIndex(area.column);\n\t\tconst row = toIndex(area.row);\n\t\treturn {\n\t\t\tcol: col === undefined ? undefined : Math.min(col, cols - cs),\n\t\t\trow: row === undefined ? undefined : Math.min(row, maxRows - rs),\n\t\t\tcs,\n\t\t\trs\n\t\t};\n\t};\n\tconst placed: (AutoLayoutGridCell | undefined)[] = new Array(kids.length).fill(undefined);\n\tkids.forEach((id, i) => {\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tif (col !== undefined && row !== undefined) {\n\t\t\tmark(col, row, cs, rs);\n\t\t\tplaced[i] = { id, column: col, row, columnSpan: cs, rowSpan: rs };\n\t\t}\n\t});\n\tlet cursor: [number, number] = [0, 0]; // [row, column]\n\tkids.forEach((id, i) => {\n\t\tif (placed[i]) return;\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tconst lastRow = maxRows - rs;\n\t\tlet c = 0;\n\t\tlet r = 0;\n\t\tif (col !== undefined) {\n\t\t\tr = lastRow;\n\t\t\tfor (let ri = 0; ri <= lastRow; ri++) if (free(col, ri, cs, rs)) { r = ri; break; }\n\t\t\tc = col;\n\t\t} else if (row !== undefined) {\n\t\t\tr = row;\n\t\t\tfor (let ci = 0; ci <= cols - cs; ci++) if (free(ci, row, cs, rs)) { c = ci; break; }\n\t\t} else {\n\t\t\tlet [rr, cc] = cursor;\n\t\t\tlet found: [number, number] | undefined;\n\t\t\twhile (rr <= lastRow) {\n\t\t\t\tif (cc + cs <= cols && free(cc, rr, cs, rs)) {\n\t\t\t\t\tfound = [cc, rr];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcc += 1;\n\t\t\t\tif (cc + cs > cols) {\n\t\t\t\t\tcc = 0;\n\t\t\t\t\trr += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t[c, r] = found ?? [0, lastRow];\n\t\t\tcursor = [r, c + cs];\n\t\t}\n\t\tmark(c, r, cs, rs);\n\t\tplaced[i] = { id, column: c, row: r, columnSpan: cs, rowSpan: rs };\n\t});\n\tconst cells = placed.filter((c): c is AutoLayoutGridCell => !!c);\n\tconst rows = Math.max(cells.reduce((m, c) => Math.max(m, c.row + c.rowSpan), 0), minRows);\n\treturn { cells, rows: Math.min(rows, MAX_TRACKS) };\n}\n\n/**\n * Resolves track sizes: fixed tracks keep their size, hug tracks fit their content, fill tracks\n * share what is left by their `fr` like CSS (a track never gets less than its content).\n * `avail` is undefined when the frame hugs that axis.\n */\nfunction resolveTracks(tracks: TrackSpec[], content: number[], avail: number | undefined): number[] {\n\tconst out = content.map((c, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : c;\n\t});\n\tif (avail === undefined) return out;\n\tconst fr = (i: number) => trackSpec(tracks, i).value;\n\tlet flex = out.map((_, i) => i).filter((i) => trackSpec(tracks, i).sizing === 'fill');\n\tlet used = out.reduce((sum, v, i) => (trackSpec(tracks, i).sizing === 'fill' ? sum : sum + v), 0);\n\twhile (flex.length > 0) {\n\t\tconst total = flex.reduce((sum, i) => sum + fr(i), 0);\n\t\tconst unit = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;\n\t\tconst open = flex.filter((i) => unit * fr(i) >= out[i]);\n\t\tconst frozen = flex.filter((i) => unit * fr(i) < out[i]);\n\t\tif (frozen.length === 0) {\n\t\t\tfor (const i of open) out[i] = unit * fr(i);\n\t\t\tbreak;\n\t\t}\n\t\tused += frozen.reduce((sum, i) => sum + out[i], 0);\n\t\tflex = open;\n\t}\n\treturn out;\n}\n\n/** Content size per track: the largest single-span child; spanning children grow the content-sized tracks they span. */\nfunction trackContent(\n\tcells: AutoLayoutGridCell[],\n\tsizes: number[],\n\ttracks: TrackSpec[],\n\tn: number,\n\tgap: number,\n\tcolumns: boolean,\n\thug: boolean\n): number[] {\n\tconst byContent = (i: number) => trackSpec(tracks, i).sizing !== 'fixed';\n\tconst content = Array.from({ length: n }, (_, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : 0;\n\t});\n\tconst span = (c: AutoLayoutGridCell): [number, number] => (columns ? [c.column, c.columnSpan] : [c.row, c.rowSpan]);\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len === 1 && byContent(start) && start < n) content[start] = Math.max(content[start], sizes[k]);\n\t});\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len < 2) return;\n\t\tconst range = Array.from({ length: len }, (_, j) => start + j).filter((i) => i < n);\n\t\tif (!hug && range.some((i) => trackSpec(tracks, i).sizing === 'fill')) return;\n\t\tconst have = range.reduce((sum, i) => sum + content[i], 0) + gap * (len - 1);\n\t\tconst grow = range.filter(byContent);\n\t\tif (sizes[k] > have && grow.length > 0) {\n\t\t\tconst extra = (sizes[k] - have) / grow.length;\n\t\t\tfor (const i of grow) content[i] += extra;\n\t\t}\n\t});\n\treturn content;\n}\n\nfunction trackRanges(start: number, sizes: number[], gap: number): [number, number][] {\n\tlet x = start;\n\treturn sizes.map((size) => {\n\t\tconst r: [number, number] = [x, x + size];\n\t\tx += size + gap;\n\t\treturn r;\n\t});\n}\n\nfunction gridGeometryOf(s: LayoutState, frame: string): AutoLayoutGridGeometry | undefined {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al || al.mode !== 'grid') return undefined;\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst kids = flowChildren(s, frame);\n\tconst { cells, rows } = gridPlace(s, al, kids);\n\tconst colSpec = trackList(al.grid?.columns, 2);\n\tconst rowSpec = trackList(al.grid?.rows, 1);\n\tconst cols = colSpec.count;\n\tconst gx = Math.max(finite(al.gap), 0);\n\tconst gy = Math.max(al.rowGap !== undefined ? finite(al.rowGap) : gx, 0);\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ws: number[] = [];\n\tconst hs: number[] = [];\n\tfor (const c of cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tws.push(getLayoutSizing(b.node, 'width') === 'fill' && !hugW ? lim.minW ?? 0 : b.w);\n\t\ths.push(getLayoutSizing(b.node, 'height') === 'fill' && !hugH ? lim.minH ?? 0 : b.h);\n\t}\n\tconst colContent = trackContent(cells, ws, colSpec.tracks, cols, gx, true, hugW);\n\tconst rowContent = trackContent(cells, hs, rowSpec.tracks, rows, gy, false, hugH);\n\tconst gapsW = gx * Math.max(cols - 1, 0);\n\tconst gapsH = gy * Math.max(rows - 1, 0);\n\tconst colW = resolveTracks(colSpec.tracks, colContent, hugW ? undefined : finite(f.w) - pl - pr - gapsW);\n\tconst rowH = resolveTracks(rowSpec.tracks, rowContent, hugH ? undefined : finite(f.h) - pt - pb - gapsH);\n\tconst lim = limitsOf(f.node);\n\tconst width = hugW ? colW.reduce((a, b) => a + b, 0) + gapsW + pl + pr : finite(f.w);\n\tconst height = hugH ? rowH.reduce((a, b) => a + b, 0) + gapsH + pt + pb : finite(f.h);\n\treturn {\n\t\tcolumns: trackRanges(pl, colW, gx),\n\t\trows: trackRanges(pt, rowH, gy),\n\t\tcells,\n\t\twidth: Math.max(clampLayoutSize(width, lim.minW, lim.maxW), pl + pr),\n\t\theight: Math.max(clampLayoutSize(height, lim.minH, lim.maxH), pt + pb)\n\t};\n}\n\nfunction layoutGrid(s: LayoutState, frame: string, al: AutoLayout) {\n\tconst f = s.boxes.get(frame)!;\n\tconst fw = f.w;\n\tconst fh = f.h;\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst lim = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tsetSize(\n\t\ts,\n\t\tframe,\n\t\thugW ? fw : Math.max(clampLayoutSize(fw, lim.minW, lim.maxW), pl + pr),\n\t\thugH ? fh : Math.max(clampLayoutSize(fh, lim.minH, lim.maxH), pt + pb)\n\t);\n\tlet g = gridGeometryOf(s, frame);\n\tif (!g) return;\n\t// Fill-width children take their column width first, so wrapping text sizes its row.\n\tlet widened = false;\n\tfor (const c of g.cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tif (getLayoutSizing(b.node, 'width') === 'fill' && !hugW) {\n\t\t\tconst l = limitsOf(b.node);\n\t\t\tresizeChild(s, c.id, clampLayoutSize(cellRect(g, c).width, l.minW, l.maxW), b.h);\n\t\t\twidened = true;\n\t\t}\n\t}\n\tif (widened) g = gridGeometryOf(s, frame) ?? g;\n\tsetSize(s, frame, g.width, g.height);\n\tconstrain(s, frame, fw, fh);\n\tconst frameX: AutoLayoutCellAlign = al.alignMain === 'center' ? 'center' : al.alignMain === 'end' ? 'end' : 'start';\n\tconst frameY: AutoLayoutCellAlign = al.alignCross === 'center' ? 'center' : al.alignCross === 'end' ? 'end' : 'start';\n\tconst offset = (a: AutoLayoutCellAlign, free: number) => (a === 'center' ? free / 2 : a === 'end' ? free : 0);\n\tfor (const c of g.cells) {\n\t\tconst cell = cellRect(g, c);\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst l = limitsOf(b.node);\n\t\tconst area = getChildLayout(b.node).gridArea ?? {};\n\t\tconst fillW = getLayoutSizing(b.node, 'width') === 'fill' && !hugW;\n\t\tconst fillH = getLayoutSizing(b.node, 'height') === 'fill' && !hugH;\n\t\tif (fillW || fillH) {\n\t\t\tresizeChild(\n\t\t\t\ts,\n\t\t\t\tc.id,\n\t\t\t\tfillW ? clampLayoutSize(cell.width, l.minW, l.maxW) : b.w,\n\t\t\t\tfillH ? clampLayoutSize(cell.height, l.minH, l.maxH) : b.h\n\t\t\t);\n\t\t}\n\t\tconst ox = offset(area.alignX ?? frameX, cell.width - b.w);\n\t\tconst oy = offset(area.alignY ?? frameY, cell.height - b.h);\n\t\tplace(s, c.id, finite(cell.x + ox), finite(cell.y + oy));\n\t}\n}\n\n// ---------------------------------------------------------------------------------------------\n// Constraints\n\n/**\n * After `id` changed size from `ow × oh`, moves / resizes its children by their constraints\n * (recursively). In an auto layout frame only absolute children follow constraints.\n */\nfunction constrain(s: LayoutState, id: string, ow: number, oh: number) {\n\tconst f = s.boxes.get(id);\n\tif (!f) return;\n\tconst nw = f.w;\n\tconst nh = f.h;\n\tif (Math.abs(nw - ow) < 1e-9 && Math.abs(nh - oh) < 1e-9) return;\n\tconst absoluteOnly = !!getAutoLayout(f.node);\n\tconst sx = ow > 1e-9 ? nw / ow : 1;\n\tconst sy = oh > 1e-9 ? nh / oh : 1;\n\ts.guard(`constrain:${id}`, () => {\n\t\tfor (const k of f.kids) {\n\t\t\tconst c = s.boxes.get(k)!;\n\t\t\tconst child = getChildLayout(c.node);\n\t\t\tif (absoluteOnly && !child.absolute) continue;\n\t\t\tlet x0 = c.x;\n\t\t\tlet x1 = c.x + c.w;\n\t\t\tlet y0 = c.y;\n\t\t\tlet y1 = c.y + c.h;\n\t\t\tconst dx = nw - ow;\n\t\t\tconst dy = nh - oh;\n\t\t\tswitch (child.constraints?.horizontal ?? 'left') {\n\t\t\t\tcase 'right':\n\t\t\t\t\tx0 += dx;\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'leftRight':\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\tx0 += dx / 2;\n\t\t\t\t\tx1 += dx / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\tx0 *= sx;\n\t\t\t\t\tx1 *= sx;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tswitch (child.constraints?.vertical ?? 'top') {\n\t\t\t\tcase 'bottom':\n\t\t\t\t\ty0 += dy;\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'topBottom':\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\ty0 += dy / 2;\n\t\t\t\t\ty1 += dy / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\ty0 *= sy;\n\t\t\t\t\ty1 *= sy;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst cw = c.w;\n\t\t\tconst ch = c.h;\n\t\t\tconst resized = Math.abs(x1 - x0 - cw) > 1e-9 || Math.abs(y1 - y0 - ch) > 1e-9;\n\t\t\tplace(s, k, finite(x0), finite(y0));\n\t\t\tif (resized) {\n\t\t\t\tsetSize(s, k, x1 - x0, y1 - y0);\n\t\t\t\tif (c.kids.length > 0) constrain(s, k, cw, ch);\n\t\t\t\tif (getAutoLayout(c.node)) layout(s, k);\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Public API\n\n/**\n * Lays out one auto layout frame's children and sizes the frame when it hugs (nested frames\n * that are resized by the layout are laid out again). Returns the position / size updates.\n */\nexport function layoutFrame(nodes: CanvasNode[], frameId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tlayout(s, frameId);\n\treturn s.updates();\n}\n\nfunction postOrder(s: LayoutState, nodes: CanvasNode[]): string[] {\n\tconst out: string[] = [];\n\tconst seen = new Set<string>();\n\tconst visit = (id: string) => {\n\t\tif (seen.has(id)) return;\n\t\tseen.add(id);\n\t\tfor (const k of s.boxes.get(id)?.kids ?? []) visit(k);\n\t\tout.push(id);\n\t};\n\tconst roots = nodes.filter((n) => !n.parentId || !s.boxes.has(n.parentId));\n\tfor (const r of roots) visit(r.id);\n\tfor (const n of nodes) visit(n.id); // nodes in parent cycles\n\treturn out;\n}\n\n/** Lays out every auto layout frame, children before parents, so nested hugging settles in one pass. */\nexport function layoutAll(nodes: CanvasNode[], options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tfor (const id of postOrder(s, nodes)) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Lays out `changedId` (when it is a frame) and then each of its auto layout ancestors,\n * innermost first. Use it after a node inside a frame was added, removed, resized or moved.\n */\nexport function layoutUp(nodes: CanvasNode[], changedId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, [changedId], options);\n}\n\n/** {@link layoutUp} for several changed nodes at once (each chain once, innermost frames first). */\nexport function layoutUpMany(nodes: CanvasNode[], changedIds: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconst chains = [...new Set(changedIds)].filter((id) => s.boxes.has(id)).map((id) => [id, ...s.ancestors(id)]);\n\t// Deeper frames first so a parent sees its children's final sizes.\n\tconst depth = new Map<string, number>();\n\tfor (const chain of chains) chain.forEach((id, i) => depth.set(id, Math.max(depth.get(id) ?? 0, chain.length - 1 - i)));\n\tconst order = [...depth.keys()].sort((a, b) => depth.get(b)! - depth.get(a)!);\n\tfor (const id of order) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Children of `parentId` follow their constraints after the parent was resized from\n * `previousSize` to its current size. In an auto layout frame only absolute children move\n * (the layout places the others). Returns the updates.\n */\nexport function applyConstraints(\n\tnodes: CanvasNode[],\n\tparentId: string,\n\tpreviousSize: { width: number; height: number },\n\toptions: AutoLayoutOptions = {}\n): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconstrain(s, parentId, previousSize.width, previousSize.height);\n\treturn s.updates();\n}\n\n/** The resolved grid of a grid frame (frame-local), from its children's current sizes. */\nexport function getAutoLayoutGrid(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\toptions: AutoLayoutOptions = {}\n): AutoLayoutGridGeometry | undefined {\n\treturn gridGeometryOf(new LayoutState(nodes, options), frameId);\n}\n\n/** Ids of the children a frame places (visible, not absolute), in order. */\nexport function getFlowChildren(nodes: CanvasNode[], frameId: string): string[] {\n\treturn nodes\n\t\t.filter((n) => n.parentId === frameId && n.id !== frameId && n.hidden !== true && !getChildLayout(n).absolute)\n\t\t.map((n) => n.id);\n}\n\n/** The nearest auto layout frame that places `id` (its parent, when `id` is a flow child). */\nexport function getLayoutParent(nodes: CanvasNode[], id: string): CanvasNode | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node?.parentId || getChildLayout(node).absolute) return undefined;\n\tconst parent = lookup.get(node.parentId);\n\treturn isAutoLayoutFrame(parent) ? parent : undefined;\n}\n\n/**\n * Makes a node's sizing coherent with where it sits, and that of its children: `fill` needs an\n * auto layout parent that does not hug that axis (and a flow child), `hug` needs an auto layout\n * frame or a text-like node (see `measure.textSize`); otherwise the axis becomes `fixed`.\n * Writes `data.childLayout.sizing`. Returns a new array (unchanged nodes keep their identity).\n */\nexport function makeSizingCoherent<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tid: string,\n\toptions: AutoLayoutOptions = {}\n): TNode[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targets = new Set([id, ...nodes.filter((n) => n.parentId === id).map((n) => n.id)]);\n\tif (!lookup.has(id)) return nodes;\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\tconst flowParent = parent && isAutoLayoutFrame(parent) && !getChildLayout(node).absolute ? parent : undefined;\n\t\tconst canHug = isAutoLayoutFrame(node) || options.measure?.textSize?.(node) !== undefined;\n\t\tconst coherent = (axis: Axis): AutoLayoutSizing => {\n\t\t\tconst sizing = getLayoutSizing(node, axis);\n\t\t\tif (sizing === 'fill' && (!flowParent || getLayoutSizing(flowParent, axis) === 'hug')) return 'fixed';\n\t\t\tif (sizing === 'hug' && !canHug) return 'fixed';\n\t\t\treturn sizing;\n\t\t};\n\t\tconst width = coherent('width');\n\t\tconst height = coherent('height');\n\t\tif (width === getLayoutSizing(node, 'width') && height === getLayoutSizing(node, 'height')) return node;\n\t\tchanged = true;\n\t\tconst child = getChildLayout(node);\n\t\treturn {\n\t\t\t...node,\n\t\t\tdata: { ...node.data, [CHILD_LAYOUT_DATA_KEY]: { ...child, sizing: { ...child.sizing, width, height } } }\n\t\t};\n\t});\n\treturn changed ? next : nodes;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Reordering\n\nexport interface AutoLayoutInsertTarget {\n\tframeId: string;\n\t/** Index among the frame's flow children, not counting the dragged node. */\n\tindex: number;\n\t/** Insertion marker (absolute flow coordinates) for `<CanvasGuidesLayer>`. */\n\tguide: GuideLine;\n}\n\n/**\n * Where a node dropped at `point` (absolute flow coordinates) goes among the flow children of\n * auto layout frame `frameId`: the reading-order index of the slot under the point. `excludeId`\n * (the dragged node) is left out of the siblings.\n */\nexport function getAutoLayoutInsertTarget(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\tpoint: XYPosition,\n\toptions: AutoLayoutOptions & { excludeId?: string } = {}\n): AutoLayoutInsertTarget | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst frame = lookup.get(frameId);\n\tconst al = getAutoLayout(frame);\n\tif (!frame || !al) return undefined;\n\tconst s = new LayoutState(nodes, options);\n\tconst origin = getAbsolutePosition(frame, lookup);\n\tconst px = point.x - origin.x;\n\tconst py = point.y - origin.y;\n\tconst siblings = flowChildren(s, frameId)\n\t\t.filter((id) => id !== options.excludeId)\n\t\t.map((id) => s.boxes.get(id)!);\n\t// Grid and wrapped layouts read in rows (vertical wrap: columns); plain stacks along their axis.\n\tconst horizontal = al.mode !== 'vertical';\n\tconst main = (b: { x: number; y: number }) => (horizontal ? b.x : b.y);\n\tconst mainSize = (b: Box) => (horizontal ? b.w : b.h);\n\tconst cross = (b: { x: number; y: number }) => (horizontal ? b.y : b.x);\n\tconst crossSize = (b: Box) => (horizontal ? b.h : b.w);\n\tconst p = { x: px, y: py };\n\tconst multiLine = al.mode === 'grid' || !!al.wrap;\n\tlet index = 0;\n\tfor (const b of siblings) {\n\t\tconst before = multiLine\n\t\t\t? cross(b) + crossSize(b) <= cross(p) ||\n\t\t\t\t(cross(b) <= cross(p) && main(b) + mainSize(b) / 2 < main(p))\n\t\t\t: main(b) + mainSize(b) / 2 < main(p);\n\t\tif (before) index += 1;\n\t}\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst prev = siblings[index - 1];\n\tconst next = siblings[index];\n\tconst f = s.boxes.get(frameId)!;\n\tlet guide: GuideLine;\n\tif (horizontal) {\n\t\tconst x = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.x + prev.w + next.x) / 2\n\t\t\t: prev\n\t\t\t\t? prev.x + prev.w\n\t\t\t\t: next\n\t\t\t\t\t? next.x\n\t\t\t\t\t: pl;\n\t\tconst ref = next ?? prev;\n\t\tconst y1 = ref ? ref.y : pt;\n\t\tconst y2 = ref ? ref.y + ref.h : f.h - pb;\n\t\tguide = { orientation: 'vertical', kind: 'edge', x1: origin.x + x, y1: origin.y + y1, x2: origin.x + x, y2: origin.y + y2 };\n\t} else {\n\t\tconst y = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.y + prev.h + next.y) / 2\n\t\t\t: prev\n\t\t\t\t? prev.y + prev.h\n\t\t\t\t: next\n\t\t\t\t\t? next.y\n\t\t\t\t\t: pt;\n\t\tconst ref = next ?? prev;\n\t\tconst x1 = ref ? ref.x : pl;\n\t\tconst x2 = ref ? ref.x + ref.w : f.w - pr;\n\t\tguide = { orientation: 'horizontal', kind: 'edge', x1: origin.x + x1, y1: origin.y + y, x2: origin.x + x2, y2: origin.y + y };\n\t}\n\treturn { frameId, index, guide };\n}\n\n/**\n * Moves child `childId` of an auto layout frame to flow index `index` (among its flow siblings)\n * by reordering the node array; parents still precede their children. Returns a new array\n * (the same array when nothing moves).\n */\nexport function moveLayoutChild<TNode extends CanvasNode>(nodes: TNode[], childId: string, index: number): TNode[] {\n\tconst child = nodes.find((n) => n.id === childId);\n\tif (!child?.parentId) return nodes;\n\tconst siblings = nodes.filter((n) => n.parentId === child.parentId && n.id !== child.parentId);\n\tconst flow = siblings.filter((n) => n.hidden !== true && !getChildLayout(n).absolute && n.id !== childId);\n\tconst clamped = Math.min(Math.max(Math.floor(Number.isFinite(index) ? index : 0), 0), flow.length);\n\t// Insert before the flow sibling now at `clamped` (or after the last one).\n\tconst order = siblings.filter((n) => n.id !== childId);\n\tconst anchor = flow[clamped];\n\tconst at = anchor ? order.indexOf(anchor) : flow.length > 0 ? order.indexOf(flow[flow.length - 1]) + 1 : order.length;\n\torder.splice(at, 0, child);\n\tif (order.every((n, i) => n === siblings[i])) return nodes;\n\t// Put the siblings back into the slots they occupied, in their new order.\n\tlet k = 0;\n\tconst parentId = child.parentId;\n\tconst placed = nodes.map((n) => (n.parentId === parentId && n.id !== parentId ? order[k++] : n));\n\treturn parentsFirst(placed);\n}\n\n/** Stable reorder so every node comes after its parent (xyflow requires it). */\nfunction parentsFirst<TNode extends CanvasNode>(nodes: TNode[]): TNode[] {\n\tconst ids = new Set(nodes.map((n) => n.id));\n\tconst pending = new Map<string, TNode[]>();\n\tconst done = new Set<string>();\n\tconst out: TNode[] = [];\n\tconst emit = (n: TNode) => {\n\t\tout.push(n);\n\t\tdone.add(n.id);\n\t\tconst waiting = pending.get(n.id);\n\t\tif (waiting) {\n\t\t\tpending.delete(n.id);\n\t\t\tfor (const w of waiting) emit(w);\n\t\t}\n\t};\n\tfor (const n of nodes) {\n\t\tif (n.parentId && ids.has(n.parentId) && !done.has(n.parentId) && n.parentId !== n.id) {\n\t\t\tif (!pending.has(n.parentId)) pending.set(n.parentId, []);\n\t\t\tpending.get(n.parentId)!.push(n);\n\t\t} else emit(n);\n\t}\n\tfor (const waiting of pending.values()) for (const n of waiting) if (!done.has(n.id)) emit(n); // cycles\n\treturn out;\n}\n","import type { Command } from '../history/types.js';\nimport {\n\talignNodes,\n\tapplyNodeUpdates,\n\tdistributeNodes,\n\tinvertNodeUpdates,\n\tisLocked,\n\tlockNodes,\n\tmatchSize,\n\treorderNodes,\n\tunlockNodes,\n\ttype AlignEdge,\n\ttype AlignNodesOptions,\n\ttype CanvasNodeUpdate,\n\ttype DistributeAxis,\n\ttype DistributeMode,\n\ttype DistributeNodesOptions,\n\ttype MatchSizeOptions,\n\ttype NodeRectOptions,\n\ttype SizeDimension,\n\ttype ZOrderOperation\n} from './arrange.js';\nimport type { CanvasArrangeAction } from './arrangeShortcuts.js';\nimport {\n\tapplyShapeCombination,\n\tapplyShapeReplacement,\n\tcanCombineShapeNodes,\n\tcanOffsetShapeNodes,\n\tcanOutlineShapeNodeStrokes,\n\tcombineShapeNodes,\n\toffsetShapeNodes,\n\toutlineShapeNodeStrokes,\n\trevertShapeCombination,\n\ttype CombineShapeNodesOptions,\n\ttype CombineShapeNodesResult,\n\ttype OffsetShapeNodesOptions,\n\ttype OutlineShapeStrokesOptions,\n\ttype ShapeOperation,\n\ttype ShapePathEditResult\n} from './shapeNodes.js';\nimport {\n\tcreateNodeGuideIndex,\n\tsnapNodeDrag,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions,\n\ttype SnapNodeDragResult\n} from './guides.js';\nimport {\n\tgetResizingNodeRect,\n\tinferResizeHandle,\n\tsnapNodeResize,\n\ttype ResizeHandle,\n\ttype SnapNodeResizeResult\n} from './resizeSnap.js';\nimport { createNodeLookup } from './arrange.js';\nimport { layoutUpMany, type AutoLayoutOptions } from './autoLayout.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/** Minimal history surface (`HistoryManager` satisfies it). */\nexport interface ArrangeHistoryTarget {\n\texecute(command: Command): void;\n}\n\nexport interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** Ids acted on when a method is called without ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** When set, every arrange action is recorded as one undoable command. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Current zoom, used to convert the guide threshold from screen pixels (default 1). */\n\tgetZoom?: () => number;\n\t/** Smart-guide options (threshold in screen px, edges / centres / spacing toggles). */\n\tguides?: Omit<SnapGuidesOptions, 'zoom'>;\n\t/** Turns drag snapping off while keeping the arrange actions (default `true`). */\n\tsnapping?: boolean;\n\t/** Turns resize snapping off (default: follows `snapping`). */\n\tresizeSnapping?: boolean;\n\t/** Resize options passed to `snapResize` (`keepAspectRatio`, `minWidth`, `minHeight`). */\n\tresize?: { keepAspectRatio?: boolean; minWidth?: number; minHeight?: number };\n\t/** Size resolution for unmeasured nodes. */\n\trectOptions?: NodeRectOptions;\n\t/** Called whenever the guide segments change (set while dragging, cleared on drop). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n\t/**\n\t * Re-run auto layout (`data.layout` frames) around the nodes an arrange action moves or\n\t * resizes, in the same undoable step. `true` uses the default measuring.\n\t */\n\tautoLayout?: boolean | AutoLayoutOptions;\n}\n\n/**\n * A React Flow / SvelteFlow style node change; only `position` changes are inspected.\n * `id` is optional because a batch can contain changes without one (React Flow's\n * `add` change carries an `item` instead), so `NodeChange[]` must be accepted as is.\n */\nexport interface PositionChangeLike {\n\ttype: string;\n\tid?: string;\n\tposition?: XYPosition;\n\tdragging?: boolean;\n\t[key: string]: any;\n}\n\n/**\n * Framework-agnostic arrange controller shared by the Svelte rune and the React hook:\n * align / distribute / match size / lock / z-order actions on the selection (optionally\n * undoable through a `HistoryManager`) and smart-guide snapping during drags.\n */\nexport function createCanvasArrangeController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasArrangeControllerOptions<TNode>\n) {\n\tlet guides: GuideLine[] = [];\n\tlet dragIndex: GuideIndex | null = null;\n\tlet dragKey = '';\n\t// Snapped positions from the last dragging batch, re-applied to the drag-stop change.\n\tlet lastSnapped = new Map<string, XYPosition>();\n\t// Resize snapping: the node, its box when the resize started, cached targets and the\n\t// last snapped box (absolute) re-applied when the flow writes the raw end size.\n\tlet resizeId: string | null = null;\n\tlet resizeStart: Rect | null = null;\n\tlet resizeIndex: GuideIndex | null = null;\n\tlet lastResized: SnapNodeResizeResult | null = null;\n\n\tconst selectedIds = (ids?: Iterable<string>) =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/** Applies updates as one (optionally undoable) step. Returns the updates. */\n\tfunction apply(name: string, updates: CanvasNodeUpdate[]): CanvasNodeUpdate[] {\n\t\tif (updates.length === 0) return updates;\n\t\tif (options.autoLayout) {\n\t\t\tconst layoutOptions = options.autoLayout === true ? { rectOptions: options.rectOptions } : options.autoLayout;\n\t\t\tconst moved = applyNodeUpdates(options.getNodes(), updates);\n\t\t\tconst layout = layoutUpMany(moved, updates.map((u) => u.id), layoutOptions);\n\t\t\tif (layout.length > 0) {\n\t\t\t\tconst byId = new Map<string, CanvasNodeUpdate>();\n\t\t\t\tfor (const u of [...updates, ...layout]) byId.set(u.id, { ...byId.get(u.id), ...u });\n\t\t\t\tupdates = [...byId.values()];\n\t\t\t}\n\t\t}\n\t\tconst inverse = invertNodeUpdates(options.getNodes(), updates);\n\t\tconst run = (u: CanvasNodeUpdate[]) => options.setNodes(applyNodeUpdates(options.getNodes(), u));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });\n\t\t} else {\n\t\t\trun(updates);\n\t\t}\n\t\treturn updates;\n\t}\n\n\tfunction setLocked(name: string, ids: string[], locked: boolean): string[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst set = new Set(ids);\n\t\tconst changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);\n\t\tif (changed.length === 0) return changed;\n\t\tconst lock = () => options.setNodes(lockNodes(options.getNodes(), changed));\n\t\tconst unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });\n\t\t} else if (locked) lock();\n\t\telse unlock();\n\t\treturn changed;\n\t}\n\n\tconst rect = () => options.rectOptions ?? {};\n\n\tconst controller = {\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\t\tapply,\n\t\talign(edge: AlignEdge, ids?: Iterable<string>, alignOptions: AlignNodesOptions = {}) {\n\t\t\treturn apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));\n\t\t},\n\t\tdistribute(\n\t\t\taxis: DistributeAxis,\n\t\t\tmode: DistributeMode = 'gaps',\n\t\t\tids?: Iterable<string>,\n\t\t\tdistributeOptions: DistributeNodesOptions = {}\n\t\t) {\n\t\t\treturn apply(\n\t\t\t\t`Distribute ${axis}`,\n\t\t\t\tdistributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })\n\t\t\t);\n\t\t},\n\t\tmatchSize(dimension: SizeDimension, ids?: Iterable<string>, sizeOptions: MatchSizeOptions = {}) {\n\t\t\treturn apply(\n\t\t\t\t'Make Same Size',\n\t\t\t\tmatchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })\n\t\t\t);\n\t\t},\n\t\treorder(operation: ZOrderOperation, ids?: Iterable<string>) {\n\t\t\treturn apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));\n\t\t},\n\t\tbringForward: (ids?: Iterable<string>) => controller.reorder('bringForward', ids),\n\t\tsendBackward: (ids?: Iterable<string>) => controller.reorder('sendBackward', ids),\n\t\tbringToFront: (ids?: Iterable<string>) => controller.reorder('bringToFront', ids),\n\t\tsendToBack: (ids?: Iterable<string>) => controller.reorder('sendToBack', ids),\n\t\tlock: (ids?: Iterable<string>) => setLocked('Lock', selectedIds(ids), true),\n\t\tunlock: (ids?: Iterable<string>) => setLocked('Unlock', selectedIds(ids), false),\n\t\ttoggleLock(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst set = new Set(list);\n\t\t\tconst anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));\n\t\t\treturn anyUnlocked ? controller.lock(list) : controller.unlock(list);\n\t\t},\n\t\tisLocked: (id: string) => isLocked(options.getNodes().find((n) => n.id === id)),\n\n\t\t/**\n\t\t * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the\n\t\t * shape nodes among `ids` (default: the selection). Replaces them with the result as\n\t\t * one undoable step. Refuses without changing anything when a selected shape is\n\t\t * locked, fewer than two shapes are selected, or the result is empty.\n\t\t */\n\t\tcombineShapes(\n\t\t\toperation: ShapeOperation,\n\t\t\tids?: Iterable<string>,\n\t\t\tcombineOptions: CombineShapeNodesOptions = {}\n\t\t): CombineShapeNodesResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = combineShapeNodes(before, selectedIds(ids), operation, { ...rect(), ...combineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst name = `${operation[0].toUpperCase()}${operation.slice(1)} Shapes`;\n\t\t\tconst run = () => options.setNodes(applyShapeCombination(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name, execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\tunite: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('unite', ids, o),\n\t\tintersect: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('intersect', ids, o),\n\t\tsubtract: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('subtract', ids, o),\n\t\texclude: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('exclude', ids, o),\n\t\tdivide: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('divide', ids, o),\n\t\t/** Whether the shape operations can run on `ids` (default: the selection). */\n\t\tcanCombineShapes: (ids?: Iterable<string>) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Offset Path: grows (`distance > 0`) or shrinks (`distance < 0`) every shape among\n\t\t * `ids` (default: the selection) by `distance` flow units, keeping curves, as one\n\t\t * undoable step. Refuses without changing anything when a shape is locked, no shape is\n\t\t * selected or every result is empty.\n\t\t */\n\t\toffsetShapes(\n\t\t\tdistance: number,\n\t\t\tids?: Iterable<string>,\n\t\t\toffsetOptions: OffsetShapeNodesOptions = {}\n\t\t): ShapePathEditResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = offsetShapeNodes(before, selectedIds(ids), distance, { ...rect(), ...offsetOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name: 'Offset Path', execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\t/** Whether `offsetShapes` can run on `ids` (default: the selection). */\n\t\tcanOffsetShapes: (ids?: Iterable<string>) => canOffsetShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Outline Stroke: converts the strokes of the shapes among `ids` (default: the\n\t\t * selection) to filled path shapes, as one undoable step. Refuses when a shape is\n\t\t * locked or no selected shape has a visible stroke.\n\t\t */\n\t\toutlineStroke(ids?: Iterable<string>, outlineOptions: OutlineShapeStrokesOptions = {}): ShapePathEditResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = outlineShapeNodeStrokes(before, selectedIds(ids), { ...rect(), ...outlineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name: 'Outline Stroke', execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\t/** Whether `outlineStroke` can run on `ids` (default: the selection). */\n\t\tcanOutlineStroke: (ids?: Iterable<string>) => canOutlineShapeNodeStrokes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Runs a shortcut action. Scene actions are not handled here (see the scenes store);\n\t\t * returns `false` for them and for unknown actions.\n\t\t */\n\t\trunAction(action: CanvasArrangeAction): boolean {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'alignLeft': controller.align('left'); return true;\n\t\t\t\tcase 'alignCenter': controller.align('center'); return true;\n\t\t\t\tcase 'alignRight': controller.align('right'); return true;\n\t\t\t\tcase 'alignTop': controller.align('top'); return true;\n\t\t\t\tcase 'alignMiddle': controller.align('middle'); return true;\n\t\t\t\tcase 'alignBottom': controller.align('bottom'); return true;\n\t\t\t\tcase 'distributeHorizontal': controller.distribute('horizontal'); return true;\n\t\t\t\tcase 'distributeVertical': controller.distribute('vertical'); return true;\n\t\t\t\tcase 'lock': controller.lock(); return true;\n\t\t\t\tcase 'unlock': controller.unlock(); return true;\n\t\t\t\tcase 'bringForward': controller.bringForward(); return true;\n\t\t\t\tcase 'sendBackward': controller.sendBackward(); return true;\n\t\t\t\tcase 'bringToFront': controller.bringToFront(); return true;\n\t\t\t\tcase 'sendToBack': controller.sendToBack(); return true;\n\t\t\t\tdefault: return false;\n\t\t\t}\n\t\t},\n\n\t\t/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */\n\t\tbeginDrag(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tdragKey = list.slice().sort().join('\\u0000');\n\t\t\tdragIndex = createNodeGuideIndex(options.getNodes(), list, rect());\n\t\t},\n\n\t\t/**\n\t\t * Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other\n\t\t * nodes, writes the snapped positions back through `setNodes` (not recorded in\n\t\t * history) and updates `guides`.\n\t\t */\n\t\tdrag(ids?: Iterable<string>): SnapNodeDragResult | null {\n\t\t\tif (options.snapping === false) return null;\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst key = list.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(list);\n\t\t\tconst result = snapNodeDrag(options.getNodes(), list, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tlastSnapped = new Map();\n\t\t\tif (result.updates.length > 0) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(options.getNodes(), result.updates));\n\t\t\t\tfor (const u of result.updates) if (u.position) lastSnapped.set(u.id, u.position);\n\t\t\t}\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so\n\t\t * the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.\n\t\t * A batch without a dragging position change ends the drag and clears the guides.\n\t\t */\n\t\tsnapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dragging = changes.filter(\n\t\t\t\t(c): c is TChange & { id: string; position: XYPosition } =>\n\t\t\t\t\tc.type === 'position' && typeof c.id === 'string' && !!c.position && !!c.dragging\n\t\t\t);\n\t\t\tif (dragging.length === 0) {\n\t\t\t\tif (!changes.some((c) => c.type === 'position' && c.dragging === false)) return changes;\n\t\t\t\t// React Flow's drag-stop change carries the raw pointer position, which would\n\t\t\t\t// undo the snap of the last frame; keep the snapped position instead.\n\t\t\t\tconst snapped = lastSnapped;\n\t\t\t\tlastSnapped = new Map(); // applied to the changes below, not by endDrag\n\t\t\t\tcontroller.endDrag();\n\t\t\t\tconst keep = (c: TChange) =>\n\t\t\t\t\tc.type === 'position' && c.dragging === false && !!c.position && c.id !== undefined && snapped.has(c.id);\n\t\t\t\tif (!changes.some(keep)) return changes;\n\t\t\t\treturn changes.map((c) => (keep(c) ? { ...c, position: snapped.get(c.id!)! } : c));\n\t\t\t}\n\t\t\tif (options.snapping === false) return changes;\n\t\t\tconst ids = dragging.map((c) => c.id);\n\t\t\tconst key = ids.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(ids);\n\t\t\tconst proposed = new Map(dragging.map((c) => [c.id, c.position]));\n\t\t\tconst nodes = options.getNodes().map((n) => (proposed.has(n.id) ? { ...n, position: proposed.get(n.id)! } : n));\n\t\t\tconst result = snapNodeDrag(nodes, ids, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst { x: dx, y: dy } = result.offset;\n\t\t\tlastSnapped = new Map();\n\t\t\tif (dx === 0 && dy === 0) return changes;\n\t\t\t// Children of a dragged parent move with it; only shift top-level dragged nodes.\n\t\t\tconst parents = new Map(nodes.map((n) => [n.id, n.parentId]));\n\t\t\tconst shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? '')));\n\t\t\treturn changes.map((c) => {\n\t\t\t\tif (!(c.type === 'position' && c.position && c.dragging && c.id !== undefined && shift.has(c.id))) return c;\n\t\t\t\tconst position = { x: c.position.x + dx, y: c.position.y + dy };\n\t\t\t\tlastSnapped.set(c.id, position);\n\t\t\t\treturn { ...c, position };\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Starts resize snapping for node `id`: records its box (to tell which handle is\n\t\t * dragged) and caches the snap targets. Optional: `resize` starts lazily.\n\t\t */\n\t\tbeginResize(id: string) {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tresizeId = id;\n\t\t\tresizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;\n\t\t\tresizeIndex = createNodeGuideIndex(nodes, [id], rect());\n\t\t\tlastResized = null;\n\t\t},\n\n\t\t/**\n\t\t * Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the\n\t\t * new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and\n\t\t * writes the snapped box back through `setNodes` (not recorded in history). `handle`\n\t\t * defaults to the one inferred from the edges that moved since `beginResize`. A locked\n\t\t * node is restored to its starting box.\n\t\t */\n\t\tresize(id: string, handle?: ResizeHandle): SnapNodeResizeResult | null {\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tif (!node || !resizeStart) return null;\n\t\t\tconst proposed = getResizingNodeRect(node, lookup, rect());\n\t\t\tif (isLocked(node)) {\n\t\t\t\tconst start = resizeStart;\n\t\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\t\tif (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {\n\t\t\t\t\toptions.setNodes(\n\t\t\t\t\t\tapplyNodeUpdates(nodes, [\n\t\t\t\t\t\t\t{ id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif ((options.resizeSnapping ?? options.snapping) === false) return null;\n\t\t\tconst h = handle ?? inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!h) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, h, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tlastResized = result.updates.length > 0 ? result : null;\n\t\t\tif (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * Ends resize snapping: clears guides and re-applies the last snapped box if the flow\n\t\t * wrote the raw pointer size afterwards.\n\t\t */\n\t\tendResize() {\n\t\t\tconst last = lastResized;\n\t\t\tresizeId = null;\n\t\t\tresizeStart = null;\n\t\t\tresizeIndex = null;\n\t\t\tlastResized = null;\n\t\t\tsetGuides([]);\n\t\t\tconst update = last?.updates[0];\n\t\t\tif (!update) return;\n\t\t\tconst node = options.getNodes().find((n) => n.id === update.id);\n\t\t\tif (!node) return;\n\t\t\tconst drift =\n\t\t\t\tnode.position.x !== update.position!.x ||\n\t\t\t\tnode.position.y !== update.position!.y ||\n\t\t\t\tnode.width !== update.width ||\n\t\t\t\tnode.height !== update.height;\n\t\t\tif (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: snaps the `dimensions` (and matching `position`) changes a\n\t\t * `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end\n\t\t * change (`resizing: false`) carries the raw pointer size, so the last snapped size is\n\t\t * kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.\n\t\t */\n\t\tsnapResizeChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dims = changes.find(\n\t\t\t\t(c) => c.type === 'dimensions' && typeof c.id === 'string' && !!c.dimensions && typeof c.resizing === 'boolean'\n\t\t\t);\n\t\t\tif (!dims) return changes;\n\t\t\tconst id = dims.id as string;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tconst isResizeChange = (c: TChange) => c.id === id && (c.type === 'dimensions' || c.type === 'position');\n\t\t\tif (node && isLocked(node)) {\n\t\t\t\tif (dims.resizing === false) controller.endResize();\n\t\t\t\treturn changes.filter((c) => !isResizeChange(c));\n\t\t\t}\n\t\t\tif (dims.resizing === false) {\n\t\t\t\tconst last = lastResized?.updates[0];\n\t\t\t\tlastResized = null; // applied to the change below, not by endResize\n\t\t\t\tcontroller.endResize();\n\t\t\t\tif (!last || last.id !== id) return changes;\n\t\t\t\treturn changes.map((c) =>\n\t\t\t\t\tc === dims ? { ...c, dimensions: { width: last.width!, height: last.height! } } : c\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!node || (options.resizeSnapping ?? options.snapping) === false) return changes;\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tif (!resizeStart) return changes;\n\t\t\tconst posChange = changes.find((c) => c.id === id && c.type === 'position' && !!c.position);\n\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\tconst position = posChange?.position ?? node.position;\n\t\t\tconst proposed: Rect = {\n\t\t\t\tx: position.x + origin.x,\n\t\t\t\ty: position.y + origin.y,\n\t\t\t\twidth: dims.dimensions.width,\n\t\t\t\theight: dims.dimensions.height\n\t\t\t};\n\t\t\tconst handle = inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!handle) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn changes;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, handle, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst update = result.updates[0];\n\t\t\tlastResized = update ? result : null;\n\t\t\tif (!update) return changes;\n\t\t\tconst next = changes.map((c) => {\n\t\t\t\tif (c === dims) return { ...c, dimensions: { width: update.width!, height: update.height! } };\n\t\t\t\tif (c === posChange) return { ...c, position: update.position! };\n\t\t\t\treturn c;\n\t\t\t});\n\t\t\tif (!posChange && (update.position!.x !== node.position.x || update.position!.y !== node.position.y)) {\n\t\t\t\tnext.push({ id, type: 'position', position: update.position! } as unknown as TChange);\n\t\t\t}\n\t\t\treturn next;\n\t\t},\n\n\t\t/**\n\t\t * Clears guides and the cached snap targets. Call it from the flow's drag-stop\n\t\t * handler: flow libraries write the raw pointer position when a drag ends, so the\n\t\t * last snapped positions from `drag` are written back here.\n\t\t */\n\t\tendDrag() {\n\t\t\tconst snapped = lastSnapped;\n\t\t\tdragIndex = null;\n\t\t\tdragKey = '';\n\t\t\tlastSnapped = new Map();\n\t\t\tsetGuides([]);\n\t\t\tif (snapped.size === 0) return;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst drift = nodes.some((n) => {\n\t\t\t\tconst p = snapped.get(n.id);\n\t\t\t\treturn !!p && (n.position.x !== p.x || n.position.y !== p.y);\n\t\t\t});\n\t\t\tif (drift) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(nodes, [...snapped].map(([id, position]) => ({ id, position }))));\n\t\t\t}\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasArrangeController<TNode>\n>;\n","/**\n * Distance measuring between bounding boxes (Alt-hover red lines with value labels).\n *\n * Portions ported from figcraft-main/crates/engine/src/distance.rs (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** Lines shorter than this (flow units) are not drawn. */\nconst EPS = 1e-6;\n\n/**\n * A measuring line in flow coordinates. `measure` lines carry a `label` (the distance);\n * `extension` lines are dashed, unlabelled lines that carry a gap over to the measured box\n * when the gap line passes beside it.\n */\nexport interface MeasureGuide extends GuideLine {\n\tkind: 'measure' | 'extension';\n\t/** The distance as shown on the label (`measure` lines only). */\n\tlabel?: string;\n}\n\n/** A distance as shown on a label: absolute value, up to two decimals, no trailing zeros. */\nexport function measureLabel(distance: number): string {\n\tconst v = Math.round(Math.abs(distance) * 100) / 100;\n\treturn Object.is(v, -0) ? '0' : String(v);\n}\n\ninterface Box {\n\tx0: number;\n\ty0: number;\n\tx1: number;\n\ty1: number;\n}\n\nconst toBox = (r: Rect): Box => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });\nconst finiteBox = (b: Box) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);\n\nfunction line(kind: 'measure' | 'extension', x1: number, y1: number, x2: number, y2: number): MeasureGuide {\n\treturn { orientation: x1 === x2 ? 'vertical' : 'horizontal', kind, x1, y1, x2, y2 };\n}\n\n/** A labelled distance line, unless it has no length. */\nfunction measured(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tconst d = Math.hypot(x2 - x1, y2 - y1);\n\tif (d > EPS && Number.isFinite(d)) out.push({ ...line('measure', x1, y1, x2, y2), label: measureLabel(d) });\n}\n\n/** A dashed extension line (no label), unless it has no length. */\nfunction extension(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tif (Math.hypot(x2 - x1, y2 - y1) > EPS) out.push(line('extension', x1, y1, x2, y2));\n}\n\nfunction contains(outer: Box, inner: Box): boolean {\n\treturn outer.x0 <= inner.x0 + EPS && outer.y0 <= inner.y0 + EPS && outer.x1 >= inner.x1 - EPS && outer.y1 >= inner.y1 - EPS;\n}\n\n/** The middle of the overlap of two spans, or `null` when they do not overlap. */\nfunction overlapMid(a0: number, a1: number, b0: number, b1: number): number | null {\n\tconst lo = Math.max(a0, b0);\n\tconst hi = Math.min(a1, b1);\n\treturn lo <= hi ? (lo + hi) / 2 : null;\n}\n\n/** From `inner` to each edge of `outer`, along `inner`'s centre lines. */\nfunction inside(out: MeasureGuide[], inner: Box, outer: Box) {\n\tconst cx = (inner.x0 + inner.x1) / 2;\n\tconst cy = (inner.y0 + inner.y1) / 2;\n\tmeasured(out, outer.x0, cy, inner.x0, cy);\n\tmeasured(out, inner.x1, cy, outer.x1, cy);\n\tmeasured(out, cx, outer.y0, cx, inner.y0);\n\tmeasured(out, cx, inner.y1, cx, outer.y1);\n}\n\n/**\n * Distance lines from box `a` (the selection) to box `b` (the hovered shape):\n *\n * - **One inside the other**: from the inner box to each edge of the outer one, along the\n * inner box's centre lines (the same lines either way round).\n * - **Overlapping**: between the matching edges (left to left, right to right, ...).\n * - **Apart**: the horizontal and/or vertical gap, across the shared rows / columns, or from\n * the middle of `a` with a dashed `extension` line to `b` when the gap line passes beside it.\n * Touching boxes have no gap to draw.\n */\nexport function measureBetween(a: Rect, b: Rect): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tconst B = toBox(b);\n\tif (!finiteBox(A) || !finiteBox(B)) return out;\n\tif (contains(B, A)) {\n\t\tinside(out, A, B);\n\t\treturn out;\n\t}\n\tif (contains(A, B)) {\n\t\tinside(out, B, A);\n\t\treturn out;\n\t}\n\tconst ox = overlapMid(A.x0, A.x1, B.x0, B.x1);\n\tconst oy = overlapMid(A.y0, A.y1, B.y0, B.y1);\n\tconst overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;\n\tif (overlapping && ox !== null && oy !== null) {\n\t\tmeasured(out, A.x0, oy, B.x0, oy);\n\t\tmeasured(out, A.x1, oy, B.x1, oy);\n\t\tmeasured(out, ox, A.y0, ox, B.y0);\n\t\tmeasured(out, ox, A.y1, ox, B.y1);\n\t\treturn out;\n\t}\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;\n\tif (gapX) {\n\t\tconst [from, to] = gapX;\n\t\tconst y = oy ?? cy;\n\t\tmeasured(out, from, y, to, y);\n\t\tif (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);\n\t}\n\tconst gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;\n\tif (gapY) {\n\t\tconst [from, to] = gapY;\n\t\tconst x = ox ?? cx;\n\t\tmeasured(out, x, from, x, to);\n\t\tif (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);\n\t}\n\treturn out;\n}\n\n/**\n * Distance lines from box `a` to an infinite guide line (`vertical` at x = `at`, otherwise\n * horizontal at y = `at`): to the nearer side, or to both sides when the line crosses the box.\n */\nexport function measureToLine(a: Rect, orientation: 'vertical' | 'horizontal', at: number): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tif (!finiteBox(A) || !Number.isFinite(at)) return out;\n\tconst vertical = orientation === 'vertical';\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];\n\tconst seg = (from: number, to: number) =>\n\t\tvertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);\n\tif (at >= hi) seg(hi, at);\n\telse if (at <= lo) seg(at, lo);\n\telse {\n\t\tseg(lo, at);\n\t\tseg(at, hi);\n\t}\n\treturn out;\n}\n\nexport interface MeasureSelectionResult {\n\t/** Measuring lines in flow coordinates. */\n\tguides: MeasureGuide[];\n\t/** The box measured to (to outline), or `null` when nothing is measured. */\n\ttarget: Rect | null;\n\t/** The selection's bounding box, or `null` without a selection. */\n\tselection: Rect | null;\n}\n\nexport interface MeasureSelectionOptions extends NodeRectOptions {}\n\n/**\n * Alt-hover measuring on canvas nodes: from the bounding box of `selectedIds` (absolute,\n * union of several nodes) to the hovered node. Hovering the selection itself, or nothing\n * (`hoveredId` null), measures to the selection's parent (`parentId`) when it has one;\n * a top-level selection then measures nothing. Hidden nodes are ignored.\n */\nexport function measureSelection(\n\tnodes: CanvasNode[],\n\tselectedIds: Iterable<string>,\n\thoveredId: string | null | undefined,\n\toptions: MeasureSelectionOptions = {}\n): MeasureSelectionResult {\n\tconst none = (selection: Rect | null = null): MeasureSelectionResult => ({ guides: [], target: null, selection });\n\tconst lookup = createNodeLookup(nodes);\n\tconst selected = [...new Set(selectedIds)]\n\t\t.map((id) => lookup.get(id))\n\t\t.filter((n): n is CanvasNode => !!n && n.hidden !== true);\n\tif (selected.length === 0) return none();\n\tconst selection = boundsOf(selected.map((n) => getNodeRect(n, lookup, options)));\n\tif (!selection) return none();\n\tconst selectedSet = new Set(selected.map((n) => n.id));\n\n\tlet targetId = hoveredId ?? null;\n\tif (targetId === null || selectedSet.has(targetId)) {\n\t\tconst parentId = selected[0].parentId;\n\t\tconst sameParent = selected.every((n) => n.parentId === parentId);\n\t\ttargetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;\n\t}\n\tconst target = targetId !== null ? lookup.get(targetId) : undefined;\n\tif (!target || target.hidden === true) return none(selection);\n\tconst rect = getNodeRect(target, lookup, options);\n\treturn { guides: measureBetween(selection, rect), target: rect, selection };\n}\n\n/**\n * Whether the measuring modifier (Alt / Option) is held after a keyboard or pointer event.\n * `keyup` of Alt releases it; `keydown` of Alt or any event with `altKey` holds it.\n */\nexport function isMeasureModifierHeld(\n\tevent: { type?: string; key?: string; altKey?: boolean } | null | undefined\n): boolean {\n\tif (!event) return false;\n\tif (event.type === 'keyup' && event.key === 'Alt') return false;\n\treturn event.altKey === true || (event.type === 'keydown' && event.key === 'Alt');\n}\n","import { applyNodeUpdates, createNodeLookup, getNodeRect, getNodeSize, invertNodeUpdates, type CanvasNodeUpdate } from './arrange.js';\nimport type { ArrangeHistoryTarget, PositionChangeLike } from './arrangeController.js';\nimport {\n\tapplyConstraints,\n\tgetAutoLayoutInsertTarget,\n\tgetLayoutParent,\n\tisAutoLayoutFrame,\n\tlayoutAll,\n\tlayoutUpMany,\n\tmoveLayoutChild,\n\ttype AutoLayoutInsertTarget,\n\ttype AutoLayoutOptions\n} from './autoLayout.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\nexport interface CanvasAutoLayoutControllerOptions<TNode extends CanvasNode = CanvasNode> extends AutoLayoutOptions {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, controller actions (add / remove / resize / reorder / apply) are one undoable step each. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Turns automatic relayout off (explicit `applyAutoLayout` still works). Default `true`. */\n\tenabled?: boolean;\n\t/** Reorder flow children by dragging them inside their frame (default `true`). */\n\tdragReorder?: boolean;\n\t/** Called when the drop marker of a reorder drag changes (empty when none). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n}\n\n/**\n * Merges the updates of several layout passes (later ones win per field).\n */\nexport function mergeNodeUpdates(...lists: CanvasNodeUpdate[][]): CanvasNodeUpdate[] {\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const list of lists) for (const u of list) byId.set(u.id, { ...byId.get(u.id), ...u });\n\treturn [...byId.values()];\n}\n\n/**\n * Updates that re-run auto layout around `ids` after a change: each id's own layout (if a\n * frame) and its auto layout ancestors, plus the frames that lost removed children\n * (`parentIds`). Pure helper used by the controllers.\n */\nexport function relayoutUpdates(nodes: CanvasNode[], ids: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, ids, options);\n}\n\n/**\n * Framework-agnostic auto layout controller shared by the React hook and the Svelte rune.\n * Applies auto layout to frames (`data.layout`) after nodes inside them are added, removed,\n * resized or reordered, either through its own (undoable) actions or by watching the flow's\n * node changes (`noteChanges` + `flush`), and reorders flow children dragged within a frame.\n */\nexport function createCanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasAutoLayoutControllerOptions<TNode>\n) {\n\tconst layoutOptions = (): AutoLayoutOptions => ({ measure: options.measure, rectOptions: options.rectOptions });\n\tconst enabled = () => options.enabled !== false;\n\n\t// Changes noted from the flow, applied by `flush`.\n\tconst pendingIds = new Set<string>();\n\tconst pendingResizes = new Map<string, { width: number; height: number }>();\n\tconst resizeStarts = new Map<string, { width: number; height: number }>();\n\tlet pendingAll = false;\n\t// Reorder drags: start position of each dragged flow child, and drops to commit.\n\tconst dragStarts = new Map<string, XYPosition>();\n\tconst pendingDrops = new Set<string>();\n\tlet guides: GuideLine[] = [];\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/**\n\t * Runs a structural change plus the relayout it causes as one (optionally undoable) step.\n\t * `forward` / `backward` change the array; `ids` are relayout around after `forward`.\n\t */\n\tfunction run(\n\t\tname: string,\n\t\tforward: (nodes: TNode[]) => TNode[],\n\t\tbackward: (nodes: TNode[]) => TNode[],\n\t\tids: (after: TNode[]) => Iterable<string>,\n\t\textra: CanvasNodeUpdate[] = [],\n\t\trestore?: { id: string; position: XYPosition }\n\t) {\n\t\tconst before = options.getNodes();\n\t\tconst structural = forward(before);\n\t\tconst withExtra = applyNodeUpdates(structural, extra);\n\t\tconst layout = enabled() || name === 'Auto Layout' ? relayoutUpdates(withExtra, ids(withExtra), layoutOptions()) : [];\n\t\tconst updates = mergeNodeUpdates(extra, layout);\n\t\tif (structural === before && updates.length === 0) return updates;\n\t\tlet inverse = invertNodeUpdates(structural, updates);\n\t\tif (restore) {\n\t\t\t// Undo puts a dragged node back where the drag began (the flow wrote the drag outside history).\n\t\t\tinverse = mergeNodeUpdates(inverse, [{ id: restore.id, position: { ...restore.position } }]);\n\t\t}\n\t\tconst execute = () => options.setNodes(applyNodeUpdates(forward(options.getNodes()), updates));\n\t\tconst undo = () => options.setNodes(backward(applyNodeUpdates(options.getNodes(), inverse)));\n\t\tif (options.history) options.history.execute({ name, execute, undo });\n\t\telse execute();\n\t\treturn updates;\n\t}\n\n\tconst identity = (nodes: TNode[]) => nodes;\n\n\tconst controller = {\n\t\t/** Drop marker of the active reorder drag (flow coordinates); empty otherwise. */\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\n\t\t/**\n\t\t * Lays out frame `frameId` and its auto layout ancestors (default: every frame) as one\n\t\t * (optionally undoable) step. Returns the updates.\n\t\t */\n\t\tapplyAutoLayout(frameId?: string): CanvasNodeUpdate[] {\n\t\t\tif (frameId === undefined) {\n\t\t\t\tconst updates = layoutAll(options.getNodes(), layoutOptions());\n\t\t\t\treturn run('Auto Layout', identity, identity, () => [], updates);\n\t\t\t}\n\t\t\treturn run('Auto Layout', identity, identity, () => [frameId]);\n\t\t},\n\n\t\t/** Updates that relayout around `ids` on the current nodes, without applying them. */\n\t\tlayoutUpdates(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\treturn relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t},\n\n\t\t/**\n\t\t * Applies the relayout around `ids` (frames, or nodes inside frames) directly, not\n\t\t * recorded in history: auto layout is derived state, re-run after any change.\n\t\t */\n\t\trelayout(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst updates = relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn updates;\n\t\t},\n\n\t\t/**\n\t\t * Adds `node` (into the frame named by its `parentId`, at flow `index` when given) and\n\t\t * lays out its frame, as one step.\n\t\t */\n\t\taddNode(node: TNode, addOptions: { index?: number } = {}): CanvasNodeUpdate[] {\n\t\t\tconst forward = (nodes: TNode[]) => {\n\t\t\t\tif (nodes.some((n) => n.id === node.id)) return nodes;\n\t\t\t\tlet next = [...nodes, node];\n\t\t\t\tif (addOptions.index !== undefined && node.parentId) next = moveLayoutChild(next, node.id, addOptions.index);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\tconst backward = (nodes: TNode[]) => nodes.filter((n) => n.id !== node.id);\n\t\t\treturn run('Add Node', forward, backward, () => [node.id]);\n\t\t},\n\n\t\t/** Removes nodes (and their descendants) and lays out the frames they leave, as one step. */\n\t\tremoveNodes(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst remove = new Set(ids);\n\t\t\t// Descendants go too (xyflow cannot render orphans).\n\t\t\tlet grew = true;\n\t\t\twhile (grew) {\n\t\t\t\tgrew = false;\n\t\t\t\tfor (const n of before) {\n\t\t\t\t\tif (n.parentId && remove.has(n.parentId) && !remove.has(n.id)) {\n\t\t\t\t\t\tremove.add(n.id);\n\t\t\t\t\t\tgrew = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst removed = before.map((n, i) => [i, n] as const).filter(([, n]) => remove.has(n.id));\n\t\t\tif (removed.length === 0) return [];\n\t\t\tconst parents = [...new Set(removed.map(([, n]) => n.parentId).filter((p): p is string => !!p && !remove.has(p)))];\n\t\t\tconst forward = (nodes: TNode[]) => nodes.filter((n) => !remove.has(n.id));\n\t\t\tconst backward = (nodes: TNode[]) => {\n\t\t\t\tconst next = [...nodes];\n\t\t\t\tfor (const [i, n] of removed) next.splice(Math.min(i, next.length), 0, n);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\treturn run('Delete', forward, backward, () => parents);\n\t\t},\n\n\t\t/**\n\t\t * Resizes node `id` (its children follow their constraints) and lays out around it,\n\t\t * as one step.\n\t\t */\n\t\tresizeNode(id: string, size: { width?: number; height?: number }): CanvasNodeUpdate[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst node = nodes.find((n) => n.id === id);\n\t\t\tif (!node) return [];\n\t\t\tconst old = getNodeSize(node, options.rectOptions);\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (size.width !== undefined) update.width = size.width;\n\t\t\tif (size.height !== undefined) update.height = size.height;\n\t\t\tconst resized = applyNodeUpdates(nodes, [update]);\n\t\t\tconst constrained = mergeNodeUpdates([update], applyConstraints(resized, id, old, layoutOptions()));\n\t\t\treturn run('Resize', identity, identity, () => [id], constrained);\n\t\t},\n\n\t\t/** Moves flow child `childId` to flow index `index` in its frame and lays it out, as one step. */\n\t\tmoveChild(childId: string, index: number): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, childId, index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\tif (forward(before) === before) return [];\n\t\t\treturn run('Reorder', forward, backward, () => [childId]);\n\t\t},\n\n\t\t/** Where flow child `id` would drop if released at `point` (default: its centre). */\n\t\tgetInsertTarget(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst frame = getLayoutParent(nodes, id);\n\t\t\tif (!frame) return undefined;\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst rect = getNodeRect(lookup.get(id)!, lookup, options.rectOptions);\n\t\t\tconst at = point ?? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };\n\t\t\treturn getAutoLayoutInsertTarget(nodes, frame.id, at, { ...layoutOptions(), excludeId: id });\n\t\t},\n\n\t\t/** Records the start position of a reorder drag. Optional: `dragReorder` records it lazily. */\n\t\tbeginReorderDrag(id: string) {\n\t\t\tconst node = options.getNodes().find((n) => n.id === id);\n\t\t\tif (node && getLayoutParent(options.getNodes(), id)) dragStarts.set(id, { ...node.position });\n\t\t},\n\n\t\t/**\n\t\t * While flow child `id` is dragged (as currently positioned in `getNodes()`): updates the\n\t\t * drop marker. Returns the insert target.\n\t\t */\n\t\tdragReorder(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tif (options.dragReorder === false) return undefined;\n\t\t\tif (!dragStarts.has(id)) controller.beginReorderDrag(id);\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tsetGuides(target ? [target.guide] : []);\n\t\t\treturn target;\n\t\t},\n\n\t\t/**\n\t\t * Ends a reorder drag: moves `id` to the slot under `point` (default: its centre) and lays\n\t\t * the frame out, as one undoable step whose undo puts the node back where the drag began.\n\t\t */\n\t\tendReorderDrag(id: string, point?: XYPosition): CanvasNodeUpdate[] {\n\t\t\tsetGuides([]);\n\t\t\tconst start = dragStarts.get(id);\n\t\t\tdragStarts.delete(id);\n\t\t\tif (options.dragReorder === false || !getLayoutParent(options.getNodes(), id)) return [];\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tif (!target) return [];\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, id, target.index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\treturn run('Reorder', forward, backward, () => [id], [], start && { id, position: start });\n\t\t},\n\n\t\t/**\n\t\t * Notes a batch of React Flow / SvelteFlow style node changes (before or after they are\n\t\t * applied) so `flush` can relayout: dimension changes (measured sizes, resize ends),\n\t\t * added and removed nodes, and position changes of flow children (reorder drags).\n\t\t * Returns the changes unchanged.\n\t\t */\n\t\tnoteChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tfor (const c of changes) {\n\t\t\t\tconst id = c.id ?? c.item?.id;\n\t\t\t\tif (c.type === 'add' && c.item) {\n\t\t\t\t\tpendingIds.add(c.item.id);\n\t\t\t\t} else if (c.type === 'remove' && id) {\n\t\t\t\t\tconst parent = lookup.get(id)?.parentId;\n\t\t\t\t\tif (parent) pendingIds.add(parent);\n\t\t\t\t\telse pendingAll = true;\n\t\t\t\t} else if (c.type === 'replace' && id) {\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'dimensions' && id) {\n\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\tif (c.resizing === true) {\n\t\t\t\t\t\tif (node && !resizeStarts.has(id)) resizeStarts.set(id, getNodeSize(node, options.rectOptions));\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst start = resizeStarts.get(id);\n\t\t\t\t\tresizeStarts.delete(id);\n\t\t\t\t\tif (start) pendingResizes.set(id, start);\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'position' && id && options.dragReorder !== false) {\n\t\t\t\t\tif (!getLayoutParent(nodes, id)) continue;\n\t\t\t\t\tif (c.dragging === true) {\n\t\t\t\t\t\tif (!dragStarts.has(id)) {\n\t\t\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\t\t\tif (node) dragStarts.set(id, { ...node.position });\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c.dragging === false || dragStarts.has(id)) {\n\t\t\t\t\t\tpendingDrops.add(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changes;\n\t\t},\n\n\t\t/**\n\t\t * Notes nodes that were already removed (e.g. SvelteFlow's `ondelete`), so `flush`\n\t\t * relayouts the frames they left. The nodes must still carry their `parentId`.\n\t\t */\n\t\tnoteRemoved(removed: Iterable<{ id: string; parentId?: string }>) {\n\t\t\tconst gone = new Set<string>();\n\t\t\tconst list = [...removed];\n\t\t\tfor (const n of list) gone.add(n.id);\n\t\t\tfor (const n of list) if (n.parentId && !gone.has(n.parentId)) pendingIds.add(n.parentId);\n\t\t},\n\n\t\t/** Whether `noteChanges` left work for `flush`. */\n\t\tget hasPending() {\n\t\t\treturn pendingAll || pendingIds.size > 0 || pendingResizes.size > 0 || pendingDrops.size > 0 || dragStarts.size > 0;\n\t\t},\n\n\t\t/**\n\t\t * Applies the work noted by `noteChanges` to the current nodes: drop markers while\n\t\t * dragging, committed reorders on drop (undoable), and a relayout (not recorded) for\n\t\t * size, add and remove changes. Call it once the changes are applied.\n\t\t */\n\t\tflush(): CanvasNodeUpdate[] {\n\t\t\tlet applied: CanvasNodeUpdate[] = [];\n\t\t\tfor (const id of pendingDrops) applied = mergeNodeUpdates(applied, controller.endReorderDrag(id));\n\t\t\tpendingDrops.clear();\n\t\t\tfor (const id of dragStarts.keys()) controller.dragReorder(id);\n\t\t\tif (!enabled()) {\n\t\t\t\tpendingIds.clear();\n\t\t\t\tpendingResizes.clear();\n\t\t\t\tpendingAll = false;\n\t\t\t\treturn applied;\n\t\t\t}\n\t\t\tlet nodes = options.getNodes();\n\t\t\tlet updates: CanvasNodeUpdate[] = [];\n\t\t\tfor (const [id, previous] of pendingResizes) {\n\t\t\t\tif (!nodes.some((n) => n.id === id)) continue;\n\t\t\t\tconst constrained = applyConstraints(nodes, id, previous, layoutOptions());\n\t\t\t\tnodes = applyNodeUpdates(nodes, constrained);\n\t\t\t\tupdates = mergeNodeUpdates(updates, constrained);\n\t\t\t}\n\t\t\tconst layout = pendingAll\n\t\t\t\t? layoutAll(nodes, layoutOptions())\n\t\t\t\t: relayoutUpdates(nodes, [...pendingIds].filter((id) => nodes.some((n) => n.id === id)), layoutOptions());\n\t\t\tupdates = mergeNodeUpdates(updates, layout);\n\t\t\tpendingIds.clear();\n\t\t\tpendingResizes.clear();\n\t\t\tpendingAll = false;\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn mergeNodeUpdates(applied, updates);\n\t\t},\n\n\t\t/** Whether `id` is an auto layout frame. */\n\t\tisFrame: (id: string) => isAutoLayoutFrame(options.getNodes().find((n) => n.id === id))\n\t};\n\n\treturn controller;\n}\n\n/** Reorders `nodes` to follow `order` (ids); ids not in `order` keep their relative place at the end. */\nfunction restoreOrder<TNode extends CanvasNode>(nodes: TNode[], order: string[]): TNode[] {\n\tconst rank = new Map(order.map((id, i) => [id, i]));\n\tconst indexed = nodes.map((n, i) => ({ n, r: rank.get(n.id) ?? order.length + i }));\n\tindexed.sort((a, b) => a.r - b.r);\n\treturn indexed.every((e, i) => e.n === nodes[i]) ? nodes : indexed.map((e) => e.n);\n}\n\nexport type CanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasAutoLayoutController<TNode>\n>;\n","// Portions ported from figcraft/crates/doc/src/lib.rs and figcraft/crates/engine/src/cmd/variables.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { AUTO_LAYOUT_DATA_KEY, getAutoLayout, resolvePadding } from './autoLayout.js';\nimport { isShapeDescription } from './shapeGeometry.js';\nimport type { CanvasNode, CanvasNodeStyle } from './types.js';\nimport {\n\tTokenError,\n\tgetTokenCollection,\n\tgetTokenVariable,\n\tisTokenInScope,\n\tresolveToken,\n\ttype TokenLiteral,\n\ttype TokenModeContext,\n\ttype TokenResolution,\n\ttype TokenScope,\n\ttype TokenState,\n\ttype TokenType,\n\ttype TokenVariable\n} from '../tokens/index.js';\n\n/**\n * Design-token bindings on canvas nodes.\n *\n * - `node.data.tokenBindings` maps a property key (`fill`, `opacity`, `gap`, …) to a variable id.\n * - `node.data.tokenModes` maps a collection id to a mode id: an explicit mode for the node and\n * its descendants (via `parentId`); the nearest ancestor wins, then the root `modes` option,\n * then the collection's default mode.\n *\n * Bound properties are *materialised*: `resolveCanvasTokens` writes the resolved values into\n * the ordinary node fields (`data.shape.fill`, `style.backgroundColor`, `data.layout.gap`, …), so\n * renderers, auto layout and exports need no token awareness, and a node whose variable is\n * gone keeps its last value. Both keys live in `data`, so they survive copy / paste,\n * duplication and serialisation unchanged.\n */\n\n/** Data key holding a node's property → variable id bindings. */\nexport const TOKEN_BINDINGS_DATA_KEY = 'tokenBindings';\n/** Data key holding a node's explicit collection → mode choices. */\nexport const TOKEN_MODES_DATA_KEY = 'tokenModes';\n\n/** A node property a variable can drive. */\nexport interface CanvasTokenProperty {\n\tkey: string;\n\tlabel: string;\n\t/** The variable type it takes. */\n\ttype: TokenType;\n\t/** The scope pickers filter by (variables scoped elsewhere are not offered). */\n\tscope?: TokenScope | ((node: CanvasNode) => TokenScope | undefined);\n\tappliesTo(node: CanvasNode): boolean;\n\tread(node: CanvasNode): TokenLiteral | undefined;\n\t/** Returns the node with the value written, or the same node when nothing changes. */\n\twrite(node: CanvasNode, value: TokenLiteral): CanvasNode;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Style helpers (CSS string for SvelteFlow, object for React Flow)\n\nconst kebab = (prop: string) => prop.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/** Splits a style string at top-level `;` (not inside quotes or parentheses, e.g. data URLs). */\nfunction parseDeclarations(style: string): [string, string][] {\n\tconst parts: string[] = [];\n\tlet depth = 0;\n\tlet quote = '';\n\tlet start = 0;\n\tfor (let i = 0; i < style.length; i++) {\n\t\tconst ch = style[i];\n\t\tif (quote) {\n\t\t\tif (ch === '\\\\') i++;\n\t\t\telse if (ch === quote) quote = '';\n\t\t} else if (ch === '\"' || ch === \"'\") quote = ch;\n\t\telse if (ch === '(') depth++;\n\t\telse if (ch === ')') depth = Math.max(0, depth - 1);\n\t\telse if (ch === ';' && depth === 0) {\n\t\t\tparts.push(style.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tparts.push(style.slice(start));\n\tconst out: [string, string][] = [];\n\tfor (const part of parts) {\n\t\tconst colon = part.indexOf(':');\n\t\tif (colon < 0) continue;\n\t\tconst name = part.slice(0, colon).trim();\n\t\tif (name) out.push([name, part.slice(colon + 1).trim()]);\n\t}\n\treturn out;\n}\n\n/** A style property (camelCase name) from a style string or object. */\nexport function readNodeStyleProperty(style: CanvasNodeStyle | undefined | null, prop: string): string | number | undefined {\n\tif (!style) return undefined;\n\tif (typeof style === 'object') return style[prop];\n\tconst name = kebab(prop);\n\tlet found: string | undefined;\n\tfor (const [key, value] of parseDeclarations(style)) if (key.toLowerCase() === name) found = value;\n\treturn found;\n}\n\n/**\n * Returns `style` with one property set (or removed with `undefined`), keeping its\n * representation. Numbers become `px` in style strings unless `unitless`.\n */\nexport function writeNodeStyleProperty(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tprop: string,\n\tvalue: string | number | undefined,\n\tunitless = false\n): CanvasNodeStyle | undefined {\n\tif (typeof style === 'string') {\n\t\tconst name = kebab(prop);\n\t\tconst text = value === undefined ? undefined : typeof value === 'number' && !unitless ? `${value}px` : String(value);\n\t\t// Replace in place (keeping declaration order), drop duplicates, or append.\n\t\tconst decls: [string, string][] = [];\n\t\tlet placed = text === undefined;\n\t\tfor (const [key, current] of parseDeclarations(style)) {\n\t\t\tif (key.toLowerCase() !== name) decls.push([key, current]);\n\t\t\telse if (!placed) {\n\t\t\t\tdecls.push([name, text!]);\n\t\t\t\tplaced = true;\n\t\t\t}\n\t\t}\n\t\tif (!placed) decls.push([name, text!]);\n\t\treturn decls.map(([k, v]) => `${k}: ${v}`).join('; ');\n\t}\n\tconst next: Record<string, any> = { ...(style ?? {}) };\n\tif (value === undefined) delete next[prop];\n\telse next[prop] = value;\n\treturn next;\n}\n\nfunction px(value: unknown): number | undefined {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n\tif (typeof value === 'string') {\n\t\tconst n = parseFloat(value);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn undefined;\n}\n\nfunction withStyle(node: CanvasNode, prop: string, value: string | number, unitless = false): CanvasNode {\n\tconst current = readNodeStyleProperty(node.style, prop);\n\tconst written = typeof node.style === 'string' && typeof value === 'number' && !unitless ? `${value}px` : value;\n\tif (typeof node.style === 'string' ? current === String(written) : current === value) return node;\n\treturn { ...node, style: writeNodeStyleProperty(node.style, prop, value, unitless) };\n}\n\nfunction shapeOf(node: CanvasNode): Record<string, any> | null {\n\tconst shape = node.data?.shape;\n\treturn isShapeDescription(shape) ? (shape as Record<string, any>) : null;\n}\n\nfunction withShape(node: CanvasNode, key: string, value: unknown): CanvasNode {\n\tconst shape = shapeOf(node)!;\n\tif (shape[key] === value) return node;\n\treturn { ...node, data: { ...node.data, shape: { ...shape, [key]: value } } };\n}\n\nconst nonNegative = (v: TokenLiteral) => Math.max(0, v as number);\n\nfunction styleOrShape(key: 'fill' | 'stroke' | 'strokeWidth', styleProp: (node: CanvasNode) => string, convert: (v: TokenLiteral) => TokenLiteral = (v) => v) {\n\treturn {\n\t\tread(node: CanvasNode): TokenLiteral | undefined {\n\t\t\tconst shape = shapeOf(node);\n\t\t\tif (shape) return shape[key];\n\t\t\tconst value = readNodeStyleProperty(node.style, styleProp(node));\n\t\t\treturn key === 'strokeWidth' ? px(value) : (value as TokenLiteral | undefined);\n\t\t},\n\t\twrite(node: CanvasNode, value: TokenLiteral): CanvasNode {\n\t\t\tconst v = convert(value);\n\t\t\tif (shapeOf(node)) return withShape(node, key, v);\n\t\t\tlet next = withStyle(node, styleProp(node), v as string | number);\n\t\t\tif (key === 'strokeWidth' && readNodeStyleProperty(next.style, 'borderStyle') === undefined) {\n\t\t\t\tnext = { ...next, style: writeNodeStyleProperty(next.style, 'borderStyle', 'solid') };\n\t\t\t}\n\t\t\treturn next;\n\t\t}\n\t};\n}\n\nfunction backgroundProp(node: CanvasNode): string {\n\treturn readNodeStyleProperty(node.style, 'backgroundColor') === undefined && readNodeStyleProperty(node.style, 'background') !== undefined\n\t\t? 'background'\n\t\t: 'backgroundColor';\n}\n\nfunction layoutProperty(key: string, label: string, read: (l: Record<string, any>) => number | undefined, write: (l: Record<string, any>, v: number) => Record<string, any>): CanvasTokenProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype: 'number',\n\t\tscope: 'GAP',\n\t\tappliesTo: (node) => getAutoLayout(node) !== undefined,\n\t\tread: (node) => {\n\t\t\tconst layout = getAutoLayout(node);\n\t\t\treturn layout ? read(layout as Record<string, any>) : undefined;\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst layout = getAutoLayout(node) as Record<string, any> | undefined;\n\t\t\tif (!layout || read(layout) === value) return node;\n\t\t\treturn { ...node, data: { ...node.data, [AUTO_LAYOUT_DATA_KEY]: write(layout, value as number) } };\n\t\t}\n\t};\n}\n\ntype Side = 'top' | 'right' | 'bottom' | 'left';\nconst SIDES: Side[] = ['top', 'right', 'bottom', 'left'];\n\nfunction paddingProperty(key: string, label: string, sides: Side[]): CanvasTokenProperty {\n\treturn layoutProperty(\n\t\tkey,\n\t\tlabel,\n\t\t(layout) => {\n\t\t\tconst p = resolvePadding(layout.padding);\n\t\t\tconst values = sides.map((s) => p[SIDES.indexOf(s)]);\n\t\t\treturn values.every((v) => v === values[0]) ? values[0] : undefined;\n\t\t},\n\t\t(layout, value) => {\n\t\t\tconst p = resolvePadding(layout.padding);\n\t\t\tconst v = Math.max(0, value);\n\t\t\tconst padding = { top: p[0], right: p[1], bottom: p[2], left: p[3] };\n\t\t\tfor (const s of sides) padding[s] = v;\n\t\t\treturn { ...layout, padding };\n\t\t}\n\t);\n}\n\n/** The built-in bindable properties. */\nexport const CANVAS_TOKEN_PROPERTIES: readonly CanvasTokenProperty[] = [\n\t{\n\t\tkey: 'fill',\n\t\tlabel: 'Fill',\n\t\ttype: 'color',\n\t\tscope: (node) => (shapeOf(node) ? 'SHAPE_FILL' : node.type === 'text' ? 'TEXT_FILL' : 'FRAME_FILL'),\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('fill', backgroundProp)\n\t},\n\t{\n\t\tkey: 'stroke',\n\t\tlabel: 'Stroke',\n\t\ttype: 'color',\n\t\tscope: 'STROKE_COLOR',\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('stroke', () => 'borderColor')\n\t},\n\t{\n\t\tkey: 'strokeWidth',\n\t\tlabel: 'Stroke width',\n\t\ttype: 'number',\n\t\tscope: 'STROKE_FLOAT',\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('strokeWidth', () => 'borderWidth', nonNegative)\n\t},\n\t{\n\t\tkey: 'opacity',\n\t\tlabel: 'Opacity',\n\t\ttype: 'number',\n\t\tscope: 'OPACITY',\n\t\tappliesTo: () => true,\n\t\t// Opacity variables are percentages (figcraft / Figma): 40 → 0.4, clamped to 0–100.\n\t\tread: (node) => {\n\t\t\tconst v = px(readNodeStyleProperty(node.style, 'opacity'));\n\t\t\treturn v === undefined ? undefined : Math.round(v * 10000) / 100;\n\t\t},\n\t\twrite: (node, value) => withStyle(node, 'opacity', Math.min(1, Math.max(0, (value as number) / 100)), true)\n\t},\n\t{\n\t\tkey: 'cornerRadius',\n\t\tlabel: 'Corner radius',\n\t\ttype: 'number',\n\t\tscope: 'CORNER_RADIUS',\n\t\tappliesTo: (node) => {\n\t\t\tconst shape = shapeOf(node);\n\t\t\treturn !shape || shape.kind === 'rect';\n\t\t},\n\t\tread: (node) => {\n\t\t\tconst shape = shapeOf(node);\n\t\t\tif (shape) return typeof shape.cornerRadius === 'number' ? shape.cornerRadius : undefined;\n\t\t\treturn px(readNodeStyleProperty(node.style, 'borderRadius'));\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst v = Math.max(0, value as number);\n\t\t\treturn shapeOf(node) ? withShape(node, 'cornerRadius', v) : withStyle(node, 'borderRadius', v);\n\t\t}\n\t},\n\tlayoutProperty('gap', 'Gap', (l) => (typeof l.gap === 'number' ? l.gap : 0), (l, v) => ({ ...l, gap: v })),\n\tlayoutProperty('rowGap', 'Row gap', (l) => (typeof l.rowGap === 'number' ? l.rowGap : undefined), (l, v) => ({ ...l, rowGap: v })),\n\tpaddingProperty('padding', 'Padding', ['top', 'right', 'bottom', 'left']),\n\tpaddingProperty('paddingHorizontal', 'Horizontal padding', ['left', 'right']),\n\tpaddingProperty('paddingVertical', 'Vertical padding', ['top', 'bottom']),\n\tpaddingProperty('paddingTop', 'Top padding', ['top']),\n\tpaddingProperty('paddingRight', 'Right padding', ['right']),\n\tpaddingProperty('paddingBottom', 'Bottom padding', ['bottom']),\n\tpaddingProperty('paddingLeft', 'Left padding', ['left']),\n\t{\n\t\tkey: 'textColor',\n\t\tlabel: 'Text colour',\n\t\ttype: 'color',\n\t\tscope: 'TEXT_FILL',\n\t\tappliesTo: (node) => !shapeOf(node),\n\t\tread: (node) => readNodeStyleProperty(node.style, 'color') as string | undefined,\n\t\twrite: (node, value) => withStyle(node, 'color', value as string)\n\t},\n\t{\n\t\tkey: 'fontSize',\n\t\tlabel: 'Font size',\n\t\ttype: 'number',\n\t\tscope: 'FONT_SIZE',\n\t\tappliesTo: (node) => !shapeOf(node),\n\t\tread: (node) => px(readNodeStyleProperty(node.style, 'fontSize')),\n\t\twrite: (node, value) => withStyle(node, 'fontSize', Math.max(0, value as number))\n\t},\n\t{\n\t\tkey: 'visible',\n\t\tlabel: 'Visible',\n\t\ttype: 'boolean',\n\t\tappliesTo: () => true,\n\t\tread: (node) => !node.hidden,\n\t\twrite: (node, value) => (!node.hidden === value ? node : { ...node, hidden: !value })\n\t}\n];\n\nexport interface CanvasTokenOptions {\n\t/** Modes for nodes without an explicit mode up their tree (e.g. the page's). */\n\tmodes?: TokenModeContext;\n\t/** Bindable properties (default {@link CANVAS_TOKEN_PROPERTIES}); add your own to extend. */\n\tproperties?: readonly CanvasTokenProperty[];\n}\n\nconst EMPTY: Readonly<Record<string, string>> = Object.freeze({});\n\nfunction stringRecord(value: unknown): Readonly<Record<string, string>> {\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) return EMPTY;\n\tconst out: Record<string, string> = {};\n\tfor (const [k, v] of Object.entries(value)) if (typeof v === 'string') out[k] = v;\n\treturn out;\n}\n\n/** The node's property → variable id bindings. */\nexport function getNodeTokenBindings(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(node?.data?.[TOKEN_BINDINGS_DATA_KEY]);\n}\n\n/** The node's own explicit modes (collection id → mode id). */\nexport function getNodeTokenModes(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(node?.data?.[TOKEN_MODES_DATA_KEY]);\n}\n\nexport function getCanvasTokenProperty(key: string, options: CanvasTokenOptions = {}): CanvasTokenProperty | undefined {\n\treturn (options.properties ?? CANVAS_TOKEN_PROPERTIES).find((p) => p.key === key);\n}\n\n/** Properties a variable can be bound to on this node. */\nexport function getBindableTokenProperties(node: CanvasNode, options: CanvasTokenOptions = {}): CanvasTokenProperty[] {\n\treturn (options.properties ?? CANVAS_TOKEN_PROPERTIES).filter((p) => p.appliesTo(node));\n}\n\n/** The scope of a property on a node. */\nexport function getTokenPropertyScope(property: CanvasTokenProperty, node: CanvasNode): TokenScope | undefined {\n\treturn typeof property.scope === 'function' ? property.scope(node) : property.scope;\n}\n\n/** Variables a picker offers for `property` on `node`: the property's type, in scope. */\nexport function getTokensForProperty(state: TokenState, property: CanvasTokenProperty, node: CanvasNode): TokenVariable[] {\n\tconst scope = getTokenPropertyScope(property, node);\n\treturn state.variables.filter((v) => v.type === property.type && isTokenInScope(v, scope));\n}\n\nfunction lookupOf(nodes: readonly CanvasNode[]): Map<string, CanvasNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\nfunction validModes(state: TokenState | undefined, modes: Readonly<Record<string, string>>, into: Record<string, string>): void {\n\tfor (const [collectionId, modeId] of Object.entries(modes)) {\n\t\tif (state) {\n\t\t\tconst collection = getTokenCollection(state, collectionId);\n\t\t\t// An unknown collection or mode counts as \"Auto\" (figcraft resets them to Auto).\n\t\t\tif (!collection || !collection.modes.some((m) => m.id === modeId)) continue;\n\t\t}\n\t\tinto[collectionId] = modeId;\n\t}\n}\n\n/**\n * The modes `node` is in: the root `modes`, overridden by each ancestor's explicit modes from\n * the top down, then the node's own. Modes `state` doesn't know are ignored.\n */\nexport function getEffectiveTokenModes(\n\tnode: CanvasNode,\n\tnodes: readonly CanvasNode[] | ReadonlyMap<string, CanvasNode>,\n\tstate?: TokenState,\n\toptions: Pick<CanvasTokenOptions, 'modes'> = {}\n): Record<string, string> {\n\tconst lookup = nodes instanceof Map ? nodes : lookupOf(nodes as readonly CanvasNode[]);\n\tconst chain: CanvasNode[] = [];\n\tconst seen = new Set<string>();\n\tfor (let at: CanvasNode | undefined = node; at && !seen.has(at.id); at = at.parentId ? lookup.get(at.parentId) : undefined) {\n\t\tseen.add(at.id);\n\t\tchain.push(at);\n\t}\n\tconst out: Record<string, string> = {};\n\tif (options.modes) validModes(state, options.modes, out);\n\tfor (let i = chain.length - 1; i >= 0; i--) validModes(state, getNodeTokenModes(chain[i]), out);\n\treturn out;\n}\n\nfunction modesResolver(nodes: readonly CanvasNode[], state: TokenState, options: CanvasTokenOptions) {\n\tconst lookup = lookupOf(nodes);\n\tconst memo = new Map<string, Record<string, string>>();\n\tconst root: Record<string, string> = {};\n\tif (options.modes) validModes(state, options.modes, root);\n\tconst visiting = new Set<string>();\n\tconst modesOf = (node: CanvasNode): Record<string, string> => {\n\t\tconst cached = memo.get(node.id);\n\t\tif (cached) return cached;\n\t\tlet base = root;\n\t\tif (node.parentId && !visiting.has(node.id)) {\n\t\t\tconst parent = lookup.get(node.parentId);\n\t\t\tif (parent) {\n\t\t\t\tvisiting.add(node.id);\n\t\t\t\tbase = modesOf(parent);\n\t\t\t\tvisiting.delete(node.id);\n\t\t\t}\n\t\t}\n\t\tconst own = getNodeTokenModes(node);\n\t\tlet result = base;\n\t\tif (Object.keys(own).length > 0) {\n\t\t\tresult = { ...base };\n\t\t\tvalidModes(state, own, result);\n\t\t}\n\t\tmemo.set(node.id, result);\n\t\treturn result;\n\t};\n\treturn { lookup, modesOf };\n}\n\nfunction applyBindings(node: CanvasNode, state: TokenState, modes: TokenModeContext, options: CanvasTokenOptions, only?: ReadonlySet<string>): CanvasNode {\n\tconst bindings = getNodeTokenBindings(node);\n\tlet next = node;\n\tfor (const [key, variableId] of Object.entries(bindings)) {\n\t\tif (only && !only.has(variableId)) continue;\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property || !property.appliesTo(next)) continue;\n\t\tconst resolved = resolveToken(state, variableId, modes);\n\t\tif (!resolved.ok || resolved.type !== property.type) continue;\n\t\tnext = property.write(next, resolved.value);\n\t}\n\treturn next;\n}\n\n/**\n * Writes every bound property's resolved value into its node. Returns the same array when\n * nothing changed (unchanged nodes keep their identity), so it is safe to run on every change.\n * Bindings that don't resolve (deleted variable, cycle) keep the node's current value.\n */\nexport function resolveCanvasTokens<TNode extends CanvasNode>(nodes: readonly TNode[], state: TokenState, options: CanvasTokenOptions = {}): TNode[] {\n\tconst { modesOf } = modesResolver(nodes, state, options);\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tif (Object.keys(getNodeTokenBindings(node)).length === 0) return node;\n\t\tconst next = applyBindings(node, state, modesOf(node), options);\n\t\tif (next !== node) changed = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\nexport interface CanvasTokenBindingInfo {\n\tproperty: CanvasTokenProperty;\n\tvariableId: string;\n\tvariable: TokenVariable | undefined;\n\tresolution: TokenResolution;\n}\n\n/** Each binding of a node with its variable and resolution (for inspectors and diagnostics). */\nexport function inspectNodeTokens(\n\tnode: CanvasNode,\n\tnodes: readonly CanvasNode[],\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): CanvasTokenBindingInfo[] {\n\tconst modes = getEffectiveTokenModes(node, nodes, state, options);\n\tconst out: CanvasTokenBindingInfo[] = [];\n\tfor (const [key, variableId] of Object.entries(getNodeTokenBindings(node))) {\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property) continue;\n\t\tout.push({ property, variableId, variable: getTokenVariable(state, variableId), resolution: resolveToken(state, variableId, modes) });\n\t}\n\treturn out;\n}\n\nfunction setDataRecord(node: CanvasNode, key: string, record: Record<string, string>): CanvasNode {\n\tconst data = { ...node.data };\n\tif (Object.keys(record).length === 0) delete data[key];\n\telse data[key] = record;\n\treturn { ...node, data };\n}\n\n/**\n * Binds `property` of each node in `ids` to `variableId` (or unbinds with `null`, keeping the\n * current value) and resolves. Throws `type-mismatch` when the variable's type doesn't fit the\n * property, `not-found` for unknown variables or properties. Nodes the property doesn't apply\n * to are left alone.\n */\nexport function bindCanvasToken<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tpropertyKey: string,\n\tvariableId: string | null,\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tconst property = getCanvasTokenProperty(propertyKey, options);\n\tif (!property) throw new TokenError('not-found', `No bindable property \\`${propertyKey}\\``);\n\tif (variableId !== null) {\n\t\tconst variable = getTokenVariable(state, variableId);\n\t\tif (!variable) throw new TokenError('not-found', `No variable \\`${variableId}\\``);\n\t\tif (variable.type !== property.type) {\n\t\t\tthrow new TokenError('type-mismatch', `${property.label} takes a ${property.type} variable; \\`${variable.name}\\` is a ${variable.type}`);\n\t\t}\n\t}\n\tconst targets = new Set(ids);\n\tlet changed = false;\n\tconst bound = nodes.map((node) => {\n\t\tif (!targets.has(node.id) || !property.appliesTo(node)) return node;\n\t\tconst bindings = { ...getNodeTokenBindings(node) };\n\t\tif (variableId === null) {\n\t\t\tif (!(propertyKey in bindings)) return node;\n\t\t\tdelete bindings[propertyKey];\n\t\t} else {\n\t\t\tif (bindings[propertyKey] === variableId) return node;\n\t\t\tbindings[propertyKey] = variableId;\n\t\t}\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_BINDINGS_DATA_KEY, bindings) as TNode;\n\t});\n\treturn changed ? resolveCanvasTokens(bound, state, options) : (nodes as TNode[]);\n}\n\n/**\n * Sets (or with `null` clears, back to \"Auto\") the explicit mode of `collectionId` on each node\n * in `ids`, then resolves, so descendants follow.\n */\nexport function setCanvasTokenMode<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tcollectionId: string,\n\tmodeId: string | null,\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tif (modeId !== null) {\n\t\tconst collection = getTokenCollection(state, collectionId);\n\t\tif (!collection) throw new TokenError('not-found', `No collection \\`${collectionId}\\``);\n\t\tif (!collection.modes.some((m) => m.id === modeId)) throw new TokenError('not-found', `No mode \\`${modeId}\\` in \\`${collection.name}\\``);\n\t}\n\tconst targets = new Set(ids);\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst modes = { ...getNodeTokenModes(node) };\n\t\tif (modeId === null) {\n\t\t\tif (!(collectionId in modes)) return node;\n\t\t\tdelete modes[collectionId];\n\t\t} else {\n\t\t\tif (modes[collectionId] === modeId) return node;\n\t\t\tmodes[collectionId] = modeId;\n\t\t}\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_MODES_DATA_KEY, modes) as TNode;\n\t});\n\treturn changed ? resolveCanvasTokens(next, state, options) : (nodes as TNode[]);\n}\n\n/**\n * Detaches bindings to `variableIds` (default: every binding whose variable is missing from\n * `state`): each property keeps its value resolved in `state` and the binding is removed. Pass\n * the state from *before* a delete (`TokenChangeEvent.previous`) so deleted variables still\n * resolve.\n */\nexport function detachTokenBindings<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tstate: TokenState,\n\tvariableIds?: Iterable<string>,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tconst only = variableIds ? new Set(variableIds) : null;\n\tconst isDetached = (id: string) => (only ? only.has(id) : !getTokenVariable(state, id));\n\tconst { modesOf } = modesResolver(nodes, state, options);\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst bindings = getNodeTokenBindings(node);\n\t\tconst gone = Object.entries(bindings).filter(([, id]) => isDetached(id));\n\t\tif (gone.length === 0) return node;\n\t\tlet next = applyBindings(node, state, modesOf(node), options, new Set(gone.map(([, id]) => id)));\n\t\tconst kept = { ...bindings };\n\t\tfor (const [key] of gone) delete kept[key];\n\t\tnext = setDataRecord(next, TOKEN_BINDINGS_DATA_KEY, kept);\n\t\tchanged = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\n/**\n * Removes explicit modes `state` no longer knows (deleted collections or modes), like figcraft\n * resetting such layers to \"Auto\". Resolution already ignores them; this tidies the data.\n */\nexport function pruneCanvasTokenModes<TNode extends CanvasNode>(nodes: readonly TNode[], state: TokenState): TNode[] {\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst modes = getNodeTokenModes(node);\n\t\tconst valid: Record<string, string> = {};\n\t\tvalidModes(state, modes, valid);\n\t\tif (Object.keys(valid).length === Object.keys(modes).length) return node;\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_MODES_DATA_KEY, valid) as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n"]}