@jsenv/dom 0.6.1 → 0.7.1
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.
- package/dist/jsenv_dom.js +339 -327
- package/package.json +2 -4
- package/index.js +0 -124
- package/src/attr/add_attribute_effect.js +0 -93
- package/src/attr/attributes.js +0 -32
- package/src/color/color_constrast.js +0 -69
- package/src/color/color_parsing.js +0 -319
- package/src/color/color_scheme.js +0 -28
- package/src/color/pick_light_or_dark.js +0 -34
- package/src/color/resolve_css_color.js +0 -60
- package/src/demos/3_columns_resize_demo.html +0 -84
- package/src/demos/3_rows_resize_demo.html +0 -89
- package/src/demos/aside_and_main_demo.html +0 -93
- package/src/demos/coordinates_demo.html +0 -450
- package/src/demos/document_autoscroll_demo.html +0 -517
- package/src/demos/drag_gesture_constraints_demo.html +0 -701
- package/src/demos/drag_gesture_demo.html +0 -1047
- package/src/demos/drag_gesture_element_to_impact_demo.html +0 -445
- package/src/demos/drag_reference_element_demo.html +0 -480
- package/src/demos/flex_details_set_demo.html +0 -302
- package/src/demos/flex_details_set_demo_2.html +0 -315
- package/src/demos/visible_rect_demo.html +0 -525
- package/src/element_signature.js +0 -100
- package/src/interaction/drag/constraint_feedback_line.js +0 -92
- package/src/interaction/drag/drag_constraint.js +0 -659
- package/src/interaction/drag/drag_debug_markers.js +0 -635
- package/src/interaction/drag/drag_element_positioner.js +0 -382
- package/src/interaction/drag/drag_gesture.js +0 -566
- package/src/interaction/drag/drag_resize_demo.html +0 -571
- package/src/interaction/drag/drag_to_move.js +0 -301
- package/src/interaction/drag/drag_to_resize_gesture.js +0 -68
- package/src/interaction/drag/drop_target_detection.js +0 -148
- package/src/interaction/drag/sticky_frontiers.js +0 -160
- package/src/interaction/event_marker.js +0 -14
- package/src/interaction/focus/active_element.js +0 -33
- package/src/interaction/focus/arrow_navigation.js +0 -599
- package/src/interaction/focus/element_is_focusable.js +0 -57
- package/src/interaction/focus/element_visibility.js +0 -111
- package/src/interaction/focus/find_focusable.js +0 -21
- package/src/interaction/focus/focus_group.js +0 -91
- package/src/interaction/focus/focus_group_registry.js +0 -12
- package/src/interaction/focus/focus_nav.js +0 -12
- package/src/interaction/focus/focus_nav_event_marker.js +0 -14
- package/src/interaction/focus/focus_trap.js +0 -105
- package/src/interaction/focus/tab_navigation.js +0 -128
- package/src/interaction/focus/tests/focus_group_skip_tab_test.html +0 -206
- package/src/interaction/focus/tests/tree_focus_test.html +0 -304
- package/src/interaction/focus/tests/tree_focus_test.jsx +0 -261
- package/src/interaction/focus/tests/tree_focus_test_preact.html +0 -13
- package/src/interaction/isolate_interactions.js +0 -161
- package/src/interaction/keyboard.js +0 -26
- package/src/interaction/scroll/capture_scroll.js +0 -47
- package/src/interaction/scroll/is_scrollable.js +0 -159
- package/src/interaction/scroll/scroll_container.js +0 -110
- package/src/interaction/scroll/scroll_trap.js +0 -44
- package/src/interaction/scroll/scrollbar_size.js +0 -20
- package/src/interaction/scroll/wheel_through.js +0 -138
- package/src/iterable_weak_set.js +0 -66
- package/src/position/dom_coords.js +0 -340
- package/src/position/offset_parent.js +0 -15
- package/src/position/position_fixed.js +0 -15
- package/src/position/position_sticky.js +0 -213
- package/src/position/sticky_rect.js +0 -79
- package/src/position/visible_rect.js +0 -486
- package/src/pub_sub.js +0 -31
- package/src/size/can_take_size.js +0 -11
- package/src/size/details_content_full_height.js +0 -63
- package/src/size/flex_details_set.js +0 -974
- package/src/size/get_available_height.js +0 -22
- package/src/size/get_available_width.js +0 -22
- package/src/size/get_border_sizes.js +0 -14
- package/src/size/get_height.js +0 -4
- package/src/size/get_inner_height.js +0 -15
- package/src/size/get_inner_width.js +0 -15
- package/src/size/get_margin_sizes.js +0 -10
- package/src/size/get_max_height.js +0 -57
- package/src/size/get_max_width.js +0 -47
- package/src/size/get_min_height.js +0 -14
- package/src/size/get_min_width.js +0 -14
- package/src/size/get_padding_sizes.js +0 -10
- package/src/size/get_width.js +0 -4
- package/src/size/hooks/use_available_height.js +0 -27
- package/src/size/hooks/use_available_width.js +0 -27
- package/src/size/hooks/use_max_height.js +0 -10
- package/src/size/hooks/use_max_width.js +0 -10
- package/src/size/hooks/use_resize_status.js +0 -62
- package/src/size/resize.js +0 -695
- package/src/size/resolve_css_size.js +0 -32
- package/src/style/dom_styles.js +0 -97
- package/src/style/style_composition.js +0 -121
- package/src/style/style_controller.js +0 -345
- package/src/style/style_default.js +0 -153
- package/src/style/style_default_demo.html +0 -128
- package/src/style/style_parsing.js +0 -375
- package/src/transition/demos/animation_resumption_test.xhtml +0 -500
- package/src/transition/demos/height_toggle_test.xhtml +0 -515
- package/src/transition/dom_transition.js +0 -254
- package/src/transition/easing.js +0 -48
- package/src/transition/group_transition.js +0 -261
- package/src/transition/transform_style_parser.js +0 -32
- package/src/transition/transition_playback.js +0 -366
- package/src/transition/transition_timeline.js +0 -79
- package/src/traversal.js +0 -247
- package/src/ui_transition/demos/content_states_transition_demo.html +0 -628
- package/src/ui_transition/demos/smooth_height_transition_demo.html +0 -149
- package/src/ui_transition/demos/transition_testing.html +0 -354
- package/src/ui_transition/ui_transition.js +0 -1470
- package/src/utils.js +0 -69
- 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
|
-
};
|