@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,32 +0,0 @@
|
|
|
1
|
-
export const resolveCSSSize = (
|
|
2
|
-
size,
|
|
3
|
-
{ availableSize, fontSize, autoIsRelativeToFont } = {},
|
|
4
|
-
) => {
|
|
5
|
-
if (typeof size === "string") {
|
|
6
|
-
if (size === "auto") {
|
|
7
|
-
return autoIsRelativeToFont ? fontSize : availableSize;
|
|
8
|
-
}
|
|
9
|
-
if (size.endsWith("%")) {
|
|
10
|
-
return availableSize * (parseFloat(size) / 100);
|
|
11
|
-
}
|
|
12
|
-
if (size.endsWith("px")) {
|
|
13
|
-
return parseFloat(size);
|
|
14
|
-
}
|
|
15
|
-
if (size.endsWith("em")) {
|
|
16
|
-
return parseFloat(size) * fontSize;
|
|
17
|
-
}
|
|
18
|
-
if (size.endsWith("rem")) {
|
|
19
|
-
return (
|
|
20
|
-
parseFloat(size) * getComputedStyle(document.documentElement).fontSize
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
if (size.endsWith("vw")) {
|
|
24
|
-
return (parseFloat(size) / 100) * window.innerWidth;
|
|
25
|
-
}
|
|
26
|
-
if (size.endsWith("vh")) {
|
|
27
|
-
return (parseFloat(size) / 100) * window.innerHeight;
|
|
28
|
-
}
|
|
29
|
-
return parseFloat(size);
|
|
30
|
-
}
|
|
31
|
-
return size;
|
|
32
|
-
};
|
package/src/style/dom_styles.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { elementToOwnerWindow } from "../utils.js";
|
|
2
|
-
|
|
3
|
-
export const getComputedStyle = (element) =>
|
|
4
|
-
elementToOwnerWindow(element).getComputedStyle(element);
|
|
5
|
-
|
|
6
|
-
export const getStyle = (element, name) =>
|
|
7
|
-
getComputedStyle(element).getPropertyValue(name);
|
|
8
|
-
|
|
9
|
-
const isCamelCase = (str) => {
|
|
10
|
-
// Check if string contains lowercase letter followed by uppercase letter (camelCase pattern)
|
|
11
|
-
return /[a-z][A-Z]/.test(str);
|
|
12
|
-
};
|
|
13
|
-
const kebabCase = (str) => {
|
|
14
|
-
// Convert camelCase to kebab-case by inserting a hyphen before uppercase letters
|
|
15
|
-
// and converting the uppercase letter to lowercase
|
|
16
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
17
|
-
};
|
|
18
|
-
export const setStyle = (element, name, value) => {
|
|
19
|
-
if (import.meta.dev) {
|
|
20
|
-
if (isCamelCase(name)) {
|
|
21
|
-
console.warn(
|
|
22
|
-
`setStyle: style name "${name}" should be in kebab-case, not camelCase. Use "${kebabCase(name)}" instead.`,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const prevValue = element.style[name];
|
|
28
|
-
if (prevValue) {
|
|
29
|
-
element.style.setProperty(name, value);
|
|
30
|
-
return () => {
|
|
31
|
-
element.style.setProperty(name, prevValue);
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
element.style.setProperty(name, value);
|
|
35
|
-
return () => {
|
|
36
|
-
element.style.removeProperty(name);
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
export const forceStyle = (element, name, value) => {
|
|
40
|
-
const inlineStyleValue = element.style[name];
|
|
41
|
-
if (inlineStyleValue === value) {
|
|
42
|
-
return () => {};
|
|
43
|
-
}
|
|
44
|
-
const computedStyleValue = getStyle(element, name);
|
|
45
|
-
if (computedStyleValue === value) {
|
|
46
|
-
return () => {};
|
|
47
|
-
}
|
|
48
|
-
const restoreStyle = setStyle(element, name, value);
|
|
49
|
-
return restoreStyle;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const addWillChange = (element, property) => {
|
|
53
|
-
const currentWillChange = element.style.willChange;
|
|
54
|
-
const willChangeValues = currentWillChange
|
|
55
|
-
? currentWillChange
|
|
56
|
-
.split(",")
|
|
57
|
-
.map((v) => v.trim())
|
|
58
|
-
.filter(Boolean)
|
|
59
|
-
: [];
|
|
60
|
-
|
|
61
|
-
if (willChangeValues.includes(property)) {
|
|
62
|
-
// Property already exists, return no-op
|
|
63
|
-
return () => {};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
willChangeValues.push(property);
|
|
67
|
-
element.style.willChange = willChangeValues.join(", ");
|
|
68
|
-
// Return function to remove only this property
|
|
69
|
-
return () => {
|
|
70
|
-
const newValues = willChangeValues.filter((v) => v !== property);
|
|
71
|
-
if (newValues.length === 0) {
|
|
72
|
-
element.style.removeProperty("will-change");
|
|
73
|
-
} else {
|
|
74
|
-
element.style.willChange = newValues.join(", ");
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const createSetMany = (setter) => {
|
|
80
|
-
return (element, description) => {
|
|
81
|
-
const cleanupCallbackSet = new Set();
|
|
82
|
-
for (const name of Object.keys(description)) {
|
|
83
|
-
const value = description[name];
|
|
84
|
-
const restoreStyle = setter(element, name, value);
|
|
85
|
-
cleanupCallbackSet.add(restoreStyle);
|
|
86
|
-
}
|
|
87
|
-
return () => {
|
|
88
|
-
for (const cleanupCallback of cleanupCallbackSet) {
|
|
89
|
-
cleanupCallback();
|
|
90
|
-
}
|
|
91
|
-
cleanupCallbackSet.clear();
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export const setStyles = createSetMany(setStyle);
|
|
97
|
-
export const forceStyles = createSetMany(forceStyle);
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeStyle,
|
|
3
|
-
normalizeStyles,
|
|
4
|
-
parseCSSTransform,
|
|
5
|
-
stringifyCSSTransform,
|
|
6
|
-
} from "./style_parsing.js";
|
|
7
|
-
|
|
8
|
-
// Merge two style objects, handling special cases like transform
|
|
9
|
-
export const mergeStyles = (stylesA, stylesB, context = "js") => {
|
|
10
|
-
if (!stylesA) {
|
|
11
|
-
return normalizeStyles(stylesB, context);
|
|
12
|
-
}
|
|
13
|
-
if (!stylesB) {
|
|
14
|
-
return normalizeStyles(stylesA, context);
|
|
15
|
-
}
|
|
16
|
-
const result = {};
|
|
17
|
-
const aKeys = Object.keys(stylesA);
|
|
18
|
-
// in case stylesB is a string we first parse it
|
|
19
|
-
stylesB = normalizeStyles(stylesB, context);
|
|
20
|
-
const bKeyToVisitSet = new Set(Object.keys(stylesB));
|
|
21
|
-
for (const aKey of aKeys) {
|
|
22
|
-
const bHasKey = bKeyToVisitSet.has(aKey);
|
|
23
|
-
if (bHasKey) {
|
|
24
|
-
bKeyToVisitSet.delete(aKey);
|
|
25
|
-
result[aKey] = mergeOneStyle(stylesA[aKey], stylesB[aKey], aKey, context);
|
|
26
|
-
} else {
|
|
27
|
-
result[aKey] = normalizeStyle(stylesA[aKey], aKey, context);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
for (const bKey of bKeyToVisitSet) {
|
|
31
|
-
result[bKey] = stylesB[bKey];
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const appendStyles = (
|
|
37
|
-
stylesAObject,
|
|
38
|
-
stylesBNormalized,
|
|
39
|
-
context = "js",
|
|
40
|
-
) => {
|
|
41
|
-
const aKeys = Object.keys(stylesAObject);
|
|
42
|
-
const bKeys = Object.keys(stylesBNormalized);
|
|
43
|
-
for (const bKey of bKeys) {
|
|
44
|
-
const aHasKey = aKeys.includes(bKey);
|
|
45
|
-
if (aHasKey) {
|
|
46
|
-
stylesAObject[bKey] = mergeOneStyle(
|
|
47
|
-
stylesAObject[bKey],
|
|
48
|
-
stylesBNormalized[bKey],
|
|
49
|
-
bKey,
|
|
50
|
-
context,
|
|
51
|
-
);
|
|
52
|
-
} else {
|
|
53
|
-
stylesAObject[bKey] = stylesBNormalized[bKey];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return stylesAObject;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Merge a single style property value with an existing value
|
|
60
|
-
export const mergeOneStyle = (
|
|
61
|
-
existingValue,
|
|
62
|
-
newValue,
|
|
63
|
-
propertyName,
|
|
64
|
-
context = "js",
|
|
65
|
-
) => {
|
|
66
|
-
if (propertyName === "transform") {
|
|
67
|
-
// Matrix parsing is now handled automatically in parseCSSTransform
|
|
68
|
-
|
|
69
|
-
// Determine the types
|
|
70
|
-
const existingIsString =
|
|
71
|
-
typeof existingValue === "string" && existingValue !== "none";
|
|
72
|
-
const newIsString = typeof newValue === "string" && newValue !== "none";
|
|
73
|
-
const existingIsObject =
|
|
74
|
-
typeof existingValue === "object" && existingValue !== null;
|
|
75
|
-
const newIsObject = typeof newValue === "object" && newValue !== null;
|
|
76
|
-
|
|
77
|
-
// Case 1: Both are objects - merge directly
|
|
78
|
-
if (existingIsObject && newIsObject) {
|
|
79
|
-
const merged = { ...existingValue, ...newValue };
|
|
80
|
-
return context === "css" ? stringifyCSSTransform(merged) : merged;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Case 2: New is object, existing is string - parse existing and merge
|
|
84
|
-
if (newIsObject && existingIsString) {
|
|
85
|
-
const parsedExisting = parseCSSTransform(existingValue);
|
|
86
|
-
const merged = { ...parsedExisting, ...newValue };
|
|
87
|
-
return context === "css" ? stringifyCSSTransform(merged) : merged;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Case 3: New is string, existing is object - parse new and merge
|
|
91
|
-
if (newIsString && existingIsObject) {
|
|
92
|
-
const parsedNew = parseCSSTransform(newValue);
|
|
93
|
-
const merged = { ...existingValue, ...parsedNew };
|
|
94
|
-
return context === "css" ? stringifyCSSTransform(merged) : merged;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Case 4: Both are strings - parse both and merge
|
|
98
|
-
if (existingIsString && newIsString) {
|
|
99
|
-
const parsedExisting = parseCSSTransform(existingValue);
|
|
100
|
-
const parsedNew = parseCSSTransform(newValue);
|
|
101
|
-
const merged = { ...parsedExisting, ...parsedNew };
|
|
102
|
-
return context === "css" ? stringifyCSSTransform(merged) : merged;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Case 5: New is object, no existing or existing is none/null
|
|
106
|
-
if (newIsObject) {
|
|
107
|
-
return context === "css" ? stringifyCSSTransform(newValue) : newValue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Case 6: New is string, no existing or existing is none/null
|
|
111
|
-
if (newIsString) {
|
|
112
|
-
if (context === "css") {
|
|
113
|
-
return newValue; // Already a string
|
|
114
|
-
}
|
|
115
|
-
return parseCSSTransform(newValue); // Convert to object
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// For all other properties, simple replacement
|
|
120
|
-
return newValue;
|
|
121
|
-
};
|
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Style Controller System
|
|
3
|
-
*
|
|
4
|
-
* Solves CSS style manipulation problems in JavaScript:
|
|
5
|
-
*
|
|
6
|
-
* ## Main problems:
|
|
7
|
-
* 1. **Temporary style override**: Code wants to read current style, force another style,
|
|
8
|
-
* then restore original. With inline styles this is ugly and loses original info.
|
|
9
|
-
* 2. **Multiple code parts**: When different parts of code want to touch styles simultaneously,
|
|
10
|
-
* they step on each other (rare but happens).
|
|
11
|
-
* 3. **Transform composition**: CSS transforms are especially painful - you want to keep
|
|
12
|
-
* existing transforms but force specific parts (e.g., keep `rotate(45deg)` but override
|
|
13
|
-
* `translateX`). Native CSS overwrites the entire transform property.
|
|
14
|
-
*
|
|
15
|
-
* ## Solution:
|
|
16
|
-
* Controller pattern + Web Animations API to preserve inline styles. Code that sets
|
|
17
|
-
* inline styles expects to find them unchanged - we use animations for clean override:
|
|
18
|
-
*
|
|
19
|
-
* ```js
|
|
20
|
-
* const controller = createStyleController("myFeature");
|
|
21
|
-
*
|
|
22
|
-
* // Smart value conversion (100 → "100px", 45 → "45deg")
|
|
23
|
-
* controller.set(element, {
|
|
24
|
-
* transform: { translateX: 100, rotate: 45 }, // Individual transform properties
|
|
25
|
-
* opacity: 0.5
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* // Transform objects merged intelligently
|
|
29
|
-
* controller.set(element, {
|
|
30
|
-
* transform: { translateX: 50 } // Merges with existing transforms
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* // Get underlying value without this controller's influence
|
|
34
|
-
* const originalOpacity = controller.getUnderlyingValue(element, "opacity");
|
|
35
|
-
* const originalTranslateX = controller.getUnderlyingValue(element, "transform.translateX"); // Magic dot notation!
|
|
36
|
-
* const actualWidth = controller.getUnderlyingValue(element, "rect.width"); // Layout measurements
|
|
37
|
-
*
|
|
38
|
-
* controller.delete(element, "opacity"); // Only removes opacity, keeps transform
|
|
39
|
-
* controller.clear(element); // Removes all styles from this controller only
|
|
40
|
-
* controller.clearAll(); // Cleanup when done
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* **Key features:**
|
|
44
|
-
* - **Transform composition**: Intelligently merges transform components instead of overwriting
|
|
45
|
-
* - **Magic properties**: Access transform components with dot notation (e.g., "transform.translateX")
|
|
46
|
-
* - **Layout measurements**: Access actual rendered dimensions with rect.* (e.g., "rect.width")
|
|
47
|
-
* - **getUnderlyingValue()**: Read the "natural" value without this controller's influence
|
|
48
|
-
* - **Smart units**: Numeric values get appropriate units automatically (px, deg, unitless)
|
|
49
|
-
*
|
|
50
|
-
* **Transform limitations:**
|
|
51
|
-
* - **3D Transforms**: Complex `matrix3d()` transforms are preserved as-is and cannot be decomposed
|
|
52
|
-
* into individual properties. Only `matrix3d()` that represent simple 2D transforms are converted
|
|
53
|
-
* to object notation. Magic properties like "transform.rotateX" work only with explicit CSS functions,
|
|
54
|
-
* not with complex 3D matrices.
|
|
55
|
-
*
|
|
56
|
-
* Multiple controllers can safely manage the same element without conflicts.
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
import { mergeOneStyle, mergeStyles } from "./style_composition.js";
|
|
60
|
-
import { normalizeStyle, normalizeStyles } from "./style_parsing.js";
|
|
61
|
-
|
|
62
|
-
// Global registry to track which controllers are managing each element's styles
|
|
63
|
-
const elementControllerSetRegistry = new WeakMap(); // element -> Set<controller>
|
|
64
|
-
|
|
65
|
-
// Top-level helpers for controller attachment tracking
|
|
66
|
-
const onElementControllerAdded = (element, controller) => {
|
|
67
|
-
if (!elementControllerSetRegistry.has(element)) {
|
|
68
|
-
elementControllerSetRegistry.set(element, new Set());
|
|
69
|
-
}
|
|
70
|
-
const elementControllerSet = elementControllerSetRegistry.get(element);
|
|
71
|
-
elementControllerSet.add(controller);
|
|
72
|
-
};
|
|
73
|
-
const onElementControllerRemoved = (element, controller) => {
|
|
74
|
-
const elementControllerSet = elementControllerSetRegistry.get(element);
|
|
75
|
-
if (elementControllerSet) {
|
|
76
|
-
elementControllerSet.delete(controller);
|
|
77
|
-
|
|
78
|
-
// Clean up empty element registry
|
|
79
|
-
if (elementControllerSet.size === 0) {
|
|
80
|
-
elementControllerSetRegistry.delete(element);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export const createStyleController = (name = "anonymous") => {
|
|
86
|
-
// Store element data for this controller: element -> { styles, animation }
|
|
87
|
-
const elementWeakMap = new WeakMap();
|
|
88
|
-
|
|
89
|
-
const set = (element, stylesToSet) => {
|
|
90
|
-
if (!element || typeof element !== "object") {
|
|
91
|
-
throw new Error("Element must be a valid DOM element");
|
|
92
|
-
}
|
|
93
|
-
if (!stylesToSet || typeof stylesToSet !== "object") {
|
|
94
|
-
throw new Error("styles must be an object");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const elementData = elementWeakMap.get(element);
|
|
98
|
-
if (!elementData) {
|
|
99
|
-
const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
|
|
100
|
-
const animation = createAnimationForStyles(
|
|
101
|
-
element,
|
|
102
|
-
normalizedStylesToSet,
|
|
103
|
-
name,
|
|
104
|
-
);
|
|
105
|
-
elementWeakMap.set(element, {
|
|
106
|
-
styles: normalizedStylesToSet,
|
|
107
|
-
animation,
|
|
108
|
-
});
|
|
109
|
-
onElementControllerAdded(element, controller);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const { styles, animation } = elementData;
|
|
114
|
-
const mergedStyles = mergeStyles(styles, stylesToSet);
|
|
115
|
-
elementData.styles = mergedStyles;
|
|
116
|
-
updateAnimationStyles(animation, mergedStyles);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const get = (element, propertyName) => {
|
|
120
|
-
const elementData = elementWeakMap.get(element);
|
|
121
|
-
if (!elementData) {
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
const { styles } = elementData;
|
|
125
|
-
if (propertyName === undefined) {
|
|
126
|
-
return { ...styles };
|
|
127
|
-
}
|
|
128
|
-
if (propertyName.startsWith("transform.")) {
|
|
129
|
-
const transformProp = propertyName.slice("transform.".length);
|
|
130
|
-
return styles.transform?.[transformProp];
|
|
131
|
-
}
|
|
132
|
-
return styles[propertyName];
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const deleteMethod = (element, propertyName) => {
|
|
136
|
-
const elementData = elementWeakMap.get(element);
|
|
137
|
-
if (!elementData) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const { styles, animation } = elementData;
|
|
141
|
-
const hasStyle = Object.hasOwn(styles, propertyName);
|
|
142
|
-
if (!hasStyle) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
delete styles[propertyName];
|
|
146
|
-
const isEmpty = Object.keys(styles).length === 0;
|
|
147
|
-
// Clean up empty controller
|
|
148
|
-
if (isEmpty) {
|
|
149
|
-
animation.cancel();
|
|
150
|
-
elementWeakMap.delete(element);
|
|
151
|
-
onElementControllerRemoved(element, controller);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
updateAnimationStyles(animation, styles);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const commit = (element) => {
|
|
158
|
-
const elementData = elementWeakMap.get(element);
|
|
159
|
-
if (!elementData) {
|
|
160
|
-
return; // Nothing to commit on this element for this controller
|
|
161
|
-
}
|
|
162
|
-
const { styles, animation } = elementData;
|
|
163
|
-
// Cancel our animation permanently since we're committing styles to inline
|
|
164
|
-
// (Keep this BEFORE getComputedStyle to prevent computedStyle reading our animation styles)
|
|
165
|
-
animation.cancel();
|
|
166
|
-
// Now read the true underlying styles (without our animation influence)
|
|
167
|
-
const computedStyles = getComputedStyle(element);
|
|
168
|
-
// Convert controller styles to CSS and commit to inline styles
|
|
169
|
-
const cssStyles = normalizeStyles(styles, "css");
|
|
170
|
-
for (const [key, value] of Object.entries(cssStyles)) {
|
|
171
|
-
// Merge with existing computed styles for all properties
|
|
172
|
-
const existingValue = computedStyles[key];
|
|
173
|
-
element.style[key] = mergeOneStyle(existingValue, value, key, "css");
|
|
174
|
-
}
|
|
175
|
-
// Clear this controller's styles since they're now inline
|
|
176
|
-
elementWeakMap.delete(element);
|
|
177
|
-
// Clean up controller from element registry
|
|
178
|
-
onElementControllerRemoved(element, controller);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const clear = (element) => {
|
|
182
|
-
const elementData = elementWeakMap.get(element);
|
|
183
|
-
if (!elementData) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const { animation } = elementData;
|
|
187
|
-
animation.cancel();
|
|
188
|
-
elementWeakMap.delete(element);
|
|
189
|
-
onElementControllerRemoved(element, controller);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const getUnderlyingValue = (element, propertyName) => {
|
|
193
|
-
const elementControllerSet = elementControllerSetRegistry.get(element);
|
|
194
|
-
|
|
195
|
-
const normalizeValueForJs = (value) => {
|
|
196
|
-
// Use normalizeStyle to handle all property types including transform dot notation
|
|
197
|
-
return normalizeStyle(value, propertyName, "js");
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const getFromOtherControllers = () => {
|
|
201
|
-
if (!elementControllerSet || elementControllerSet.size <= 1) {
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
let resultValue;
|
|
206
|
-
for (const otherController of elementControllerSet) {
|
|
207
|
-
if (otherController === controller) continue;
|
|
208
|
-
const otherStyles = otherController.get(element);
|
|
209
|
-
if (propertyName in otherStyles) {
|
|
210
|
-
resultValue = mergeOneStyle(
|
|
211
|
-
resultValue,
|
|
212
|
-
otherStyles[propertyName],
|
|
213
|
-
propertyName,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Note: For CSS width/height properties, we can trust the values from other controllers
|
|
219
|
-
// because we assume box-sizing: border-box. If the element used content-box,
|
|
220
|
-
// the CSS width/height would differ from getBoundingClientRect() due to padding/borders,
|
|
221
|
-
// but since controllers set the final rendered size, the CSS value is what matters.
|
|
222
|
-
// For actual layout measurements, use rect.* properties instead.
|
|
223
|
-
return normalizeValueForJs(resultValue);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const getFromDOM = () => {
|
|
227
|
-
// Handle transform dot notation
|
|
228
|
-
if (propertyName.startsWith("transform.")) {
|
|
229
|
-
const transformValue = getComputedStyle(element).transform;
|
|
230
|
-
return normalizeValueForJs(transformValue);
|
|
231
|
-
}
|
|
232
|
-
// For all other CSS properties, use computed styles
|
|
233
|
-
const computedValue = getComputedStyle(element)[propertyName];
|
|
234
|
-
return normalizeValueForJs(computedValue);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const getFromDOMLayout = () => {
|
|
238
|
-
// For rect.* properties that reflect actual layout, always read from DOM
|
|
239
|
-
// These represent the actual rendered dimensions, bypassing any controller influence
|
|
240
|
-
if (propertyName === "rect.width") {
|
|
241
|
-
return element.getBoundingClientRect().width;
|
|
242
|
-
}
|
|
243
|
-
if (propertyName === "rect.height") {
|
|
244
|
-
return element.getBoundingClientRect().height;
|
|
245
|
-
}
|
|
246
|
-
if (propertyName === "rect.left") {
|
|
247
|
-
return element.getBoundingClientRect().left;
|
|
248
|
-
}
|
|
249
|
-
if (propertyName === "rect.top") {
|
|
250
|
-
return element.getBoundingClientRect().top;
|
|
251
|
-
}
|
|
252
|
-
if (propertyName === "rect.right") {
|
|
253
|
-
return element.getBoundingClientRect().right;
|
|
254
|
-
}
|
|
255
|
-
if (propertyName === "rect.bottom") {
|
|
256
|
-
return element.getBoundingClientRect().bottom;
|
|
257
|
-
}
|
|
258
|
-
return undefined;
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
const getWhileDisablingThisController = (fn) => {
|
|
262
|
-
const elementData = elementWeakMap.get(element);
|
|
263
|
-
if (!elementData) {
|
|
264
|
-
return fn();
|
|
265
|
-
}
|
|
266
|
-
const { styles, animation } = elementData;
|
|
267
|
-
// Temporarily cancel our animation to read underlying value
|
|
268
|
-
animation.cancel();
|
|
269
|
-
const underlyingValue = fn();
|
|
270
|
-
// Restore our animation
|
|
271
|
-
elementData.animation = createAnimationForStyles(element, styles, name);
|
|
272
|
-
return underlyingValue;
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
if (typeof propertyName === "function") {
|
|
276
|
-
return getWhileDisablingThisController(propertyName);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Handle computed layout properties (rect.*) - always read from DOM, bypass controllers
|
|
280
|
-
if (propertyName.startsWith("rect.")) {
|
|
281
|
-
return getWhileDisablingThisController(getFromDOMLayout);
|
|
282
|
-
}
|
|
283
|
-
if (!elementControllerSet || !elementControllerSet.has(controller)) {
|
|
284
|
-
// This controller is not applied, just read current value
|
|
285
|
-
return getFromDOM();
|
|
286
|
-
}
|
|
287
|
-
// Check if other controllers would provide this style
|
|
288
|
-
const valueFromOtherControllers = getFromOtherControllers();
|
|
289
|
-
if (valueFromOtherControllers !== undefined) {
|
|
290
|
-
return valueFromOtherControllers;
|
|
291
|
-
}
|
|
292
|
-
return getWhileDisablingThisController(getFromDOM);
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const clearAll = () => {
|
|
296
|
-
// Remove this controller from all elements and clean up animations
|
|
297
|
-
for (const [
|
|
298
|
-
element,
|
|
299
|
-
elementControllerSet,
|
|
300
|
-
] of elementControllerSetRegistry) {
|
|
301
|
-
if (!elementControllerSet.has(controller)) {
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
const elementData = elementWeakMap.get(element);
|
|
305
|
-
if (!elementData) {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
const { animation } = elementData;
|
|
309
|
-
animation.cancel();
|
|
310
|
-
elementWeakMap.delete(element);
|
|
311
|
-
onElementControllerRemoved(element, controller);
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
const controller = {
|
|
315
|
-
name,
|
|
316
|
-
set,
|
|
317
|
-
get,
|
|
318
|
-
delete: deleteMethod,
|
|
319
|
-
getUnderlyingValue,
|
|
320
|
-
commit,
|
|
321
|
-
clear,
|
|
322
|
-
clearAll,
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
return controller;
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const createAnimationForStyles = (element, styles, id) => {
|
|
329
|
-
const cssStylesToSet = normalizeStyles(styles, "css");
|
|
330
|
-
const animation = element.animate([cssStylesToSet], {
|
|
331
|
-
duration: 0,
|
|
332
|
-
fill: "forwards",
|
|
333
|
-
});
|
|
334
|
-
animation.id = id; // Set a debug name for this animation
|
|
335
|
-
animation.play();
|
|
336
|
-
animation.pause();
|
|
337
|
-
return animation; // Return the created animation
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const updateAnimationStyles = (animation, styles) => {
|
|
341
|
-
const cssStyles = normalizeStyles(styles, "css");
|
|
342
|
-
animation.effect.setKeyframes([cssStyles]);
|
|
343
|
-
animation.play();
|
|
344
|
-
animation.pause();
|
|
345
|
-
};
|