@jsenv/dom 0.6.1 → 0.7.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 (109) hide show
  1. package/dist/jsenv_dom.js +259 -314
  2. package/package.json +2 -4
  3. package/index.js +0 -124
  4. package/src/attr/add_attribute_effect.js +0 -93
  5. package/src/attr/attributes.js +0 -32
  6. package/src/color/color_constrast.js +0 -69
  7. package/src/color/color_parsing.js +0 -319
  8. package/src/color/color_scheme.js +0 -28
  9. package/src/color/pick_light_or_dark.js +0 -34
  10. package/src/color/resolve_css_color.js +0 -60
  11. package/src/demos/3_columns_resize_demo.html +0 -84
  12. package/src/demos/3_rows_resize_demo.html +0 -89
  13. package/src/demos/aside_and_main_demo.html +0 -93
  14. package/src/demos/coordinates_demo.html +0 -450
  15. package/src/demos/document_autoscroll_demo.html +0 -517
  16. package/src/demos/drag_gesture_constraints_demo.html +0 -701
  17. package/src/demos/drag_gesture_demo.html +0 -1047
  18. package/src/demos/drag_gesture_element_to_impact_demo.html +0 -445
  19. package/src/demos/drag_reference_element_demo.html +0 -480
  20. package/src/demos/flex_details_set_demo.html +0 -302
  21. package/src/demos/flex_details_set_demo_2.html +0 -315
  22. package/src/demos/visible_rect_demo.html +0 -525
  23. package/src/element_signature.js +0 -100
  24. package/src/interaction/drag/constraint_feedback_line.js +0 -92
  25. package/src/interaction/drag/drag_constraint.js +0 -659
  26. package/src/interaction/drag/drag_debug_markers.js +0 -635
  27. package/src/interaction/drag/drag_element_positioner.js +0 -382
  28. package/src/interaction/drag/drag_gesture.js +0 -566
  29. package/src/interaction/drag/drag_resize_demo.html +0 -571
  30. package/src/interaction/drag/drag_to_move.js +0 -301
  31. package/src/interaction/drag/drag_to_resize_gesture.js +0 -68
  32. package/src/interaction/drag/drop_target_detection.js +0 -148
  33. package/src/interaction/drag/sticky_frontiers.js +0 -160
  34. package/src/interaction/event_marker.js +0 -14
  35. package/src/interaction/focus/active_element.js +0 -33
  36. package/src/interaction/focus/arrow_navigation.js +0 -599
  37. package/src/interaction/focus/element_is_focusable.js +0 -57
  38. package/src/interaction/focus/element_visibility.js +0 -111
  39. package/src/interaction/focus/find_focusable.js +0 -21
  40. package/src/interaction/focus/focus_group.js +0 -91
  41. package/src/interaction/focus/focus_group_registry.js +0 -12
  42. package/src/interaction/focus/focus_nav.js +0 -12
  43. package/src/interaction/focus/focus_nav_event_marker.js +0 -14
  44. package/src/interaction/focus/focus_trap.js +0 -105
  45. package/src/interaction/focus/tab_navigation.js +0 -128
  46. package/src/interaction/focus/tests/focus_group_skip_tab_test.html +0 -206
  47. package/src/interaction/focus/tests/tree_focus_test.html +0 -304
  48. package/src/interaction/focus/tests/tree_focus_test.jsx +0 -261
  49. package/src/interaction/focus/tests/tree_focus_test_preact.html +0 -13
  50. package/src/interaction/isolate_interactions.js +0 -161
  51. package/src/interaction/keyboard.js +0 -26
  52. package/src/interaction/scroll/capture_scroll.js +0 -47
  53. package/src/interaction/scroll/is_scrollable.js +0 -159
  54. package/src/interaction/scroll/scroll_container.js +0 -110
  55. package/src/interaction/scroll/scroll_trap.js +0 -44
  56. package/src/interaction/scroll/scrollbar_size.js +0 -20
  57. package/src/interaction/scroll/wheel_through.js +0 -138
  58. package/src/iterable_weak_set.js +0 -66
  59. package/src/position/dom_coords.js +0 -340
  60. package/src/position/offset_parent.js +0 -15
  61. package/src/position/position_fixed.js +0 -15
  62. package/src/position/position_sticky.js +0 -213
  63. package/src/position/sticky_rect.js +0 -79
  64. package/src/position/visible_rect.js +0 -486
  65. package/src/pub_sub.js +0 -31
  66. package/src/size/can_take_size.js +0 -11
  67. package/src/size/details_content_full_height.js +0 -63
  68. package/src/size/flex_details_set.js +0 -974
  69. package/src/size/get_available_height.js +0 -22
  70. package/src/size/get_available_width.js +0 -22
  71. package/src/size/get_border_sizes.js +0 -14
  72. package/src/size/get_height.js +0 -4
  73. package/src/size/get_inner_height.js +0 -15
  74. package/src/size/get_inner_width.js +0 -15
  75. package/src/size/get_margin_sizes.js +0 -10
  76. package/src/size/get_max_height.js +0 -57
  77. package/src/size/get_max_width.js +0 -47
  78. package/src/size/get_min_height.js +0 -14
  79. package/src/size/get_min_width.js +0 -14
  80. package/src/size/get_padding_sizes.js +0 -10
  81. package/src/size/get_width.js +0 -4
  82. package/src/size/hooks/use_available_height.js +0 -27
  83. package/src/size/hooks/use_available_width.js +0 -27
  84. package/src/size/hooks/use_max_height.js +0 -10
  85. package/src/size/hooks/use_max_width.js +0 -10
  86. package/src/size/hooks/use_resize_status.js +0 -62
  87. package/src/size/resize.js +0 -695
  88. package/src/size/resolve_css_size.js +0 -32
  89. package/src/style/dom_styles.js +0 -97
  90. package/src/style/style_composition.js +0 -121
  91. package/src/style/style_controller.js +0 -345
  92. package/src/style/style_default.js +0 -153
  93. package/src/style/style_default_demo.html +0 -128
  94. package/src/style/style_parsing.js +0 -375
  95. package/src/transition/demos/animation_resumption_test.xhtml +0 -500
  96. package/src/transition/demos/height_toggle_test.xhtml +0 -515
  97. package/src/transition/dom_transition.js +0 -254
  98. package/src/transition/easing.js +0 -48
  99. package/src/transition/group_transition.js +0 -261
  100. package/src/transition/transform_style_parser.js +0 -32
  101. package/src/transition/transition_playback.js +0 -366
  102. package/src/transition/transition_timeline.js +0 -79
  103. package/src/traversal.js +0 -247
  104. package/src/ui_transition/demos/content_states_transition_demo.html +0 -628
  105. package/src/ui_transition/demos/smooth_height_transition_demo.html +0 -149
  106. package/src/ui_transition/demos/transition_testing.html +0 -354
  107. package/src/ui_transition/ui_transition.js +0 -1470
  108. package/src/utils.js +0 -69
  109. package/src/value_effect.js +0 -35
@@ -1,153 +0,0 @@
1
- import { normalizeStyle } from "./style_parsing.js";
2
-
3
- const DEBUG = false;
4
-
5
- // Register the style isolator custom element once
6
- let persistentStyleIsolator = null;
7
- const getNaviStyleIsolator = () => {
8
- if (persistentStyleIsolator) {
9
- return persistentStyleIsolator;
10
- }
11
-
12
- class StyleIsolator extends HTMLElement {
13
- constructor() {
14
- super();
15
-
16
- // Create shadow DOM to isolate from external CSS
17
- const shadow = this.attachShadow({ mode: "closed" });
18
-
19
- shadow.innerHTML = `
20
- <style>
21
- :host {
22
- all: initial;
23
- display: block;
24
- position: fixed;
25
- top: 0;
26
- left: 0;
27
- opacity: ${DEBUG ? 0.5 : 0};
28
- visibility: ${DEBUG ? "visible" : "hidden"};
29
- pointer-events: none;
30
- }
31
- * {
32
- all: revert;
33
- }
34
- </style>
35
- <div id="unstyled_element_slot"></div>
36
- `;
37
-
38
- this.unstyledElementSlot = shadow.querySelector("#unstyled_element_slot");
39
- }
40
-
41
- getIsolatedStyles(element, context = "js") {
42
- if (!DEBUG) {
43
- this.unstyledElementSlot.innerHTML = "";
44
- }
45
- const unstyledElement = element.cloneNode(true);
46
- this.unstyledElementSlot.appendChild(unstyledElement);
47
-
48
- // Get computed styles of the actual element inside the shadow DOM
49
- const computedStyles = getComputedStyle(unstyledElement);
50
- // Create a copy of the styles since the original will be invalidated when element is removed
51
- const stylesCopy = {};
52
- for (let i = 0; i < computedStyles.length; i++) {
53
- const property = computedStyles[i];
54
- stylesCopy[property] = normalizeStyle(
55
- computedStyles.getPropertyValue(property),
56
- property,
57
- context,
58
- );
59
- }
60
-
61
- return stylesCopy;
62
- }
63
- }
64
-
65
- if (!customElements.get("navi-style-isolator")) {
66
- customElements.define("navi-style-isolator", StyleIsolator);
67
- }
68
- // Create and add the persistent element to the document
69
- persistentStyleIsolator = document.createElement("navi-style-isolator");
70
- document.body.appendChild(persistentStyleIsolator);
71
- return persistentStyleIsolator;
72
- };
73
-
74
- const stylesCache = new Map();
75
- /**
76
- * Gets the default browser styles for an HTML element by creating an isolated custom element
77
- * @param {string|Element} input - CSS selector (e.g., 'input[type="text"]'), HTML source (e.g., '<button>'), or DOM element
78
- * @param {string} context - Output format: "js" for JS object (default) or "css" for CSS string
79
- * @returns {Object|string} Computed styles as JS object or CSS string
80
- */
81
- export const getDefaultStyles = (input, context = "js") => {
82
- let element;
83
- let cacheKey;
84
-
85
- // Determine input type and create element accordingly
86
- if (typeof input === "string") {
87
- if (input[0] === "<") {
88
- // HTML source
89
- const tempDiv = document.createElement("div");
90
- tempDiv.innerHTML = input;
91
- element = tempDiv.firstElementChild;
92
- if (!element) {
93
- throw new Error(`Invalid HTML source: ${input}`);
94
- }
95
- cacheKey = `${input}:${context}`;
96
- } else {
97
- // CSS selector
98
- element = createElementFromSelector(input);
99
- cacheKey = `${input}:${context}`;
100
- }
101
- } else if (input instanceof Element) {
102
- // DOM element
103
- element = input;
104
- cacheKey = `${input.outerHTML}:${context}`;
105
- } else {
106
- throw new Error(
107
- "Input must be a CSS selector, HTML source, or DOM element",
108
- );
109
- }
110
-
111
- // Check cache first
112
- if (stylesCache.has(cacheKey)) {
113
- return stylesCache.get(cacheKey);
114
- }
115
-
116
- // Get the persistent style isolator element
117
- const naviStyleIsolator = getNaviStyleIsolator();
118
- const defaultStyles = naviStyleIsolator.getIsolatedStyles(element, context);
119
-
120
- // Cache the result
121
- stylesCache.set(cacheKey, defaultStyles);
122
-
123
- return defaultStyles;
124
- };
125
-
126
- /**
127
- * Creates an HTML element from a CSS selector
128
- * @param {string} selector - CSS selector (e.g., 'input[type="text"]', 'button', 'a[href="#"]')
129
- * @returns {Element} DOM element
130
- */
131
- const createElementFromSelector = (selector) => {
132
- // Parse the selector to extract tag name and attributes
133
- const tagMatch = selector.match(/^([a-zA-Z][a-zA-Z0-9-]*)/);
134
- if (!tagMatch) {
135
- throw new Error(`Invalid selector: ${selector}`);
136
- }
137
-
138
- const tagName = tagMatch[1].toLowerCase();
139
- const element = document.createElement(tagName);
140
-
141
- // Extract and apply attributes from selector
142
- const attributeRegex = /\[([^=\]]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\]]*)))?\]/g;
143
- let attributeMatch;
144
-
145
- while ((attributeMatch = attributeRegex.exec(selector)) !== null) {
146
- const attrName = attributeMatch[1];
147
- const attrValue =
148
- attributeMatch[2] || attributeMatch[3] || attributeMatch[4] || "";
149
- element.setAttribute(attrName, attrValue);
150
- }
151
-
152
- return element;
153
- };
@@ -1,128 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Button Background-Color Demo</title>
7
- <style>
8
- body {
9
- font-family: Arial, sans-serif;
10
- margin: 40px;
11
- line-height: 1.6;
12
- }
13
-
14
- .demo-section {
15
- background: white;
16
- padding: 20px;
17
- margin: 20px 0;
18
- border: 1px solid #ddd;
19
- border-radius: 8px;
20
- }
21
-
22
- .result {
23
- background: #f8f8f8;
24
- border: 1px solid #ddd;
25
- padding: 15px;
26
- margin: 10px 0;
27
- font-family: monospace;
28
- font-size: 14px;
29
- }
30
-
31
- .result h3 {
32
- margin-top: 0;
33
- font-family: Arial, sans-serif;
34
- }
35
-
36
- .color-preview {
37
- display: inline-block;
38
- width: 20px;
39
- height: 20px;
40
- border: 1px solid #333;
41
- vertical-align: middle;
42
- margin-left: 8px;
43
- }
44
-
45
- /* Intentionally style buttons to test isolation */
46
- button {
47
- background: red !important;
48
- color: white !important;
49
- border: 3px solid blue !important;
50
- padding: 10px 20px;
51
- margin: 10px;
52
- }
53
- </style>
54
- </head>
55
- <body>
56
- <h1>Button Background-Color Demo</h1>
57
- <p>
58
- This demo tests <code>getDefaultStyles('button')</code> and focuses on the
59
- background-color property.
60
- </p>
61
-
62
- <div class="demo-section">
63
- <h2>Styled Button on Page</h2>
64
- <p>This button is styled with CSS (red background):</p>
65
- <button>Styled Button</button>
66
- <p>
67
- <em
68
- >The button above should be red, proving our page CSS is working.</em
69
- >
70
- </p>
71
- </div>
72
-
73
- <div class="demo-section">
74
- <h2>Default Button Background-Color Test</h2>
75
- <div id="result"></div>
76
- </div>
77
-
78
- <script type="module">
79
- import { getDefaultStyles } from "./style_default.js";
80
-
81
- // Make function available globally for onclick
82
- window.testButtonBackgroundColor = testButtonBackgroundColor;
83
-
84
- function testButtonBackgroundColor() {
85
- const resultDiv = document.getElementById("result");
86
-
87
- try {
88
- console.log("Testing button default background-color...");
89
- const startTime = performance.now();
90
-
91
- // Get default styles for button
92
- const styles = getDefaultStyles("button");
93
-
94
- const endTime = performance.now();
95
- const duration = (endTime - startTime).toFixed(2);
96
-
97
- // Extract background-color
98
- const backgroundColor = styles["background-color"];
99
-
100
- // Display result
101
- resultDiv.innerHTML = `
102
- <div class="result">
103
- <h3>Result (${duration}ms)</h3>
104
- <p><strong>Element:</strong> button</p>
105
- <p><strong>background-color:</strong> <code>${backgroundColor}</code> <span class="color-preview" style="background-color: ${backgroundColor};" title="Color preview: ${backgroundColor}"></span></p>
106
- <p><strong>Expected:</strong> Should be a light color (not red), proving CSS isolation works</p>
107
- <p><strong>Full styles object keys:</strong> ${Object.keys(styles).length} properties</p>
108
- </div>
109
- `;
110
-
111
- console.log("Button default background-color:", backgroundColor);
112
- console.log("Full styles object:", styles);
113
- } catch (error) {
114
- console.error("Error:", error);
115
- resultDiv.innerHTML = `
116
- <div class="result" style="background: #ffe6e6;">
117
- <h3>Error</h3>
118
- <p>${error.message}</p>
119
- </div>
120
- `;
121
- }
122
- }
123
-
124
- // Auto-run test on page load
125
- setTimeout(testButtonBackgroundColor, 500);
126
- </script>
127
- </body>
128
- </html>
@@ -1,375 +0,0 @@
1
- // Properties that need px units
2
- const pxProperties = [
3
- "width",
4
- "height",
5
- "top",
6
- "left",
7
- "right",
8
- "bottom",
9
- "margin",
10
- "marginTop",
11
- "marginRight",
12
- "marginBottom",
13
- "marginLeft",
14
- "padding",
15
- "paddingTop",
16
- "paddingRight",
17
- "paddingBottom",
18
- "paddingLeft",
19
- "border",
20
- "borderWidth",
21
- "borderTopWidth",
22
- "borderRightWidth",
23
- "borderBottomWidth",
24
- "borderLeftWidth",
25
- "fontSize",
26
- "lineHeight",
27
- "letterSpacing",
28
- "wordSpacing",
29
- "translateX",
30
- "translateY",
31
- "translateZ",
32
- "borderRadius",
33
- "borderTopLeftRadius",
34
- "borderTopRightRadius",
35
- "borderBottomLeftRadius",
36
- "borderBottomRightRadius",
37
- "gap",
38
- "rowGap",
39
- "columnGap",
40
- ];
41
-
42
- // Properties that need deg units
43
- const degProperties = [
44
- "rotate",
45
- "rotateX",
46
- "rotateY",
47
- "rotateZ",
48
- "skew",
49
- "skewX",
50
- "skewY",
51
- ];
52
-
53
- // Properties that should remain unitless
54
- const unitlessProperties = [
55
- "opacity",
56
- "zIndex",
57
- "flexGrow",
58
- "flexShrink",
59
- "order",
60
- "columnCount",
61
- "scale",
62
- "scaleX",
63
- "scaleY",
64
- "scaleZ",
65
- ];
66
-
67
- // Normalize a single style value
68
- export const normalizeStyle = (value, propertyName, context = "js") => {
69
- if (propertyName === "transform") {
70
- if (context === "js") {
71
- if (typeof value === "string") {
72
- // For js context, prefer objects
73
- return parseCSSTransform(value);
74
- }
75
- // If code does transform: { translateX: "10px" }
76
- // we want to store { translateX: 10 }
77
- const transformNormalized = {};
78
- for (const key of Object.keys(value)) {
79
- const partValue = normalizeStyle(value[key], key, "js");
80
- transformNormalized[key] = partValue;
81
- }
82
- return transformNormalized;
83
- }
84
- if (typeof value === "object" && value !== null) {
85
- // For CSS context, ensure transform is a string
86
- return stringifyCSSTransform(value);
87
- }
88
- return value;
89
- }
90
-
91
- // Handle transform.* properties (e.g., "transform.translateX")
92
- if (propertyName.startsWith("transform.")) {
93
- if (context === "css") {
94
- console.warn(
95
- `normalizeStyle: magic properties like "${propertyName}" are not applicable in "css" context. Returning original value.`,
96
- );
97
- return value;
98
- }
99
- const transformProperty = propertyName.slice(10); // Remove "transform." prefix
100
- // If value is a CSS transform string, parse it first to extract the specific property
101
- if (typeof value === "string") {
102
- if (value === "none") {
103
- return undefined;
104
- }
105
- const parsedTransform = parseCSSTransform(value);
106
- return parsedTransform?.[transformProperty];
107
- }
108
- // If value is a transform object, extract the property directly
109
- if (typeof value === "object" && value !== null) {
110
- return value[transformProperty];
111
- }
112
- // never supposed to happen, the value given is neither string nor object
113
- return undefined;
114
- }
115
-
116
- if (pxProperties.includes(propertyName)) {
117
- return normalizeNumber(value, context, "px", propertyName);
118
- }
119
- if (degProperties.includes(propertyName)) {
120
- return normalizeNumber(value, context, "deg", propertyName);
121
- }
122
- if (unitlessProperties.includes(propertyName)) {
123
- return normalizeNumber(value, context, "", propertyName);
124
- }
125
-
126
- return value;
127
- };
128
- const normalizeNumber = (value, context, unit, propertyName) => {
129
- if (context === "css") {
130
- if (typeof value === "number") {
131
- if (isNaN(value)) {
132
- console.warn(`NaN found for "${propertyName}"`);
133
- }
134
- return `${value}${unit}`;
135
- }
136
- return value;
137
- }
138
- if (typeof value === "string") {
139
- // For js context, only convert px values to numbers
140
- if (unit === "px" && value.endsWith("px")) {
141
- const numericValue = parseFloat(value);
142
- if (!isNaN(numericValue)) {
143
- return numericValue;
144
- }
145
- }
146
- // Keep all other strings as-is (including %, em, rem, auto, none, etc.)
147
- return value;
148
- }
149
- return value;
150
- };
151
-
152
- // Normalize styles for DOM application
153
- export const normalizeStyles = (styles, context = "js") => {
154
- if (!styles) {
155
- return {};
156
- }
157
- if (typeof styles === "string") {
158
- styles = parseStyleString(styles);
159
- return styles;
160
- }
161
- const normalized = {};
162
- for (const key of Object.keys(styles)) {
163
- const value = styles[key];
164
- normalized[key] = normalizeStyle(value, key, context);
165
- }
166
- return normalized;
167
- };
168
-
169
- /**
170
- * Parses a CSS style string into a style object.
171
- * Handles CSS properties with proper camelCase conversion.
172
- *
173
- * @param {string} styleString - CSS style string like "color: red; font-size: 14px;"
174
- * @returns {object} Style object with camelCase properties
175
- */
176
- export const parseStyleString = (styleString, context = "js") => {
177
- const style = {};
178
-
179
- if (!styleString || typeof styleString !== "string") {
180
- return style;
181
- }
182
-
183
- // Split by semicolon and process each declaration
184
- const declarations = styleString.split(";");
185
-
186
- for (let declaration of declarations) {
187
- declaration = declaration.trim();
188
- if (!declaration) continue;
189
-
190
- const colonIndex = declaration.indexOf(":");
191
- if (colonIndex === -1) continue;
192
-
193
- const property = declaration.slice(0, colonIndex).trim();
194
- const value = declaration.slice(colonIndex + 1).trim();
195
-
196
- if (property && value) {
197
- // CSS custom properties (starting with --) should NOT be converted to camelCase
198
- if (property.startsWith("--")) {
199
- style[property] = normalizeStyle(value, property, context);
200
- } else {
201
- // Convert kebab-case to camelCase (e.g., "font-size" -> "fontSize")
202
- const camelCaseProperty = property.replace(
203
- /-([a-z])/g,
204
- (match, letter) => letter.toUpperCase(),
205
- );
206
- style[camelCaseProperty] = normalizeStyle(
207
- value,
208
- camelCaseProperty,
209
- context,
210
- );
211
- }
212
- }
213
- }
214
-
215
- return style;
216
- };
217
-
218
- // Convert transform object to CSS string
219
- export const stringifyCSSTransform = (transformObj) => {
220
- const transforms = [];
221
- for (const key of Object.keys(transformObj)) {
222
- const transformPartValue = transformObj[key];
223
- const normalizedTransformPartValue = normalizeStyle(
224
- transformPartValue,
225
- key,
226
- "css",
227
- );
228
- transforms.push(`${key}(${normalizedTransformPartValue})`);
229
- }
230
- return transforms.join(" ");
231
- };
232
-
233
- // Parse transform CSS string into object
234
- export const parseCSSTransform = (transformString) => {
235
- if (!transformString || transformString === "none") {
236
- return undefined;
237
- }
238
-
239
- const transformObj = {};
240
-
241
- // Parse transform functions
242
- const transformPattern = /(\w+)\(([^)]+)\)/g;
243
- let match;
244
-
245
- while ((match = transformPattern.exec(transformString)) !== null) {
246
- const [, functionName, value] = match;
247
-
248
- // Handle matrix functions specially
249
- if (functionName === "matrix" || functionName === "matrix3d") {
250
- const matrixComponents = parseMatrixTransform(match[0]);
251
- if (matrixComponents) {
252
- // Only add non-default values to preserve original information
253
- Object.assign(transformObj, matrixComponents);
254
- }
255
- // If matrix can't be parsed to simple components, skip it (keep complex transforms as-is)
256
- continue;
257
- }
258
-
259
- // Handle regular transform functions
260
- const normalizedValue = normalizeStyle(value.trim(), functionName, "js");
261
- if (normalizedValue !== undefined) {
262
- transformObj[functionName] = normalizedValue;
263
- }
264
- }
265
-
266
- // Return undefined if no properties were extracted (preserves original information)
267
- return Object.keys(transformObj).length > 0 ? transformObj : undefined;
268
- };
269
-
270
- // Parse a matrix transform and extract simple transform components when possible
271
- const parseMatrixTransform = (matrixString) => {
272
- // Match matrix() or matrix3d() functions
273
- const matrixMatch = matrixString.match(/matrix(?:3d)?\(([^)]+)\)/);
274
- if (!matrixMatch) {
275
- return null;
276
- }
277
-
278
- const values = matrixMatch[1].split(",").map((v) => parseFloat(v.trim()));
279
-
280
- if (matrixString.includes("matrix3d")) {
281
- // matrix3d(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
282
- if (values.length !== 16) {
283
- return null;
284
- }
285
- const [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = values;
286
- // Check if it's a simple 2D transform (most common case)
287
- if (
288
- c === 0 &&
289
- d === 0 &&
290
- g === 0 &&
291
- h === 0 &&
292
- i === 0 &&
293
- j === 0 &&
294
- k === 1 &&
295
- l === 0 &&
296
- o === 0 &&
297
- p === 1
298
- ) {
299
- // This is essentially a 2D transform
300
- return parseSimple2DMatrix(a, b, e, f, m, n);
301
- }
302
- return null; // Complex 3D transform
303
- }
304
- // matrix(a, b, c, d, e, f)
305
- if (values.length !== 6) {
306
- return null;
307
- }
308
- const [a, b, c, d, e, f] = values;
309
- return parseSimple2DMatrix(a, b, c, d, e, f);
310
- };
311
-
312
- // Parse a simple 2D matrix into transform components
313
- const parseSimple2DMatrix = (a, b, c, d, e, f) => {
314
- const result = {};
315
-
316
- // Extract translation - only add if not default (0)
317
- if (e !== 0) {
318
- result.translateX = e;
319
- }
320
- if (f !== 0) {
321
- result.translateY = f;
322
- }
323
-
324
- // Check for identity matrix (no transform)
325
- if (a === 1 && b === 0 && c === 0 && d === 1) {
326
- return result; // Only translation
327
- }
328
-
329
- // Decompose the 2D transformation matrix
330
- // Based on: https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
331
-
332
- const det = a * d - b * c;
333
- // Degenerate matrix (maps to a line or point)
334
- if (det === 0) {
335
- return null;
336
- }
337
-
338
- // Extract scale and rotation
339
- if (c === 0) {
340
- // Simple case: no skew
341
- if (a !== 1) {
342
- result.scaleX = a;
343
- }
344
- if (d !== 1) {
345
- result.scaleY = d;
346
- }
347
- if (b !== 0) {
348
- const angle = Math.atan(b / a) * (180 / Math.PI);
349
- if (angle !== 0) {
350
- result.rotate = angle;
351
- }
352
- }
353
- return result;
354
- }
355
-
356
- // General case: decompose using QR decomposition approach
357
- const scaleX = Math.sqrt(a * a + b * b);
358
- const scaleY = det / scaleX;
359
- const rotation = Math.atan2(b, a) * (180 / Math.PI);
360
- const skewX =
361
- Math.atan((a * c + b * d) / (scaleX * scaleX)) * (180 / Math.PI);
362
- if (scaleX !== 1) {
363
- result.scaleX = scaleX;
364
- }
365
- if (scaleY !== 1) {
366
- result.scaleY = scaleY;
367
- }
368
- if (rotation !== 0) {
369
- result.rotate = rotation;
370
- }
371
- if (skewX !== 0) {
372
- result.skewX = skewX;
373
- }
374
- return result;
375
- };