@node-projects/web-component-designer 0.1.353 → 0.2.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 (188) hide show
  1. package/dist/elements/controls/NumericStyleInput.d.ts +114 -2
  2. package/dist/elements/controls/NumericStyleInput.js +757 -30
  3. package/dist/elements/controls/NumericStyleInput.js.map +1 -1
  4. package/dist/elements/controls/NumericStyleInputValueHelpers.d.ts +14 -0
  5. package/dist/elements/controls/NumericStyleInputValueHelpers.js +31 -0
  6. package/dist/elements/controls/NumericStyleInputValueHelpers.js.map +1 -0
  7. package/dist/elements/documentContainer.js +1 -1
  8. package/dist/elements/documentContainer.js.map +1 -1
  9. package/dist/elements/helper/GridHelper.d.ts +41 -20
  10. package/dist/elements/helper/GridHelper.js +118 -19
  11. package/dist/elements/helper/GridHelper.js.map +1 -1
  12. package/dist/elements/helper/getBoxQuads.d.ts +2 -1
  13. package/dist/elements/helper/getBoxQuads.js +419 -154
  14. package/dist/elements/helper/getBoxQuads.js.map +1 -1
  15. package/dist/elements/item/DesignItem.js +46 -7
  16. package/dist/elements/item/DesignItem.js.map +1 -1
  17. package/dist/elements/services/DefaultServiceBootstrap.js +4 -7
  18. package/dist/elements/services/DefaultServiceBootstrap.js.map +1 -1
  19. package/dist/elements/services/InstanceServiceContainer.d.ts +20 -3
  20. package/dist/elements/services/InstanceServiceContainer.js +5 -3
  21. package/dist/elements/services/InstanceServiceContainer.js.map +1 -1
  22. package/dist/elements/services/ServiceContainer.d.ts +0 -2
  23. package/dist/elements/services/ServiceContainer.js.map +1 -1
  24. package/dist/elements/services/bindingsService/SpecialTagsBindingService.js +1 -1
  25. package/dist/elements/services/collaborationService/CollaborationNodeIndex.js +1 -1
  26. package/dist/elements/services/contentService/IContentChanged.js +10 -0
  27. package/dist/elements/services/contentService/IContentChanged.js.map +1 -1
  28. package/dist/elements/services/placementService/GridPlacementService.js +54 -70
  29. package/dist/elements/services/placementService/GridPlacementService.js.map +1 -1
  30. package/dist/elements/services/propertiesService/DefaultEditorTypesService.js +4 -6
  31. package/dist/elements/services/propertiesService/DefaultEditorTypesService.js.map +1 -1
  32. package/dist/elements/services/propertiesService/IProperty.d.ts +6 -1
  33. package/dist/elements/services/propertiesService/PropertyMutationHandling.d.ts +6 -0
  34. package/dist/elements/services/propertiesService/PropertyMutationHandling.js +89 -0
  35. package/dist/elements/services/propertiesService/PropertyMutationHandling.js.map +1 -0
  36. package/dist/elements/services/propertiesService/propertyEditors/BasePropertyEditor.d.ts +2 -0
  37. package/dist/elements/services/propertiesService/propertyEditors/BasePropertyEditor.js +12 -0
  38. package/dist/elements/services/propertiesService/propertyEditors/BasePropertyEditor.js.map +1 -1
  39. package/dist/elements/services/propertiesService/propertyEditors/CssLengthPropertyEditor.d.ts +1 -0
  40. package/dist/elements/services/propertiesService/propertyEditors/CssLengthPropertyEditor.js +2 -0
  41. package/dist/elements/services/propertiesService/propertyEditors/CssLengthPropertyEditor.js.map +1 -0
  42. package/dist/elements/services/propertiesService/propertyEditors/CssLengthPropertyEditorConfig.d.ts +3 -0
  43. package/dist/elements/services/propertiesService/propertyEditors/CssLengthPropertyEditorConfig.js +4 -0
  44. package/dist/elements/services/propertiesService/propertyEditors/CssLengthPropertyEditorConfig.js.map +1 -0
  45. package/dist/elements/services/propertiesService/propertyEditors/CssNumericPropertyEditor.d.ts +10 -0
  46. package/dist/elements/services/propertiesService/propertyEditors/CssNumericPropertyEditor.js +34 -0
  47. package/dist/elements/services/propertiesService/propertyEditors/CssNumericPropertyEditor.js.map +1 -0
  48. package/dist/elements/services/propertiesService/propertyEditors/CssNumericPropertyEditorConfig.d.ts +30 -0
  49. package/dist/elements/services/propertiesService/propertyEditors/CssNumericPropertyEditorConfig.js +475 -0
  50. package/dist/elements/services/propertiesService/propertyEditors/CssNumericPropertyEditorConfig.js.map +1 -0
  51. package/dist/elements/services/propertiesService/services/AbstractCssPropertiesService.d.ts +1 -0
  52. package/dist/elements/services/propertiesService/services/AbstractCssPropertiesService.js +4 -0
  53. package/dist/elements/services/propertiesService/services/AbstractCssPropertiesService.js.map +1 -1
  54. package/dist/elements/services/propertiesService/services/CssCurrentPropertiesService.js +6 -4
  55. package/dist/elements/services/propertiesService/services/CssCurrentPropertiesService.js.map +1 -1
  56. package/dist/elements/services/propertiesService/services/CssCustomPropertiesService.js +3 -3
  57. package/dist/elements/services/propertiesService/services/CssCustomPropertiesService.js.map +1 -1
  58. package/dist/elements/services/propertiesService/services/CssPropertiesService.js +3 -3
  59. package/dist/elements/services/propertiesService/services/CssPropertiesService.js.map +1 -1
  60. package/dist/elements/services/propertiesService/services/IJsonPropertyDefinition.d.ts +2 -0
  61. package/dist/elements/services/selectionService/SelectionService.js +1 -1
  62. package/dist/elements/services/undoService/ChangeGroup.d.ts +8 -4
  63. package/dist/elements/services/undoService/ChangeGroup.js +25 -4
  64. package/dist/elements/services/undoService/ChangeGroup.js.map +1 -1
  65. package/dist/elements/services/undoService/ITransactionItem.d.ts +3 -2
  66. package/dist/elements/services/undoService/ITransactionItem.js.map +1 -1
  67. package/dist/elements/services/undoService/UndoService.js +15 -8
  68. package/dist/elements/services/undoService/UndoService.js.map +1 -1
  69. package/dist/elements/services/undoService/transactionItems/AttributeAndPropertyChangeAction.d.ts +3 -2
  70. package/dist/elements/services/undoService/transactionItems/AttributeAndPropertyChangeAction.js +12 -8
  71. package/dist/elements/services/undoService/transactionItems/AttributeAndPropertyChangeAction.js.map +1 -1
  72. package/dist/elements/services/undoService/transactionItems/AttributeChangeAction.d.ts +3 -2
  73. package/dist/elements/services/undoService/transactionItems/AttributeChangeAction.js +12 -8
  74. package/dist/elements/services/undoService/transactionItems/AttributeChangeAction.js.map +1 -1
  75. package/dist/elements/services/undoService/transactionItems/CssStyleChangeAction.d.ts +3 -2
  76. package/dist/elements/services/undoService/transactionItems/CssStyleChangeAction.js +4 -2
  77. package/dist/elements/services/undoService/transactionItems/CssStyleChangeAction.js.map +1 -1
  78. package/dist/elements/services/undoService/transactionItems/DeleteAction.d.ts +3 -2
  79. package/dist/elements/services/undoService/transactionItems/DeleteAction.js +2 -2
  80. package/dist/elements/services/undoService/transactionItems/DeleteAction.js.map +1 -1
  81. package/dist/elements/services/undoService/transactionItems/InsertAction.d.ts +3 -2
  82. package/dist/elements/services/undoService/transactionItems/InsertAction.js +2 -2
  83. package/dist/elements/services/undoService/transactionItems/InsertAction.js.map +1 -1
  84. package/dist/elements/services/undoService/transactionItems/InsertChildAction.d.ts +3 -2
  85. package/dist/elements/services/undoService/transactionItems/InsertChildAction.js +3 -3
  86. package/dist/elements/services/undoService/transactionItems/InsertChildAction.js.map +1 -1
  87. package/dist/elements/services/undoService/transactionItems/PropertyChangeAction.d.ts +3 -2
  88. package/dist/elements/services/undoService/transactionItems/PropertyChangeAction.js +2 -0
  89. package/dist/elements/services/undoService/transactionItems/PropertyChangeAction.js.map +1 -1
  90. package/dist/elements/services/undoService/transactionItems/SelectionChangedAction.d.ts +3 -2
  91. package/dist/elements/services/undoService/transactionItems/SelectionChangedAction.js +2 -0
  92. package/dist/elements/services/undoService/transactionItems/SelectionChangedAction.js.map +1 -1
  93. package/dist/elements/services/undoService/transactionItems/SetDesignItemsAction.d.ts +3 -2
  94. package/dist/elements/services/undoService/transactionItems/SetDesignItemsAction.js +2 -0
  95. package/dist/elements/services/undoService/transactionItems/SetDesignItemsAction.js.map +1 -1
  96. package/dist/elements/services/undoService/transactionItems/StylesheetChangedAction.d.ts +3 -2
  97. package/dist/elements/services/undoService/transactionItems/StylesheetChangedAction.js +2 -0
  98. package/dist/elements/services/undoService/transactionItems/StylesheetChangedAction.js.map +1 -1
  99. package/dist/elements/services/undoService/transactionItems/TextContentChangeAction.d.ts +3 -2
  100. package/dist/elements/services/undoService/transactionItems/TextContentChangeAction.js +2 -0
  101. package/dist/elements/services/undoService/transactionItems/TextContentChangeAction.js.map +1 -1
  102. package/dist/elements/widgets/designerView/DomConverter.d.ts +1 -1
  103. package/dist/elements/widgets/designerView/IDesignerCanvas.d.ts +0 -2
  104. package/dist/elements/widgets/designerView/designerCanvas.d.ts +0 -1
  105. package/dist/elements/widgets/designerView/designerCanvas.js +14 -7
  106. package/dist/elements/widgets/designerView/designerCanvas.js.map +1 -1
  107. package/dist/elements/widgets/designerView/extensions/ExtensionManager.js +18 -16
  108. package/dist/elements/widgets/designerView/extensions/ExtensionManager.js.map +1 -1
  109. package/dist/elements/widgets/designerView/extensions/ResizeExtension.d.ts +19 -10
  110. package/dist/elements/widgets/designerView/extensions/ResizeExtension.js +179 -52
  111. package/dist/elements/widgets/designerView/extensions/ResizeExtension.js.map +1 -1
  112. package/dist/elements/widgets/designerView/extensions/buttons/FlexboxExtensionDesignViewConfigButtons.js +1 -1
  113. package/dist/elements/widgets/designerView/extensions/buttons/FlexboxExtensionDesignViewConfigButtons.js.map +1 -1
  114. package/dist/elements/widgets/designerView/extensions/flex/FlexboxExtension.d.ts +11 -0
  115. package/dist/elements/widgets/designerView/extensions/flex/FlexboxExtension.js +20 -0
  116. package/dist/elements/widgets/designerView/extensions/flex/FlexboxExtension.js.map +1 -0
  117. package/dist/elements/widgets/designerView/extensions/flex/FlexboxExtensionProvider.d.ts +11 -0
  118. package/dist/elements/widgets/designerView/extensions/flex/FlexboxExtensionProvider.js +18 -0
  119. package/dist/elements/widgets/designerView/extensions/flex/FlexboxExtensionProvider.js.map +1 -0
  120. package/dist/elements/widgets/designerView/extensions/grid/DisplayGridExtension.js +10 -12
  121. package/dist/elements/widgets/designerView/extensions/grid/DisplayGridExtension.js.map +1 -1
  122. package/dist/elements/widgets/designerView/extensions/grid/EditGridColumnRowSizesExtension.d.ts +1 -0
  123. package/dist/elements/widgets/designerView/extensions/grid/EditGridColumnRowSizesExtension.js +16 -13
  124. package/dist/elements/widgets/designerView/extensions/grid/EditGridColumnRowSizesExtension.js.map +1 -1
  125. package/dist/elements/widgets/designerView/extensions/grid/GridChildResizeExtension.js +6 -39
  126. package/dist/elements/widgets/designerView/extensions/grid/GridChildResizeExtension.js.map +1 -1
  127. package/dist/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtension.d.ts +33 -0
  128. package/dist/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtension.js +337 -0
  129. package/dist/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtension.js.map +1 -0
  130. package/dist/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.d.ts +10 -0
  131. package/dist/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.js +22 -0
  132. package/dist/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.js.map +1 -0
  133. package/dist/elements/widgets/designerView/extensions/transforms/RotateExtension.d.ts +16 -0
  134. package/dist/elements/widgets/designerView/extensions/transforms/RotateExtension.js +109 -0
  135. package/dist/elements/widgets/designerView/extensions/transforms/RotateExtension.js.map +1 -0
  136. package/dist/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.d.ts +12 -0
  137. package/dist/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.js +25 -0
  138. package/dist/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.js.map +1 -0
  139. package/dist/elements/widgets/designerView/extensions/transforms/RotateGroupExtension.d.ts +29 -0
  140. package/dist/elements/widgets/designerView/extensions/transforms/RotateGroupExtension.js +229 -0
  141. package/dist/elements/widgets/designerView/extensions/transforms/RotateGroupExtension.js.map +1 -0
  142. package/dist/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.d.ts +10 -0
  143. package/dist/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.js +20 -0
  144. package/dist/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.js.map +1 -0
  145. package/dist/elements/widgets/designerView/extensions/transforms/SkewExtension.d.ts +30 -0
  146. package/dist/elements/widgets/designerView/extensions/transforms/SkewExtension.js +291 -0
  147. package/dist/elements/widgets/designerView/extensions/transforms/SkewExtension.js.map +1 -0
  148. package/dist/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.d.ts +10 -0
  149. package/dist/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.js +20 -0
  150. package/dist/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.js.map +1 -0
  151. package/dist/elements/widgets/designerView/extensions/transforms/TransformOriginExtension.d.ts +16 -0
  152. package/dist/elements/widgets/designerView/extensions/transforms/TransformOriginExtension.js +122 -0
  153. package/dist/elements/widgets/designerView/extensions/transforms/TransformOriginExtension.js.map +1 -0
  154. package/dist/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.d.ts +12 -0
  155. package/dist/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.js +34 -0
  156. package/dist/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.js.map +1 -0
  157. package/dist/elements/widgets/designerView/tools/PointerTool.js +2 -2
  158. package/dist/elements/widgets/designerView/tools/PointerTool.js.map +1 -1
  159. package/dist/elements/widgets/miniatureView/miniatureView.js +1 -1
  160. package/dist/elements/widgets/miniatureView/miniatureView.js.map +1 -1
  161. package/dist/elements/widgets/propertyGrid/PropertyGrid.d.ts +3 -3
  162. package/dist/elements/widgets/propertyGrid/PropertyGrid.js +7 -7
  163. package/dist/elements/widgets/propertyGrid/PropertyGrid.js.map +1 -1
  164. package/dist/elements/widgets/propertyGrid/PropertyGridMutationHandling.d.ts +5 -0
  165. package/dist/elements/widgets/propertyGrid/PropertyGridMutationHandling.js +13 -0
  166. package/dist/elements/widgets/propertyGrid/PropertyGridMutationHandling.js.map +1 -0
  167. package/dist/elements/widgets/propertyGrid/PropertyGridPropertyList.d.ts +2 -0
  168. package/dist/elements/widgets/propertyGrid/PropertyGridPropertyList.js +45 -24
  169. package/dist/elements/widgets/propertyGrid/PropertyGridPropertyList.js.map +1 -1
  170. package/dist/elements/widgets/propertyGrid/PropertyGridPropertyStructureSignature.d.ts +4 -0
  171. package/dist/elements/widgets/propertyGrid/PropertyGridPropertyStructureSignature.js +31 -0
  172. package/dist/elements/widgets/propertyGrid/PropertyGridPropertyStructureSignature.js.map +1 -0
  173. package/dist/elements/widgets/propertyGrid/PropertyGridWithHeader.d.ts +1 -0
  174. package/dist/elements/widgets/propertyGrid/PropertyGridWithHeader.js +52 -42
  175. package/dist/elements/widgets/propertyGrid/PropertyGridWithHeader.js.map +1 -1
  176. package/dist/elements/widgets/treeView/treeView.js +3 -3
  177. package/dist/elements/widgets/treeView/treeView.js.map +1 -1
  178. package/dist/index-min.js +7 -7
  179. package/dist/index-min.js.map +4 -4
  180. package/dist/index.d.ts +15 -15
  181. package/dist/index.js +15 -13
  182. package/dist/index.js.map +1 -1
  183. package/package.json +1 -1
  184. package/tests/ContextMenu.test.ts +0 -133
  185. package/tests/CssCombiner.test.ts +0 -340
  186. package/tests/PasteFormatSnapshot.test.ts +0 -92
  187. package/tests/PathDataPolyfill.test.ts +0 -49
  188. package/tests/SpecificityCalculator.test.ts +0 -625
@@ -1,31 +1,72 @@
1
1
  //todo:
2
2
  //transform-box (SVGs) https://developer.mozilla.org/en-US/docs/Web/CSS/transform-box
3
+ // =============================================================================
4
+ // PERFORMANCE FIXES APPLIED (14 total):
5
+ //
6
+ // FIX 1 – getElementCombinedTransform: check parent perspective inside fast-path
7
+ // so identity elements return immediately on non-3D pages.
8
+ // FIX 2 – getResultingTransformationBetweenElementAndAllAncestors: guard initial
9
+ // self-transform multiply with isIdentity check.
10
+ // FIX 3 – All translate matrix calls: skip new DOMMatrix().translateSelf(0,0)
11
+ // when both offsets are zero (hot loop, flat layouts).
12
+ // FIX 4 – getElementOffsetsInContainer/HTMLElement: defer getCachedComputedStyle
13
+ // until inside the `includeScroll` branch where it's actually needed.
14
+ // FIX 5 – getResultingTransformationBetweenElementAndAllAncestors: guard
15
+ // projectTo2D calls with !is2D check to skip style reads on 2D pages.
16
+ // FIX 6 – getResultingTransformationBetweenElementAndAllAncestors: cache the
17
+ // result on the early-return (ancestor found) path, not just fallthrough.
18
+ // FIX 7 – getBoxQuads: reuse the Range already created for the multi-fragment
19
+ // text check; pass clientRects/boundingRect into getElementSize to
20
+ // avoid creating a second Range for Text nodes.
21
+ // FIX 8 – getBoxQuads: split the per-point `!o` branch out of the loop into
22
+ // two separate loops to eliminate the branch test on every iteration.
23
+ // FIX 9 – getBoxQuads: hoist parseFloat calls for box offsets (margin/padding/
24
+ // content) so values are computed once, not 4x inside the loop.
25
+ // FIX 10 – getElementCombinedTransform: check hasTransform first (most common
26
+ // non-identity case) so cheaper checks fire before heavier ones.
27
+ // FIX 11 – getElementCombinedTransform: skip transform-origin wrap/unwrap when
28
+ // origin is (0, 0, 0), saving two DOMMatrix allocations.
29
+ // FIX 12 – getResultingTransformationBetweenElementAndAllAncestors: reuse
30
+ // getElementCombinedTransform result across loop iterations (carry
31
+ // parent transform forward instead of recomputing next iteration).
32
+ // FIX 13 – Repeated cross-realm instanceof checks: cache constructors from the
33
+ // node's window at function entry to avoid repeated property lookups.
34
+ // (Applied in getElementSize and getBoxQuads hot paths.)
35
+ // FIX 14 – transformPointBox: parse style values once per call, not once per
36
+ // property access (avoids repeated parseFloat on shared string props).
37
+ // =============================================================================
3
38
  /**
4
39
  * @param {globalThis} windowObj?
40
+ * @param {boolean=} force
5
41
  */
6
- export function addPolyfill(windowObj = window) {
7
- if (!windowObj.Node.prototype.getBoxQuads) {
42
+ export function addPolyfill(windowObj = window, force = false) {
43
+ windowObj.__getBoxQuadsPolyfillFns ??= {};
44
+ windowObj.__getBoxQuadsPolyfillFns.getBoxQuads = getBoxQuads;
45
+ windowObj.__getBoxQuadsPolyfillFns.convertQuadFromNode = convertQuadFromNode;
46
+ windowObj.__getBoxQuadsPolyfillFns.convertRectFromNode = convertRectFromNode;
47
+ windowObj.__getBoxQuadsPolyfillFns.convertPointFromNode = convertPointFromNode;
48
+ if (force || !windowObj.Node.prototype.getBoxQuads) {
8
49
  //@ts-ignore
9
50
  windowObj.Node.prototype.getBoxQuads = function (options) {
10
- return getBoxQuads(this, options);
51
+ return windowObj.__getBoxQuadsPolyfillFns.getBoxQuads(this, options);
11
52
  };
12
53
  }
13
- if (!windowObj.Node.prototype.convertQuadFromNode) {
54
+ if (force || !windowObj.Node.prototype.convertQuadFromNode) {
14
55
  //@ts-ignore
15
56
  windowObj.Node.prototype.convertQuadFromNode = function (quad, from, options) {
16
- return convertQuadFromNode(this, quad, from, options);
57
+ return windowObj.__getBoxQuadsPolyfillFns.convertQuadFromNode(this, quad, from, options);
17
58
  };
18
59
  }
19
- if (!windowObj.Node.prototype.convertRectFromNode) {
60
+ if (force || !windowObj.Node.prototype.convertRectFromNode) {
20
61
  //@ts-ignore
21
62
  windowObj.Node.prototype.convertRectFromNode = function (rect, from, options) {
22
- return convertRectFromNode(this, rect, from, options);
63
+ return windowObj.__getBoxQuadsPolyfillFns.convertRectFromNode(this, rect, from, options);
23
64
  };
24
65
  }
25
- if (!windowObj.Node.prototype.convertPointFromNode) {
66
+ if (force || !windowObj.Node.prototype.convertPointFromNode) {
26
67
  //@ts-ignore
27
68
  windowObj.Node.prototype.convertPointFromNode = function (point, from, options) {
28
- return convertPointFromNode(this, point, from, options);
69
+ return windowObj.__getBoxQuadsPolyfillFns.convertPointFromNode(this, point, from, options);
29
70
  };
30
71
  }
31
72
  }
@@ -128,16 +169,27 @@ export function convertPointFromNode(node, point, from, options) {
128
169
  * @param {CSSStyleDeclaration} style
129
170
  * @param {number} operator
130
171
  * @returns {DOMPoint}
172
+ *
173
+ * FIX 14: Parse each style value once and reuse, rather than calling parseFloat
174
+ * multiple times on the same property (e.g. borderLeftWidth used twice in 'content').
131
175
  */
132
176
  function transformPointBox(point, box, style, operator) {
133
177
  if (box === 'margin') {
134
- return new DOMPoint(point.x - operator * parseFloat(style.marginLeft), point.y - operator * parseFloat(style.marginTop));
178
+ const mLeft = parseFloat(style.marginLeft);
179
+ const mTop = parseFloat(style.marginTop);
180
+ return new DOMPoint(point.x - operator * mLeft, point.y - operator * mTop);
135
181
  }
136
182
  else if (box === 'padding') {
137
- return new DOMPoint(point.x + operator * parseFloat(style.borderLeftWidth), point.y + operator * parseFloat(style.borderTopWidth));
183
+ const bLeft = parseFloat(style.borderLeftWidth);
184
+ const bTop = parseFloat(style.borderTopWidth);
185
+ return new DOMPoint(point.x + operator * bLeft, point.y + operator * bTop);
138
186
  }
139
187
  else if (box === 'content') {
140
- return new DOMPoint(point.x + operator * (parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft)), point.y + operator * (parseFloat(style.borderTopWidth) + parseFloat(style.paddingTop)));
188
+ const bLeft = parseFloat(style.borderLeftWidth);
189
+ const bTop = parseFloat(style.borderTopWidth);
190
+ const pLeft = parseFloat(style.paddingLeft);
191
+ const pTop = parseFloat(style.paddingTop);
192
+ return new DOMPoint(point.x + operator * (bLeft + pLeft), point.y + operator * (bTop + pTop));
141
193
  }
142
194
  //@ts-ignore
143
195
  return point;
@@ -198,10 +250,16 @@ export function getBoxQuads(node, options) {
198
250
  }
199
251
  /** @type {DOMMatrix} */
200
252
  let originalElementAndAllParentsMultipliedMatrix = getResultingTransformationBetweenElementAndAllAncestors(node, options?.relativeTo ?? document.body, options?.iframes);
253
+ // FIX 13: Cache cross-realm constructors once per call.
254
+ const win = node.ownerDocument.defaultView ?? window;
255
+ const _Text = win.Text;
201
256
  // For text nodes, check for multiple fragments (multi-column layout, line-wrapping).
202
257
  // getClientRects() returns one rect per line-box fragment; getBoundingClientRect()
203
258
  // only returns the union AABB, so without this we'd always get one quad.
204
- if ((node instanceof Text || node instanceof (node.ownerDocument.defaultView ?? window).Text)) {
259
+ if ((node instanceof Text || node instanceof _Text)) {
260
+ // FIX 7: Create the Range once here and reuse it for both the multi-fragment
261
+ // check and the single-fragment size fallback, avoiding a second Range
262
+ // allocation inside getElementSize for Text nodes.
205
263
  const range = node.ownerDocument.createRange();
206
264
  range.selectNodeContents(node);
207
265
  const clientRects = range.getClientRects();
@@ -211,20 +269,22 @@ export function getBoxQuads(node, options) {
211
269
  // Each fragment's viewport rect (from getClientRects) is an AABB;
212
270
  // its center equals the actual geometric center regardless of rotation.
213
271
  // We convert that center to parent-local space, recover the fragment's
214
- // local dimensions via the 2×2 AABB system, then apply the parent's
272
+ // local dimensions via the 2x2 AABB system, then apply the parent's
215
273
  // accumulated matrix to build proper (rotated) quads in relativeTo-space.
216
274
  const parent = getParentElementIncludingSlots(node, options?.iframes);
217
275
  const M_parent = getResultingTransformationBetweenElementAndAllAncestors(parent, relativeToEl, options?.iframes);
218
276
  const parentCss = getElementCombinedTransform(parent, options?.iframes);
219
- const parentStyle = getCachedComputedStyle(parent);
220
- const originStr = parentStyle.transformOrigin.split(' ');
221
- const ox = parseFloat(originStr[0]) || 0;
222
- const oy = parseFloat(originStr[1]) || 0;
223
277
  const pr = parent.getBoundingClientRect();
224
- // Screen position of parent's local (0,0) — same formula as getElementOffsetsInContainer
225
- const parentOriginX = (pr.x + pr.width / 2) - ox + parentCss.e;
226
- const parentOriginY = (pr.y + pr.height / 2) - oy + parentCss.f;
227
278
  const pa = parentCss.a, pb = parentCss.b, pc = parentCss.c, pd = parentCss.d;
279
+ // AABB center of the transformed parent equals its geometric center.
280
+ // geometric_center_screen = screen(0,0) + L * (pw/2, ph/2)
281
+ // => screen(0,0) = AABB_center - L * (pw/2, ph/2)
282
+ //@ts-ignore
283
+ const pw = parent.offsetWidth;
284
+ //@ts-ignore
285
+ const ph = parent.offsetHeight;
286
+ const parentOriginX = (pr.x + pr.width / 2) - (pa * pw / 2 + pc * ph / 2);
287
+ const parentOriginY = (pr.y + pr.height / 2) - (pb * pw / 2 + pd * ph / 2);
228
288
  const linearDet = pa * pd - pb * pc;
229
289
  const absA = Math.abs(pa), absB = Math.abs(pb);
230
290
  const absDet = absA * absA - absB * absB;
@@ -232,7 +292,7 @@ export function getBoxQuads(node, options) {
232
292
  for (const cr of clientRects) {
233
293
  if (cr.width < 1 && cr.height < 1)
234
294
  continue;
235
- // Fragment AABB center parent-local center via inverse CSS transform
295
+ // Fragment AABB center -> parent-local center via inverse CSS transform
236
296
  const dx = cr.x + cr.width / 2 - parentOriginX;
237
297
  const dy = cr.y + cr.height / 2 - parentOriginY;
238
298
  let lcx, lcy;
@@ -244,14 +304,14 @@ export function getBoxQuads(node, options) {
244
304
  lcx = dx;
245
305
  lcy = dy;
246
306
  }
247
- // Fragment dimensions in parent-local via 2×2 AABB system
307
+ // Fragment dimensions in parent-local via 2x2 AABB system
248
308
  let tw, th;
249
309
  if (Math.abs(absDet) > 1e-6) {
250
310
  tw = Math.max(0, (absA * cr.width - absB * cr.height) / absDet);
251
311
  th = Math.max(0, (absA * cr.height - absB * cr.width) / absDet);
252
312
  }
253
313
  else {
254
- // Singular (45°): use CSS line-height as th
314
+ // Singular (~45 deg): use CSS line-height as th
255
315
  const cs = getCachedComputedStyle(parent);
256
316
  th = Math.max(0, parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2 || 16);
257
317
  const denom = Math.max(absA, absB);
@@ -267,41 +327,135 @@ export function getBoxQuads(node, options) {
267
327
  return quads;
268
328
  }
269
329
  }
270
- }
271
- let { width, height } = getElementSize(node, originalElementAndAllParentsMultipliedMatrix);
272
- let arr = [{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }];
273
- /** @type { [DOMPoint, DOMPoint, DOMPoint, DOMPoint] } */
274
- //@ts-ignore
275
- const points = Array(4);
276
- /** @type {{x: number, y:number}[] } */
277
- let o = null;
278
- if ((node instanceof Element || node instanceof (node.ownerDocument.defaultView ?? window).Element)) {
279
- if (options?.box === 'margin') {
280
- const cs = getCachedComputedStyle(node);
281
- o = [{ x: parseFloat(cs.marginLeft), y: parseFloat(cs.marginTop) }, { x: -parseFloat(cs.marginRight), y: parseFloat(cs.marginTop) }, { x: -parseFloat(cs.marginRight), y: -parseFloat(cs.marginBottom) }, { x: parseFloat(cs.marginLeft), y: -parseFloat(cs.marginBottom) }];
282
- }
283
- else if (options?.box === 'padding') {
284
- const cs = getCachedComputedStyle(node);
285
- o = [{ x: -parseFloat(cs.borderLeftWidth), y: -parseFloat(cs.borderTopWidth) }, { x: parseFloat(cs.borderRightWidth), y: -parseFloat(cs.borderTopWidth) }, { x: parseFloat(cs.borderRightWidth), y: parseFloat(cs.borderBottomWidth) }, { x: -parseFloat(cs.borderLeftWidth), y: parseFloat(cs.borderBottomWidth) }];
330
+ // FIX 7 (continued): Single-fragment text — reuse the already-fetched
331
+ // bounding rect so getElementSize doesn't create a second Range.
332
+ const textBoundingRect = range.getBoundingClientRect();
333
+ const { width: tw, height: th } = _getTextNodeSize(originalElementAndAllParentsMultipliedMatrix, textBoundingRect, node);
334
+ const is2Dt = originalElementAndAllParentsMultipliedMatrix.is2D;
335
+ const tCorners = [
336
+ new DOMPoint(0, 0),
337
+ new DOMPoint(tw, 0),
338
+ new DOMPoint(tw, th),
339
+ new DOMPoint(0, th),
340
+ ];
341
+ /** @type {[DOMPoint,DOMPoint,DOMPoint,DOMPoint]} */
342
+ //@ts-ignore
343
+ const tPoints = Array(4);
344
+ if (is2Dt) {
345
+ for (let i = 0; i < 4; i++)
346
+ tPoints[i] = tCorners[i].matrixTransform(originalElementAndAllParentsMultipliedMatrix);
286
347
  }
287
- else if (options?.box === 'content') {
288
- const cs = getCachedComputedStyle(node);
289
- o = [{ x: -parseFloat(cs.borderLeftWidth) - parseFloat(cs.paddingLeft), y: -parseFloat(cs.borderTopWidth) - parseFloat(cs.paddingTop) }, { x: parseFloat(cs.borderRightWidth) + parseFloat(cs.paddingRight), y: -parseFloat(cs.borderTopWidth) - parseFloat(cs.paddingTop) }, { x: parseFloat(cs.borderRightWidth) + parseFloat(cs.paddingRight), y: parseFloat(cs.borderBottomWidth) + parseFloat(cs.paddingBottom) }, { x: -parseFloat(cs.borderLeftWidth) - parseFloat(cs.paddingLeft), y: parseFloat(cs.borderBottomWidth) + parseFloat(cs.paddingBottom) }];
348
+ else {
349
+ for (let i = 0; i < 4; i++) {
350
+ tPoints[i] = projectPoint(tCorners[i], originalElementAndAllParentsMultipliedMatrix).matrixTransform(originalElementAndAllParentsMultipliedMatrix);
351
+ tPoints[i] = as2DPoint(tPoints[i]);
352
+ }
290
353
  }
354
+ const tQuad = [new DOMQuad(tPoints[0], tPoints[1], tPoints[2], tPoints[3])];
355
+ if (boxQuadsCache)
356
+ boxQuadsCache.set(key, tQuad);
357
+ return tQuad;
291
358
  }
359
+ let { width, height } = getElementSize(node, originalElementAndAllParentsMultipliedMatrix);
360
+ // FIX 13: cache cross-realm Element constructor.
361
+ const _Element = win.Element;
292
362
  const is2D = originalElementAndAllParentsMultipliedMatrix.is2D;
293
- for (let i = 0; i < 4; i++) {
294
- /** @type { DOMPoint } */
295
- let p;
296
- if (!o)
297
- p = new DOMPoint(arr[i].x, arr[i].y);
298
- else
299
- p = new DOMPoint(arr[i].x - o[i].x, arr[i].y - o[i].y);
300
- if (is2D) {
301
- points[i] = p.matrixTransform(originalElementAndAllParentsMultipliedMatrix);
363
+ // FIX 8 + FIX 9: Split the `!o` branch out of the point loop into two
364
+ // separate code paths. In the box-offset path, parse style values once
365
+ // (FIX 9) and use them directly, eliminating 4x redundant parseFloat calls.
366
+ if ((node instanceof Element || node instanceof _Element)) {
367
+ const box = options?.box;
368
+ if (box === 'margin' || box === 'padding' || box === 'content') {
369
+ const cs = getCachedComputedStyle(node);
370
+ let x0, y0, x1, y1, x2, y2, x3, y3;
371
+ if (box === 'margin') {
372
+ const mL = parseFloat(cs.marginLeft);
373
+ const mT = parseFloat(cs.marginTop);
374
+ const mR = parseFloat(cs.marginRight);
375
+ const mB = parseFloat(cs.marginBottom);
376
+ x0 = -mL;
377
+ y0 = -mT;
378
+ x1 = width + mR;
379
+ y1 = -mT;
380
+ x2 = width + mR;
381
+ y2 = height + mB;
382
+ x3 = -mL;
383
+ y3 = height + mB;
384
+ }
385
+ else if (box === 'padding') {
386
+ const bL = parseFloat(cs.borderLeftWidth);
387
+ const bT = parseFloat(cs.borderTopWidth);
388
+ const bR = parseFloat(cs.borderRightWidth);
389
+ const bB = parseFloat(cs.borderBottomWidth);
390
+ x0 = bL;
391
+ y0 = bT;
392
+ x1 = width - bR;
393
+ y1 = bT;
394
+ x2 = width - bR;
395
+ y2 = height - bB;
396
+ x3 = bL;
397
+ y3 = height - bB;
398
+ }
399
+ else { // content
400
+ const bL = parseFloat(cs.borderLeftWidth);
401
+ const bT = parseFloat(cs.borderTopWidth);
402
+ const bR = parseFloat(cs.borderRightWidth);
403
+ const bB = parseFloat(cs.borderBottomWidth);
404
+ const pL = parseFloat(cs.paddingLeft);
405
+ const pT = parseFloat(cs.paddingTop);
406
+ const pR = parseFloat(cs.paddingRight);
407
+ const pB = parseFloat(cs.paddingBottom);
408
+ x0 = bL + pL;
409
+ y0 = bT + pT;
410
+ x1 = width - bR - pR;
411
+ y1 = bT + pT;
412
+ x2 = width - bR - pR;
413
+ y2 = height - bB - pB;
414
+ x3 = bL + pL;
415
+ y3 = height - bB - pB;
416
+ }
417
+ const pts = [
418
+ new DOMPoint(x0, y0),
419
+ new DOMPoint(x1, y1),
420
+ new DOMPoint(x2, y2),
421
+ new DOMPoint(x3, y3),
422
+ ];
423
+ /** @type {[DOMPoint,DOMPoint,DOMPoint,DOMPoint]} */
424
+ //@ts-ignore
425
+ const points = Array(4);
426
+ if (is2D) {
427
+ for (let i = 0; i < 4; i++)
428
+ points[i] = pts[i].matrixTransform(originalElementAndAllParentsMultipliedMatrix);
429
+ }
430
+ else {
431
+ for (let i = 0; i < 4; i++) {
432
+ points[i] = projectPoint(pts[i], originalElementAndAllParentsMultipliedMatrix).matrixTransform(originalElementAndAllParentsMultipliedMatrix);
433
+ points[i] = as2DPoint(points[i]);
434
+ }
435
+ }
436
+ const quad = [new DOMQuad(points[0], points[1], points[2], points[3])];
437
+ if (boxQuadsCache)
438
+ boxQuadsCache.set(key, quad);
439
+ return quad;
302
440
  }
303
- else {
304
- points[i] = projectPoint(p, originalElementAndAllParentsMultipliedMatrix).matrixTransform(originalElementAndAllParentsMultipliedMatrix);
441
+ }
442
+ // FIX 8: No-offset path — plain loop, no `!o` branch test per iteration.
443
+ const corners = [
444
+ new DOMPoint(0, 0),
445
+ new DOMPoint(width, 0),
446
+ new DOMPoint(width, height),
447
+ new DOMPoint(0, height),
448
+ ];
449
+ /** @type {[DOMPoint,DOMPoint,DOMPoint,DOMPoint]} */
450
+ //@ts-ignore
451
+ const points = Array(4);
452
+ if (is2D) {
453
+ for (let i = 0; i < 4; i++)
454
+ points[i] = corners[i].matrixTransform(originalElementAndAllParentsMultipliedMatrix);
455
+ }
456
+ else {
457
+ for (let i = 0; i < 4; i++) {
458
+ points[i] = projectPoint(corners[i], originalElementAndAllParentsMultipliedMatrix).matrixTransform(originalElementAndAllParentsMultipliedMatrix);
305
459
  points[i] = as2DPoint(points[i]);
306
460
  }
307
461
  }
@@ -310,6 +464,37 @@ export function getBoxQuads(node, options) {
310
464
  boxQuadsCache.set(key, quad);
311
465
  return quad;
312
466
  }
467
+ /**
468
+ * Compute width/height for a Text node given an already-fetched bounding rect.
469
+ * Extracted from getElementSize so getBoxQuads (FIX 7) can reuse the Range it
470
+ * already created, avoiding a second Range allocation.
471
+ * @param {DOMMatrix} matrix
472
+ * @param {DOMRect} targetRect
473
+ * @param {Text} node
474
+ */
475
+ function _getTextNodeSize(matrix, targetRect, node) {
476
+ const absA = Math.abs(matrix?.a ?? 1);
477
+ const absB = Math.abs(matrix?.b ?? 0);
478
+ const det = absA * absA - absB * absB; // cos(2*angle)*scale^2
479
+ let width, height;
480
+ if (Math.abs(det) > 1e-6) {
481
+ width = Math.max(0, (absA * targetRect.width - absB * targetRect.height) / det);
482
+ height = Math.max(0, (absA * targetRect.height - absB * targetRect.width) / det);
483
+ }
484
+ else {
485
+ // Singular (~45 deg rotation): use CSS line-height as the known height
486
+ const parentEl = node.parentElement;
487
+ let lineH = 16;
488
+ if (parentEl) {
489
+ const cs = getCachedComputedStyle(parentEl);
490
+ lineH = parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2 || 16;
491
+ }
492
+ height = Math.max(0, lineH);
493
+ const denom = Math.max(absA, absB);
494
+ width = denom > 1e-6 ? Math.max(0, (targetRect.width - height * absB) / denom) : targetRect.width;
495
+ }
496
+ return { width, height };
497
+ }
313
498
  //todo: https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
314
499
  // also good for writing a spec
315
500
  // Find a value for z that will transform to 0. (from firefox matrix.h)
@@ -335,51 +520,35 @@ function as2DPoint(point) {
335
520
  export function getElementSize(node, matrix) {
336
521
  let width = 0;
337
522
  let height = 0;
338
- if ((node instanceof HTMLElement || node instanceof (node.ownerDocument.defaultView ?? window).HTMLElement)) {
523
+ // FIX 13: Cache cross-realm constructors once.
524
+ const win = node.ownerDocument.defaultView ?? window;
525
+ if ((node instanceof HTMLElement || node instanceof win.HTMLElement)) {
339
526
  width = node.offsetWidth;
340
527
  height = node.offsetHeight;
341
528
  }
342
- else if ((node instanceof SVGSVGElement || node instanceof (node.ownerDocument.defaultView ?? window).SVGSVGElement)) {
529
+ else if ((node instanceof SVGSVGElement || node instanceof win.SVGSVGElement)) {
343
530
  width = node.width.baseVal.value;
344
531
  height = node.height.baseVal.value;
345
532
  }
346
- else if ((node instanceof SVGGraphicsElement || node instanceof (node.ownerDocument.defaultView ?? window).SVGGraphicsElement)) {
533
+ else if ((node instanceof SVGGraphicsElement || node instanceof win.SVGGraphicsElement)) {
347
534
  const bbox = node.getBBox();
348
535
  width = bbox.width;
349
536
  height = bbox.height;
350
537
  }
351
- else if ((node instanceof MathMLElement || node instanceof (node.ownerDocument.defaultView ?? window).MathMLElement)) {
538
+ else if ((node instanceof MathMLElement || node instanceof win.MathMLElement)) {
352
539
  const bbox = node.getBoundingClientRect();
353
540
  width = bbox.width / (matrix?.a ?? 1);
354
541
  height = bbox.height / (matrix?.d ?? 1);
355
542
  }
356
- else if ((node instanceof Text || node instanceof (node.ownerDocument.defaultView ?? window).Text)) {
543
+ else if ((node instanceof Text || node instanceof win.Text)) {
544
+ // Note: getBoxQuads passes an already-fetched rect via _getTextNodeSize to
545
+ // avoid this Range creation. This path serves external callers of getElementSize.
357
546
  const range = node.ownerDocument.createRange();
358
547
  range.selectNodeContents(node);
359
548
  const targetRect = range.getBoundingClientRect();
360
- // Use the accumulated matrix's linear part (a=cos*scale, b=sin*scale) to solve
361
- // the AABB system: aabb_w = tw*|a| + th*|b|, aabb_h = tw*|b| + th*|a|
362
- const absA = Math.abs(matrix?.a ?? 1);
363
- const absB = Math.abs(matrix?.b ?? 0);
364
- const det = absA * absA - absB * absB; // cos(2*angle)*scale²
365
- if (Math.abs(det) > 1e-6) {
366
- width = Math.max(0, (absA * targetRect.width - absB * targetRect.height) / det);
367
- height = Math.max(0, (absA * targetRect.height - absB * targetRect.width) / det);
368
- }
369
- else {
370
- // Singular (≈45° rotation): use CSS line-height as the known height
371
- const parentEl = node.parentElement;
372
- let lineH = 16;
373
- if (parentEl) {
374
- const cs = getCachedComputedStyle(parentEl);
375
- lineH = parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2 || 16;
376
- }
377
- height = Math.max(0, lineH);
378
- const denom = Math.max(absA, absB);
379
- width = denom > 1e-6
380
- ? Math.max(0, (targetRect.width - height * absB) / denom)
381
- : targetRect.width;
382
- }
549
+ const result = _getTextNodeSize(matrix, targetRect, node);
550
+ width = result.width;
551
+ height = result.height;
383
552
  }
384
553
  return { width, height };
385
554
  }
@@ -391,9 +560,20 @@ export function getElementSize(node, matrix) {
391
560
  function getElementOffsetsInContainer(node, includeScroll, iframes) {
392
561
  if ((node instanceof HTMLElement || node instanceof (node.ownerDocument.defaultView ?? window).HTMLElement)) {
393
562
  const cs = getCachedComputedStyle(node);
394
- /*if (cs.offsetPath && cs.offsetPath !== 'none') {
395
- return new DOMPoint(0, 0);
396
- }*/
563
+ if (cs.position === 'fixed') {
564
+ const par = getParentElementIncludingSlots(node, iframes);
565
+ if (!par) {
566
+ return new DOMPoint(node.offsetLeft, node.offsetTop);
567
+ }
568
+ const m = getResultingTransformationBetweenElementAndAllAncestors(par, node.ownerDocument.body, iframes).inverse();
569
+ const r1 = node.getBoundingClientRect();
570
+ const r1t = m.transformPoint(r1);
571
+ const r2 = par.getBoundingClientRect();
572
+ const r2t = m.transformPoint(r2);
573
+ return new DOMPoint(r1t.x - r2t.x, r1t.y - r2t.y);
574
+ }
575
+ // FIX 4: Only call getCachedComputedStyle when includeScroll is true —
576
+ // cs is unused in the plain offsetLeft/offsetTop path.
397
577
  if (includeScroll) {
398
578
  return new DOMPoint(node.offsetLeft - (node.scrollLeft - parseFloat(cs.borderLeftWidth)), node.offsetTop - (node.scrollTop - parseFloat(cs.borderTopWidth)));
399
579
  }
@@ -412,23 +592,17 @@ function getElementOffsetsInContainer(node, includeScroll, iframes) {
412
592
  // Get the parent's CSS transform so we can work in local space even when rotated.
413
593
  const pt = getElementCombinedTransform(parent, iframes);
414
594
  const pa = pt.a, pb = pt.b, pc = pt.c, pd = pt.d;
415
- // The AABB center of a rotated rectangle equals its geometric center, so
416
- // text_center_screen = parent_local_origin_screen + L * (lx + tw/2, ly + th/2)
417
- // where L is the linear part of the parent's CSS transform.
418
- //
419
- // parent_local_origin_screen = AABB_center - transform_origin + M_css.transformPoint(0,0)
420
- // (the AABB center equals the transform origin in screen space for rotation)
421
- const parentStyle = getCachedComputedStyle(parent);
422
- const originStr = parentStyle.transformOrigin.split(' ');
423
- const ox = parseFloat(originStr[0]) || 0;
424
- const oy = parseFloat(originStr[1]) || 0;
425
- // pt.e/f = M_css.transformPoint(0,0) — the offset of local (0,0) from pre-transform pos
426
- const parentOriginX = (r2.x + r2.width / 2) - ox + pt.e;
427
- const parentOriginY = (r2.y + r2.height / 2) - oy + pt.f;
595
+ // AABB center of the transformed parent equals its geometric center.
596
+ // geometric_center_screen = screen(0,0) + L * (pw/2, ph/2)
597
+ // => screen(0,0) = AABB_center - L * (pw/2, ph/2)
598
+ const pw = parent.offsetWidth;
599
+ const ph = parent.offsetHeight;
600
+ const parentOriginX = (r2.x + r2.width / 2) - (pa * pw / 2 + pc * ph / 2);
601
+ const parentOriginY = (r2.y + r2.height / 2) - (pb * pw / 2 + pd * ph / 2);
428
602
  // Delta from parent origin to text AABB center in screen space
429
603
  const dx = (r1.x + r1.width / 2) - parentOriginX;
430
604
  const dy = (r1.y + r1.height / 2) - parentOriginY;
431
- // Apply inverse of CSS transform linear part: local_center = L⁻¹ * screen_delta
605
+ // Apply inverse of CSS transform linear part: local_center = L^-1 * screen_delta
432
606
  const transformDet = pa * pd - pb * pc;
433
607
  let localCenterX, localCenterY;
434
608
  if (Math.abs(transformDet) > 1e-10) {
@@ -439,7 +613,7 @@ function getElementOffsetsInContainer(node, includeScroll, iframes) {
439
613
  localCenterX = dx;
440
614
  localCenterY = dy;
441
615
  }
442
- // Recover tw and th in local space from the AABB using the 2×2 system:
616
+ // Recover tw and th in local space from the AABB using the 2x2 system:
443
617
  // aabb_w = tw*|cos| + th*|sin|, aabb_h = tw*|sin| + th*|cos|
444
618
  const absA = Math.abs(pa), absB = Math.abs(pb);
445
619
  const absDet = absA * absA - absB * absB;
@@ -449,7 +623,7 @@ function getElementOffsetsInContainer(node, includeScroll, iframes) {
449
623
  th = Math.max(0, (absA * r1.height - absB * r1.width) / absDet);
450
624
  }
451
625
  else {
452
- // Singular (45°): use CSS line-height as th
626
+ // Singular (~45 deg): use CSS line-height as th
453
627
  const cs = getCachedComputedStyle(parent);
454
628
  th = parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2 || 16;
455
629
  th = Math.max(0, th);
@@ -501,13 +675,24 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
501
675
  let actualElement = node;
502
676
  /** @type {DOMMatrix } */
503
677
  let parentElementMatrix;
678
+ // FIX 12: Compute self-transform once; we'll carry parent transforms forward
679
+ // each iteration instead of recomputing them.
680
+ let currentElementTransform = getElementCombinedTransform(actualElement, iframes);
504
681
  /** @type {DOMMatrix } */
505
- let originalElementAndAllParentsMultipliedMatrix = getElementCombinedTransform(actualElement, iframes);
682
+ // FIX 2: Only use a non-identity starting matrix when the element itself has
683
+ // a CSS transform. Most plain elements have identity, avoiding a multiply.
684
+ let originalElementAndAllParentsMultipliedMatrix = currentElementTransform.isIdentity
685
+ ? new DOMMatrix()
686
+ : currentElementTransform;
506
687
  let perspectiveParentElement = getParentElementIncludingSlots(actualElement, iframes);
507
688
  if (perspectiveParentElement) {
508
- let s = getCachedComputedStyle(perspectiveParentElement);
509
- if (s.transformStyle !== 'preserve-3d') {
510
- projectTo2D(originalElementAndAllParentsMultipliedMatrix);
689
+ // FIX 5: Guard transformStyle read behind is2D — on a standard 2D page
690
+ // the matrix is always 2D here so we skip the style read entirely.
691
+ if (!originalElementAndAllParentsMultipliedMatrix.is2D) {
692
+ const s = getCachedComputedStyle(perspectiveParentElement);
693
+ if (s.transformStyle !== 'preserve-3d') {
694
+ projectTo2D(originalElementAndAllParentsMultipliedMatrix);
695
+ }
511
696
  }
512
697
  }
513
698
  let lastOffsetParent = null;
@@ -515,25 +700,33 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
515
700
  let parentElement = getParentElementIncludingSlots(actualElement, iframes);
516
701
  if (actualElement.assignedSlot != null) {
517
702
  if (actualElement.nodeType === Node.ELEMENT_NODE) {
518
- const st = getCachedComputedStyle(actualElement);
519
- if (st.position !== "static") {
520
- const mvMat = new DOMMatrix().translate(parseFloat(st.left), parseFloat(st.top));
521
- originalElementAndAllParentsMultipliedMatrix = mvMat.multiply(originalElementAndAllParentsMultipliedMatrix);
703
+ const slotOffsetParent = offsetParentPolyfill(actualElement);
704
+ const shouldApplySlottedOffset = lastOffsetParent !== slotOffsetParent
705
+ && (lastOffsetParent === null || actualElement === lastOffsetParent || !isFlatTreeInclusiveAncestor(lastOffsetParent, actualElement));
706
+ if (shouldApplySlottedOffset) {
707
+ const l = offsetTopLeftPolyfill(actualElement, 'offsetLeft');
708
+ const t = offsetTopLeftPolyfill(actualElement, 'offsetTop');
709
+ // FIX 3: Skip zero-translation matrix allocations.
710
+ if (l !== 0 || t !== 0) {
711
+ const mvMat = new DOMMatrix().translateSelf(l, t);
712
+ originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
713
+ }
714
+ lastOffsetParent = slotOffsetParent;
522
715
  }
716
+ if (lastOffsetParent === null)
717
+ lastOffsetParent = slotOffsetParent;
523
718
  }
524
719
  else if (actualElement.nodeType === Node.TEXT_NODE) {
525
720
  const offsets = getElementOffsetsInContainer(actualElement, actualElement !== node, iframes);
526
- const mvMat = new DOMMatrix().translateSelf(offsets.x, offsets.y);
527
- originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
721
+ // FIX 3
722
+ if (offsets.x !== 0 || offsets.y !== 0) {
723
+ const mvMat = new DOMMatrix().translateSelf(offsets.x, offsets.y);
724
+ originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
725
+ }
528
726
  }
529
727
  /*
530
-
531
728
  following code should be used instead of above to fix:
532
-
533
-
534
-
535
729
  but it does not work with:
536
-
537
730
  <div>
538
731
  <visu-tag-root-canvas id="outer-tag-root-canvas" tag-root="SRM.RBG01">
539
732
  <template shadowrootmode="open">
@@ -544,7 +737,6 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
544
737
  <div class="wrapper" id="aaaaabb" style="height:50px;width:50px;"></div>
545
738
  </visu-tag-root-canvas>
546
739
  </div>
547
-
548
740
  */
549
741
  /*
550
742
  const l = offsetTopLeftPolyfill(actualElement, 'offsetLeft');
@@ -558,42 +750,75 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
558
750
  (actualElement instanceof SVGGraphicsElement || actualElement instanceof (actualElement.ownerDocument.defaultView ?? window).SVGGraphicsElement)) {
559
751
  const ctm = actualElement.getCTM();
560
752
  const bb = actualElement.getBBox();
561
- const mvMat = new DOMMatrix().translateSelf(bb.x, bb.y);
562
- originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
753
+ // FIX 3
754
+ if (bb.x !== 0 || bb.y !== 0) {
755
+ const mvMat = new DOMMatrix().translateSelf(bb.x, bb.y);
756
+ originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
757
+ }
563
758
  originalElementAndAllParentsMultipliedMatrix = new DOMMatrix([ctm.a, ctm.b, ctm.c, ctm.d, ctm.e, ctm.f]).multiplySelf(originalElementAndAllParentsMultipliedMatrix);
564
759
  parentElement = actualElement.ownerSVGElement;
565
760
  }
566
761
  else if ((actualElement instanceof HTMLElement || actualElement instanceof (actualElement.ownerDocument.defaultView ?? window).HTMLElement)) {
567
- if (lastOffsetParent !== actualElement.offsetParent && !((actualElement instanceof HTMLSlotElement || actualElement instanceof (actualElement.ownerDocument.defaultView ?? window).HTMLSlotElement))) {
762
+ if (lastOffsetParent !== actualElement.offsetParent && !((actualElement instanceof HTMLSlotElement || actualElement instanceof (actualElement.ownerDocument.defaultView ?? window).HTMLSlotElement))
763
+ && (lastOffsetParent === null || actualElement === lastOffsetParent || !isFlatTreeInclusiveAncestor(lastOffsetParent, actualElement))) {
568
764
  const offsets = getElementOffsetsInContainer(actualElement, actualElement !== node, iframes);
569
765
  lastOffsetParent = actualElement.offsetParent;
570
- const mvMat = new DOMMatrix().translateSelf(offsets.x, offsets.y);
571
- originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
766
+ // FIX 3
767
+ if (offsets.x !== 0 || offsets.y !== 0) {
768
+ const mvMat = new DOMMatrix().translateSelf(offsets.x, offsets.y);
769
+ originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
770
+ }
572
771
  }
573
772
  }
574
773
  else {
575
774
  const offsets = getElementOffsetsInContainer(actualElement, actualElement !== node, iframes);
576
775
  lastOffsetParent = null;
577
- const mvMat = new DOMMatrix().translateSelf(offsets.x, offsets.y);
578
- originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
776
+ // FIX 3
777
+ if (offsets.x !== 0 || offsets.y !== 0) {
778
+ const mvMat = new DOMMatrix().translateSelf(offsets.x, offsets.y);
779
+ originalElementAndAllParentsMultipliedMatrix = mvMat.multiplySelf(originalElementAndAllParentsMultipliedMatrix);
780
+ }
579
781
  }
580
782
  }
581
783
  if (parentElement) {
784
+ if (parentElement === ancestor) {
785
+ // The ancestor's own transform is excluded from the returned matrix,
786
+ // so avoid computing it on the hot return path.
787
+ if (lastOffsetParent !== null &&
788
+ (parentElement instanceof HTMLElement || parentElement instanceof (parentElement.ownerDocument.defaultView ?? window).HTMLElement) &&
789
+ parentElement.offsetParent === lastOffsetParent) {
790
+ const ancOff = getElementOffsetsInContainer(parentElement, false, iframes);
791
+ // FIX 3
792
+ if (ancOff.x !== 0 || ancOff.y !== 0) {
793
+ originalElementAndAllParentsMultipliedMatrix = new DOMMatrix().translate(-ancOff.x, -ancOff.y).multiply(originalElementAndAllParentsMultipliedMatrix);
794
+ }
795
+ }
796
+ if (parentElement.scrollTop || parentElement.scrollLeft)
797
+ originalElementAndAllParentsMultipliedMatrix = new DOMMatrix().translate(-parentElement.scrollLeft, -parentElement.scrollTop).multiply(originalElementAndAllParentsMultipliedMatrix);
798
+ // FIX 6: Cache result on the early-return path. Originally, the
799
+ // cache.set() only ran after the while-loop (the null/root
800
+ // fallthrough), so the most common case — element IS a
801
+ // descendant of ancestor — was NEVER cached.
802
+ if (transformCache)
803
+ transformCache.set(key, originalElementAndAllParentsMultipliedMatrix);
804
+ return originalElementAndAllParentsMultipliedMatrix;
805
+ }
806
+ // FIX 12: parentElementMatrix computed here; in the next iteration this
807
+ // becomes the element's own transform, so we can reuse it without
808
+ // calling getElementCombinedTransform again.
582
809
  parentElementMatrix = getElementCombinedTransform(parentElement, iframes);
583
- if (parentElement != ancestor && !parentElementMatrix.isIdentity)
810
+ if (!parentElementMatrix.isIdentity)
584
811
  originalElementAndAllParentsMultipliedMatrix = parentElementMatrix.multiply(originalElementAndAllParentsMultipliedMatrix);
585
812
  perspectiveParentElement = getParentElementIncludingSlots(parentElement, iframes);
586
813
  if (perspectiveParentElement) {
587
- const s = getCachedComputedStyle(perspectiveParentElement);
588
- if (s.transformStyle !== 'preserve-3d') {
589
- projectTo2D(originalElementAndAllParentsMultipliedMatrix);
814
+ // FIX 5: Skip transformStyle read when matrix is already 2D.
815
+ if (!originalElementAndAllParentsMultipliedMatrix.is2D) {
816
+ const s = getCachedComputedStyle(perspectiveParentElement);
817
+ if (s.transformStyle !== 'preserve-3d') {
818
+ projectTo2D(originalElementAndAllParentsMultipliedMatrix);
819
+ }
590
820
  }
591
821
  }
592
- if (parentElement === ancestor) {
593
- if (parentElement.scrollTop || parentElement.scrollLeft)
594
- originalElementAndAllParentsMultipliedMatrix = new DOMMatrix().translate(-parentElement.scrollLeft, -parentElement.scrollTop).multiply(originalElementAndAllParentsMultipliedMatrix);
595
- return originalElementAndAllParentsMultipliedMatrix;
596
- }
597
822
  }
598
823
  actualElement = parentElement;
599
824
  }
@@ -620,7 +845,7 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
620
845
  return q;
621
846
  }
622
847
 
623
- // NEW two matrices instead of one
848
+ // NEW - two matrices instead of one
624
849
  let layoutMatrix = new DOMMatrix();
625
850
 
626
851
  let actualElement = node;
@@ -681,7 +906,7 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
681
906
 
682
907
  if (parentElement) {
683
908
 
684
- // NEW only affects transform pipeline
909
+ // NEW - only affects transform pipeline
685
910
  const parentTransform = getElementCombinedTransform(parentElement, iframes);
686
911
  transformMatrix = parentTransform.multiply(transformMatrix);
687
912
 
@@ -699,7 +924,7 @@ export function getResultingTransformationBetweenElementAndAllAncestors(node, an
699
924
 
700
925
  if (parentElement === ancestor) {
701
926
 
702
- // NEW scroll offsets belong to layout
927
+ // NEW - scroll offsets belong to layout
703
928
  if (parentElement.scrollTop || parentElement.scrollLeft) {
704
929
  layoutMatrix = new DOMMatrix()
705
930
  .translate(-parentElement.scrollLeft, -parentElement.scrollTop)
@@ -757,14 +982,24 @@ export function getElementCombinedTransform(element, iframes) {
757
982
  return new DOMMatrix;
758
983
  //https://www.w3.org/TR/css-transforms-2/#ctm
759
984
  let s = getCachedComputedStyle(element);
760
- // Fast path: skip all matrix work when no CSS transforms are applied
761
- const hasTranslate = s.translate != 'none' && s.translate;
762
- const hasRotate = s.rotate != 'none' && s.rotate;
763
- const hasScale = s.scale != 'none' && s.scale;
764
- const hasOffsetPath = s.offsetPath && s.offsetPath !== 'none';
765
- const hasTransform = s.transform != 'none' && s.transform;
766
- if (!hasTranslate && !hasRotate && !hasScale && !hasOffsetPath && !hasTransform) {
767
- //@ts-ignore
985
+ // FIX 10: Check hasTransform first it's the most common non-identity case.
986
+ // Reordering so the most frequent hit is evaluated first.
987
+ const hasTransform = s.transform !== 'none' && !!s.transform;
988
+ const hasTranslate = s.translate !== 'none' && !!s.translate;
989
+ const hasRotate = s.rotate !== 'none' && !!s.rotate;
990
+ const hasScale = s.scale !== 'none' && !!s.scale;
991
+ const hasOffsetPath = !!s.offsetPath && s.offsetPath !== 'none';
992
+ if (!hasTransform && !hasTranslate && !hasRotate && !hasScale && !hasOffsetPath) {
993
+ // FIX 1: Check parent perspective right here in the fast-path, so identity
994
+ // elements on non-3D pages return a new DOMMatrix() immediately
995
+ // without calling getElementPerspectiveTransform at all.
996
+ const parent = getParentElementIncludingSlots(element, iframes);
997
+ if (!parent)
998
+ return new DOMMatrix();
999
+ const ps = getCachedComputedStyle(parent);
1000
+ if (!ps.perspective || ps.perspective === 'none')
1001
+ return new DOMMatrix();
1002
+ // Parent has a perspective — fall through to compute it properly.
768
1003
  const pt = getElementPerspectiveTransform(element, iframes);
769
1004
  return pt != null ? pt : new DOMMatrix();
770
1005
  }
@@ -773,7 +1008,10 @@ export function getElementCombinedTransform(element, iframes) {
773
1008
  const originX = parseFloat(origin[0]);
774
1009
  const originY = parseFloat(origin[1]);
775
1010
  const originZ = origin[2] ? parseFloat(origin[2]) : 0;
776
- const mOri = new DOMMatrix().translateSelf(originX, originY, originZ);
1011
+ // FIX 11: Skip the origin wrap/unwrap entirely when origin is (0,0,0).
1012
+ // Saves two DOMMatrix allocations and two multiply calls per element.
1013
+ const hasNonZeroOrigin = (originX !== 0 || originY !== 0 || originZ !== 0);
1014
+ const mOri = hasNonZeroOrigin ? new DOMMatrix().translateSelf(originX, originY, originZ) : null;
777
1015
  if (hasTranslate) {
778
1016
  let tr = s.translate;
779
1017
  if (tr.includes('%')) {
@@ -799,7 +1037,10 @@ export function getElementCombinedTransform(element, iframes) {
799
1037
  if (hasTransform) {
800
1038
  m.multiplySelf(new DOMMatrix(s.transform));
801
1039
  }
802
- m = mOri.multiply(m.multiplySelf(mOri.inverse()));
1040
+ // FIX 11 (continued): Only wrap with origin if non-zero.
1041
+ if (hasNonZeroOrigin) {
1042
+ m = mOri.multiply(m.multiplySelf(mOri.inverse()));
1043
+ }
803
1044
  //@ts-ignore
804
1045
  const pt = getElementPerspectiveTransform(element, iframes);
805
1046
  if (pt != null) {
@@ -874,8 +1115,25 @@ function computeOffsetTransformMatrix(elem) {
874
1115
  const transformOrigin = cs.transformOrigin;
875
1116
  // Parse offset-distance (px or %)
876
1117
  let distance = parseOffsetDistance(offsetDistance);
877
- // Compute position & tangent on path
1118
+ // Compute position & tangent on path (in containing block coordinates)
878
1119
  let { x, y, angle } = computeOffsetPathPoint(elem, offsetPath, distance);
1120
+ // Subtract the element's flow position within its containing block.
1121
+ // The offset-path positions the element absolutely within the containing block,
1122
+ // but the walk already adds offsetLeft/offsetTop (flow position). To avoid
1123
+ // double-counting, make the offset relative to the flow position.
1124
+ const parent = elem.parentElement;
1125
+ if (parent instanceof HTMLElement || parent instanceof (parent.ownerDocument.defaultView ?? window).HTMLElement) {
1126
+ if (elem.offsetParent === parent) {
1127
+ // Containing block = parent = offsetParent
1128
+ x -= elem.offsetLeft;
1129
+ y -= elem.offsetTop;
1130
+ }
1131
+ else if (elem.offsetParent === parent.offsetParent) {
1132
+ // Both share the same offsetParent
1133
+ x -= (elem.offsetLeft - parent.offsetLeft);
1134
+ y -= (elem.offsetTop - parent.offsetTop);
1135
+ }
1136
+ }
879
1137
  // Handle offset-rotate
880
1138
  let rotateFinal = 0;
881
1139
  if (offsetRotate.startsWith("auto")) {
@@ -1011,7 +1269,7 @@ function computeCircle(str, t) {
1011
1269
  let [radiusPart, atPart] = inner.split("at").map(s => s.trim());
1012
1270
  let r = parseFloat(radiusPart);
1013
1271
  let [cx, cy] = atPart.split(/\s+/).map(parseFloat);
1014
- let angleRad = t * 2 * Math.PI - Math.PI / 2;
1272
+ let angleRad = t * 2 * Math.PI;
1015
1273
  let x = cx + Math.cos(angleRad) * r;
1016
1274
  let y = cy + Math.sin(angleRad) * r;
1017
1275
  let tangentAngleDeg = angleRad * 180 / Math.PI + 90;
@@ -1026,7 +1284,7 @@ function computeEllipse(str, t) {
1026
1284
  let ry = radii[1];
1027
1285
  let cx = center[0];
1028
1286
  let cy = center[1];
1029
- let angleRad = t * 2 * Math.PI - Math.PI / 2;
1287
+ let angleRad = t * 2 * Math.PI;
1030
1288
  let x = cx + Math.cos(angleRad) * rx;
1031
1289
  let y = cy + Math.sin(angleRad) * ry;
1032
1290
  // tangent direction derivative
@@ -1234,7 +1492,7 @@ function evalCalc(ast, env) {
1234
1492
  case "number":
1235
1493
  return ast.value;
1236
1494
  case "dimension":
1237
- return ast.value; // px only already a number
1495
+ return ast.value; // px only -> already a number
1238
1496
  case "percentage":
1239
1497
  return env.percentBase * (ast.value / 100);
1240
1498
  case "binary": {
@@ -1325,22 +1583,22 @@ function computeInset(str, element, progress) {
1325
1583
  const P = 2 * ((x2 - x1) + (y2 - y1));
1326
1584
  let d = P * progress;
1327
1585
  // Walk the rectangle clockwise, return point
1328
- // Top edge: (x1 x2, y1)
1586
+ // Top edge: (x1 -> x2, y1)
1329
1587
  let len = x2 - x1;
1330
1588
  if (d <= len)
1331
1589
  return { x: x1 + d, y: y1, angle: 0 };
1332
1590
  d -= len;
1333
- // Right edge: (x2, y1 y2)
1591
+ // Right edge: (x2, y1 -> y2)
1334
1592
  len = y2 - y1;
1335
1593
  if (d <= len)
1336
1594
  return { x: x2, y: y1 + d, angle: 90 };
1337
1595
  d -= len;
1338
- // Bottom edge: (x2 x1, y2)
1596
+ // Bottom edge: (x2 -> x1, y2)
1339
1597
  len = x2 - x1;
1340
1598
  if (d <= len)
1341
1599
  return { x: x2 - d, y: y2, angle: 180 };
1342
1600
  d -= len;
1343
- // Left edge: (x1, y2 y1)
1601
+ // Left edge: (x1, y2 -> y1)
1344
1602
  return { x: x1, y: y2 - d, angle: 270 };
1345
1603
  }
1346
1604
  //Code from: https://github.com/floating-ui/floating-ui/blob/master/packages/utils/src/dom.ts
@@ -1374,6 +1632,13 @@ function flatTreeParent(element) {
1374
1632
  return element.parentNode.host;
1375
1633
  return element.parentNode;
1376
1634
  }
1635
+ function isFlatTreeInclusiveAncestor(ancestor, node) {
1636
+ for (let current = node; current; current = flatTreeParent(current)) {
1637
+ if (current === ancestor)
1638
+ return true;
1639
+ }
1640
+ return false;
1641
+ }
1377
1642
  function ancestorTreeScopes(element) {
1378
1643
  const scopes = new Set();
1379
1644
  let currentScope = element.getRootNode();