@jsenv/dom 0.6.0 → 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.
- package/dist/jsenv_dom.js +262 -330
- 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 -1491
- package/src/utils.js +0 -69
- package/src/value_effect.js +0 -35
|
@@ -1,659 +0,0 @@
|
|
|
1
|
-
import { getElementSignature } from "../../element_signature.js";
|
|
2
|
-
import {
|
|
3
|
-
addScrollToRect,
|
|
4
|
-
getScrollRelativeRect,
|
|
5
|
-
} from "../../position/dom_coords.js";
|
|
6
|
-
import { setupConstraintFeedbackLine } from "./constraint_feedback_line.js";
|
|
7
|
-
import { setupDragDebugMarkers } from "./drag_debug_markers.js";
|
|
8
|
-
|
|
9
|
-
const CONSOLE_DEBUG_BOUNDS = true;
|
|
10
|
-
const CONSOLE_DEBUG_OBSTACLES = false;
|
|
11
|
-
|
|
12
|
-
export const initDragConstraints = (
|
|
13
|
-
dragGesture,
|
|
14
|
-
{
|
|
15
|
-
areaConstraint,
|
|
16
|
-
obstaclesContainer,
|
|
17
|
-
obstacleAttributeName,
|
|
18
|
-
showConstraintFeedbackLine,
|
|
19
|
-
showDebugMarkers,
|
|
20
|
-
referenceElement,
|
|
21
|
-
},
|
|
22
|
-
) => {
|
|
23
|
-
const dragGestureName = dragGesture.gestureInfo.name;
|
|
24
|
-
const direction = dragGesture.gestureInfo.direction;
|
|
25
|
-
const scrollContainer = dragGesture.gestureInfo.scrollContainer;
|
|
26
|
-
const leftAtGrab = dragGesture.gestureInfo.leftAtGrab;
|
|
27
|
-
const topAtGrab = dragGesture.gestureInfo.topAtGrab;
|
|
28
|
-
|
|
29
|
-
const constraintFunctions = [];
|
|
30
|
-
const addConstraint = (constraint) => {
|
|
31
|
-
constraintFunctions.push(constraint);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
if (showConstraintFeedbackLine) {
|
|
35
|
-
const constraintFeedbackLine = setupConstraintFeedbackLine(dragGesture);
|
|
36
|
-
dragGesture.addDragCallback((gestureInfo) => {
|
|
37
|
-
constraintFeedbackLine.onDrag(gestureInfo);
|
|
38
|
-
});
|
|
39
|
-
dragGesture.addReleaseCallback(() => {
|
|
40
|
-
constraintFeedbackLine.onRelease();
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
let dragDebugMarkers;
|
|
44
|
-
if (showDebugMarkers) {
|
|
45
|
-
dragDebugMarkers = setupDragDebugMarkers(dragGesture, {
|
|
46
|
-
referenceElement,
|
|
47
|
-
});
|
|
48
|
-
dragGesture.addReleaseCallback(() => {
|
|
49
|
-
dragDebugMarkers.onRelease();
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
area: {
|
|
54
|
-
const areaConstraintFunction = createAreaConstraint(areaConstraint, {
|
|
55
|
-
scrollContainer,
|
|
56
|
-
});
|
|
57
|
-
if (areaConstraintFunction) {
|
|
58
|
-
addConstraint(areaConstraintFunction);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
obstacles: {
|
|
62
|
-
if (!obstacleAttributeName || !obstaclesContainer) {
|
|
63
|
-
break obstacles;
|
|
64
|
-
}
|
|
65
|
-
const obstacleConstraintFunctions =
|
|
66
|
-
createObstacleConstraintsFromQuerySelector(obstaclesContainer, {
|
|
67
|
-
obstacleAttributeName,
|
|
68
|
-
gestureInfo: dragGesture.gestureInfo,
|
|
69
|
-
isDraggedElementSticky: false,
|
|
70
|
-
// isStickyLeftOrHasStickyLeftAttr || isStickyTopOrHasStickyTopAttr,
|
|
71
|
-
});
|
|
72
|
-
for (const obstacleConstraintFunction of obstacleConstraintFunctions) {
|
|
73
|
-
addConstraint(obstacleConstraintFunction);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const applyConstraints = (
|
|
78
|
-
layoutRequested,
|
|
79
|
-
currentLayout,
|
|
80
|
-
limitLayout,
|
|
81
|
-
{
|
|
82
|
-
elementWidth,
|
|
83
|
-
elementHeight,
|
|
84
|
-
scrollArea,
|
|
85
|
-
scrollport,
|
|
86
|
-
hasCrossedScrollportLeftOnce,
|
|
87
|
-
hasCrossedScrollportTopOnce,
|
|
88
|
-
autoScrollArea,
|
|
89
|
-
dragEvent,
|
|
90
|
-
},
|
|
91
|
-
) => {
|
|
92
|
-
if (constraintFunctions.length === 0) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const elementCurrentLeft = currentLayout.left;
|
|
97
|
-
const elementCurrentTop = currentLayout.top;
|
|
98
|
-
const elementLeftRequested = layoutRequested.left;
|
|
99
|
-
const elementTopRequested = layoutRequested.top;
|
|
100
|
-
let elementLeft = elementLeftRequested;
|
|
101
|
-
let elementTop = elementTopRequested;
|
|
102
|
-
|
|
103
|
-
const constraintInitParams = {
|
|
104
|
-
leftAtGrab,
|
|
105
|
-
topAtGrab,
|
|
106
|
-
left: elementCurrentLeft,
|
|
107
|
-
top: elementCurrentTop,
|
|
108
|
-
right: elementCurrentLeft + elementWidth,
|
|
109
|
-
bottom: elementCurrentTop + elementHeight,
|
|
110
|
-
width: elementWidth,
|
|
111
|
-
height: elementHeight,
|
|
112
|
-
scrollContainer,
|
|
113
|
-
scrollArea,
|
|
114
|
-
scrollport,
|
|
115
|
-
autoScrollArea,
|
|
116
|
-
dragGestureName,
|
|
117
|
-
dragEvent,
|
|
118
|
-
};
|
|
119
|
-
const constraints = constraintFunctions.map((fn) =>
|
|
120
|
-
fn(constraintInitParams),
|
|
121
|
-
);
|
|
122
|
-
// Development safeguards: detect impossible/illogical constraints
|
|
123
|
-
if (import.meta.dev) {
|
|
124
|
-
validateConstraints(constraints, constraintInitParams);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const logConstraintEnforcement = (axis, constraint) => {
|
|
128
|
-
if (!CONSOLE_DEBUG_BOUNDS && constraint.type === "bounds") {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (!CONSOLE_DEBUG_OBSTACLES && constraint.type === "obstacle") {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const requested =
|
|
135
|
-
axis === "x" ? elementLeftRequested : elementTopRequested;
|
|
136
|
-
const constrained = axis === "x" ? elementLeft : elementTop;
|
|
137
|
-
const action = constrained > requested ? "increased" : "capped";
|
|
138
|
-
const property = axis === "x" ? "left" : "top";
|
|
139
|
-
console.debug(
|
|
140
|
-
`Drag by ${dragEvent.type}: ${property} ${action} from ${requested.toFixed(2)} to ${constrained.toFixed(2)} by ${constraint.type}:${constraint.name}`,
|
|
141
|
-
constraint.element,
|
|
142
|
-
);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// Apply each constraint in sequence, accumulating their effects
|
|
146
|
-
// This allows multiple constraints to work together (e.g., bounds + obstacles)
|
|
147
|
-
for (const constraint of constraints) {
|
|
148
|
-
const result = constraint.apply({
|
|
149
|
-
// each constraint works with scroll included coordinates
|
|
150
|
-
// and coordinates we provide here includes the scroll of the container
|
|
151
|
-
left: elementLeft,
|
|
152
|
-
top: elementTop,
|
|
153
|
-
right: elementLeft + elementWidth,
|
|
154
|
-
bottom: elementTop + elementHeight,
|
|
155
|
-
width: elementWidth,
|
|
156
|
-
height: elementHeight,
|
|
157
|
-
currentLeft: elementCurrentLeft,
|
|
158
|
-
currentTop: elementCurrentTop,
|
|
159
|
-
scrollport,
|
|
160
|
-
hasCrossedScrollportLeftOnce,
|
|
161
|
-
hasCrossedScrollportTopOnce,
|
|
162
|
-
});
|
|
163
|
-
if (!result) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
const [elementLeftConstrained, elementTopConstrained] = result;
|
|
167
|
-
if (direction.x && elementLeftConstrained !== elementLeft) {
|
|
168
|
-
elementLeft = elementLeftConstrained;
|
|
169
|
-
logConstraintEnforcement("x", constraint);
|
|
170
|
-
}
|
|
171
|
-
if (direction.y && elementTopConstrained !== elementTop) {
|
|
172
|
-
elementTop = elementTopConstrained;
|
|
173
|
-
logConstraintEnforcement("y", constraint);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (dragDebugMarkers) {
|
|
178
|
-
dragDebugMarkers.onConstraints(constraints, {
|
|
179
|
-
left: elementLeft,
|
|
180
|
-
top: elementTop,
|
|
181
|
-
right: elementLeft + elementWidth,
|
|
182
|
-
bottom: elementTop + elementHeight,
|
|
183
|
-
elementWidth,
|
|
184
|
-
elementHeight,
|
|
185
|
-
scrollport,
|
|
186
|
-
autoScrollArea,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const leftModified = elementLeft !== elementLeftRequested;
|
|
191
|
-
const topModified = elementTop !== elementTopRequested;
|
|
192
|
-
if (!leftModified && !topModified) {
|
|
193
|
-
if (CONSOLE_DEBUG_BOUNDS || CONSOLE_DEBUG_OBSTACLES) {
|
|
194
|
-
console.debug(
|
|
195
|
-
`Drag by ${dragEvent.type}: no constraint enforcement needed (${elementLeftRequested.toFixed(2)}, ${elementTopRequested.toFixed(2)})`,
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
limitLayout(elementLeft, elementTop);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
return { applyConstraints };
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const createAreaConstraint = (areaConstraint, { scrollContainer }) => {
|
|
208
|
-
if (!areaConstraint || areaConstraint === "none") {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
if (areaConstraint === "scrollport") {
|
|
212
|
-
const scrollportConstraintFunction = ({ scrollport }) => {
|
|
213
|
-
return createBoundConstraint(scrollport, {
|
|
214
|
-
element: scrollContainer,
|
|
215
|
-
name: "scrollport",
|
|
216
|
-
});
|
|
217
|
-
};
|
|
218
|
-
return scrollportConstraintFunction;
|
|
219
|
-
}
|
|
220
|
-
if (areaConstraint === "scroll") {
|
|
221
|
-
const scrollAreaConstraintFunction = ({ scrollArea }) => {
|
|
222
|
-
return createBoundConstraint(scrollArea, {
|
|
223
|
-
element: scrollContainer,
|
|
224
|
-
name: "scroll_area",
|
|
225
|
-
});
|
|
226
|
-
};
|
|
227
|
-
return scrollAreaConstraintFunction;
|
|
228
|
-
}
|
|
229
|
-
if (typeof areaConstraint === "function") {
|
|
230
|
-
const dynamicAreaConstraintFunction = (params) => {
|
|
231
|
-
const bounds = areaConstraint(params);
|
|
232
|
-
return createBoundConstraint(bounds, {
|
|
233
|
-
name: "dynamic_area",
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
return dynamicAreaConstraintFunction;
|
|
237
|
-
}
|
|
238
|
-
if (typeof areaConstraint === "object") {
|
|
239
|
-
const { left, top, right, bottom } = areaConstraint;
|
|
240
|
-
const turnSidePropertyInToGetter = (value, side) => {
|
|
241
|
-
if (value === "scrollport") {
|
|
242
|
-
return ({ scrollport }) => scrollport[side];
|
|
243
|
-
}
|
|
244
|
-
if (value === "scroll") {
|
|
245
|
-
return ({ scrollArea }) => scrollArea[side];
|
|
246
|
-
}
|
|
247
|
-
if (typeof value === "function") {
|
|
248
|
-
return value;
|
|
249
|
-
}
|
|
250
|
-
if (value === undefined) {
|
|
251
|
-
// defaults to scrollport
|
|
252
|
-
return ({ scrollport }) => scrollport[side];
|
|
253
|
-
}
|
|
254
|
-
return () => value;
|
|
255
|
-
};
|
|
256
|
-
const getLeft = turnSidePropertyInToGetter(left, "left");
|
|
257
|
-
const getRight = turnSidePropertyInToGetter(right, "right");
|
|
258
|
-
const getTop = turnSidePropertyInToGetter(top, "top");
|
|
259
|
-
const getBottom = turnSidePropertyInToGetter(bottom, "bottom");
|
|
260
|
-
|
|
261
|
-
const dynamicAreaConstraintFunction = (params) => {
|
|
262
|
-
const bounds = {
|
|
263
|
-
left: getLeft(params),
|
|
264
|
-
right: getRight(params),
|
|
265
|
-
top: getTop(params),
|
|
266
|
-
bottom: getBottom(params),
|
|
267
|
-
};
|
|
268
|
-
return createBoundConstraint(bounds, {
|
|
269
|
-
name: "dynamic_area",
|
|
270
|
-
});
|
|
271
|
-
};
|
|
272
|
-
return dynamicAreaConstraintFunction;
|
|
273
|
-
}
|
|
274
|
-
console.warn(
|
|
275
|
-
`Unknown areaConstraint value: ${areaConstraint}. Expected "scrollport", "scroll", "none", an object with boundary definitions, or a function returning boundary definitions.`,
|
|
276
|
-
);
|
|
277
|
-
return null;
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const createObstacleConstraintsFromQuerySelector = (
|
|
281
|
-
scrollableElement,
|
|
282
|
-
{ obstacleAttributeName, gestureInfo, isDraggedElementSticky = false },
|
|
283
|
-
) => {
|
|
284
|
-
const dragGestureName = gestureInfo.name;
|
|
285
|
-
const obstacles = scrollableElement.querySelectorAll(
|
|
286
|
-
`[${obstacleAttributeName}]`,
|
|
287
|
-
);
|
|
288
|
-
const obstacleConstraintFunctions = [];
|
|
289
|
-
for (const obstacle of obstacles) {
|
|
290
|
-
if (obstacle.closest("[data-drag-ignore]")) {
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
if (dragGestureName) {
|
|
294
|
-
const obstacleAttributeValue = obstacle.getAttribute(
|
|
295
|
-
obstacleAttributeName,
|
|
296
|
-
);
|
|
297
|
-
if (obstacleAttributeValue) {
|
|
298
|
-
const obstacleNames = obstacleAttributeValue.split(",");
|
|
299
|
-
const found = obstacleNames.some(
|
|
300
|
-
(obstacleName) =>
|
|
301
|
-
obstacleName.trim().toLowerCase() === dragGestureName.toLowerCase(),
|
|
302
|
-
);
|
|
303
|
-
if (!found) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
obstacleConstraintFunctions.push(
|
|
310
|
-
({ hasCrossedVisibleAreaLeftOnce, hasCrossedVisibleAreaTopOnce }) => {
|
|
311
|
-
// Only apply the "before crossing visible area" logic when dragging sticky elements
|
|
312
|
-
// Non-sticky elements should be able to cross sticky obstacles while stuck regardless of visible area crossing
|
|
313
|
-
const useOriginalPositionEvenIfSticky = isDraggedElementSticky
|
|
314
|
-
? !hasCrossedVisibleAreaLeftOnce && !hasCrossedVisibleAreaTopOnce
|
|
315
|
-
: true;
|
|
316
|
-
|
|
317
|
-
const obstacleScrollRelativeRect = getScrollRelativeRect(
|
|
318
|
-
obstacle,
|
|
319
|
-
scrollableElement,
|
|
320
|
-
{
|
|
321
|
-
useOriginalPositionEvenIfSticky,
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
let obstacleBounds;
|
|
325
|
-
if (
|
|
326
|
-
useOriginalPositionEvenIfSticky &&
|
|
327
|
-
obstacleScrollRelativeRect.isSticky
|
|
328
|
-
) {
|
|
329
|
-
obstacleBounds = obstacleScrollRelativeRect;
|
|
330
|
-
} else {
|
|
331
|
-
obstacleBounds = addScrollToRect(obstacleScrollRelativeRect);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// obstacleBounds are already in scrollable-relative coordinates, no conversion needed
|
|
335
|
-
const obstacleObject = createObstacleContraint(obstacleBounds, {
|
|
336
|
-
name: `${obstacleBounds.isSticky ? "sticky " : ""}obstacle (${getElementSignature(obstacle)})`,
|
|
337
|
-
element: obstacle,
|
|
338
|
-
});
|
|
339
|
-
return obstacleObject;
|
|
340
|
-
},
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
return obstacleConstraintFunctions;
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const createBoundConstraint = (bounds, { name, element } = {}) => {
|
|
347
|
-
const leftBound = bounds.left;
|
|
348
|
-
const rightBound = bounds.right;
|
|
349
|
-
const topBound = bounds.top;
|
|
350
|
-
const bottomBound = bounds.bottom;
|
|
351
|
-
|
|
352
|
-
const apply = ({ left, top, right, bottom, width, height }) => {
|
|
353
|
-
let leftConstrained = left;
|
|
354
|
-
let topConstrained = top;
|
|
355
|
-
// Left boundary: element's left edge should not go before leftBound
|
|
356
|
-
if (leftBound !== undefined && left < leftBound) {
|
|
357
|
-
leftConstrained = leftBound;
|
|
358
|
-
}
|
|
359
|
-
// Right boundary: element's right edge should not go past rightBound
|
|
360
|
-
if (rightBound !== undefined && right > rightBound) {
|
|
361
|
-
leftConstrained = rightBound - width;
|
|
362
|
-
}
|
|
363
|
-
// Top boundary: element's top edge should not go before topBound
|
|
364
|
-
if (topBound !== undefined && top < topBound) {
|
|
365
|
-
topConstrained = topBound;
|
|
366
|
-
}
|
|
367
|
-
// Bottom boundary: element's bottom edge should not go past bottomBound
|
|
368
|
-
if (bottomBound !== undefined && bottom > bottomBound) {
|
|
369
|
-
topConstrained = bottomBound - height;
|
|
370
|
-
}
|
|
371
|
-
return [leftConstrained, topConstrained];
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
type: "bounds",
|
|
376
|
-
name,
|
|
377
|
-
apply,
|
|
378
|
-
element,
|
|
379
|
-
bounds,
|
|
380
|
-
};
|
|
381
|
-
};
|
|
382
|
-
const createObstacleContraint = (bounds, { element, name }) => {
|
|
383
|
-
const leftBound = bounds.left;
|
|
384
|
-
const rightBound = bounds.right;
|
|
385
|
-
const topBound = bounds.top;
|
|
386
|
-
const bottomBound = bounds.bottom;
|
|
387
|
-
const leftBoundRounded = roundForConstraints(leftBound);
|
|
388
|
-
const rightBoundRounded = roundForConstraints(rightBound);
|
|
389
|
-
const topBoundRounded = roundForConstraints(topBound);
|
|
390
|
-
const bottomBoundRounded = roundForConstraints(bottomBound);
|
|
391
|
-
|
|
392
|
-
const apply = ({
|
|
393
|
-
left,
|
|
394
|
-
top,
|
|
395
|
-
right,
|
|
396
|
-
bottom,
|
|
397
|
-
width,
|
|
398
|
-
height,
|
|
399
|
-
currentLeft,
|
|
400
|
-
currentTop,
|
|
401
|
-
}) => {
|
|
402
|
-
// Simple collision detection: check where element is and prevent movement into obstacle
|
|
403
|
-
{
|
|
404
|
-
// Determine current position relative to obstacle
|
|
405
|
-
const currentLeftRounded = roundForConstraints(currentLeft);
|
|
406
|
-
const currentRightRounded = roundForConstraints(currentLeft + width);
|
|
407
|
-
const currentTopRounded = roundForConstraints(currentTop);
|
|
408
|
-
const currentBottomRounded = roundForConstraints(currentTop + height);
|
|
409
|
-
const isOnTheLeft = currentRightRounded <= leftBoundRounded;
|
|
410
|
-
const isOnTheRight = currentLeftRounded >= rightBoundRounded;
|
|
411
|
-
const isAbove = currentBottomRounded <= topBoundRounded;
|
|
412
|
-
const isBelow = currentTopRounded >= bottomBoundRounded;
|
|
413
|
-
// Debug logging to understand element position
|
|
414
|
-
if (CONSOLE_DEBUG_OBSTACLES) {
|
|
415
|
-
const position = isOnTheLeft
|
|
416
|
-
? "left"
|
|
417
|
-
: isOnTheRight
|
|
418
|
-
? "right"
|
|
419
|
-
: isAbove
|
|
420
|
-
? "above"
|
|
421
|
-
: isBelow
|
|
422
|
-
? "below"
|
|
423
|
-
: "overlapping";
|
|
424
|
-
console.log(`Element position relative to obstacle: ${position}`);
|
|
425
|
-
console.log(
|
|
426
|
-
`Element current position: left=${currentLeftRounded}, right=${currentRightRounded}, top=${currentTopRounded}, bottom=${currentBottomRounded}`,
|
|
427
|
-
);
|
|
428
|
-
console.log(
|
|
429
|
-
`Obstacle position: leftBound=${leftBound}, rightBound=${rightBound}, topBound=${topBound}, bottomBound=${bottomBound}`,
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// If element is on the left, apply X constraint to prevent moving right into obstacle
|
|
434
|
-
if (isOnTheLeft) {
|
|
435
|
-
const wouldHaveYOverlap = top < bottomBound && bottom > topBound;
|
|
436
|
-
if (wouldHaveYOverlap) {
|
|
437
|
-
const maxLeft = leftBound - width;
|
|
438
|
-
if (left > maxLeft) {
|
|
439
|
-
return [maxLeft, top];
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
// If element is on the right, apply X constraint to prevent moving left into obstacle
|
|
444
|
-
else if (isOnTheRight) {
|
|
445
|
-
const wouldHaveYOverlap = top < bottomBound && bottom > topBound;
|
|
446
|
-
if (wouldHaveYOverlap) {
|
|
447
|
-
const minLeft = rightBound;
|
|
448
|
-
if (left < minLeft) {
|
|
449
|
-
return [minLeft, top];
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
// If element is above, apply Y constraint to prevent moving down into obstacle
|
|
454
|
-
else if (isAbove) {
|
|
455
|
-
const wouldHaveXOverlap = left < rightBound && right > leftBound;
|
|
456
|
-
if (wouldHaveXOverlap) {
|
|
457
|
-
const maxTop = topBound - height;
|
|
458
|
-
if (top > maxTop) {
|
|
459
|
-
return [left, maxTop];
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// If element is below, apply Y constraint to prevent moving up into obstacle
|
|
464
|
-
else if (isBelow) {
|
|
465
|
-
const wouldHaveXOverlap = left < rightBound && right > leftBound;
|
|
466
|
-
if (wouldHaveXOverlap) {
|
|
467
|
-
const minTop = bottomBound;
|
|
468
|
-
if (top < minTop) {
|
|
469
|
-
return [left, minTop];
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Element is overlapping with obstacle - push it out in the direction of least resistance
|
|
476
|
-
// Calculate distances to push element out in each direction
|
|
477
|
-
const distanceToLeft = right - leftBound; // Distance to push left
|
|
478
|
-
const distanceToRight = rightBound - left; // Distance to push right
|
|
479
|
-
const distanceToTop = bottom - topBound; // Distance to push up
|
|
480
|
-
const distanceToBottom = bottomBound - top; // Distance to push down
|
|
481
|
-
// Find the minimum distance (direction of least resistance)
|
|
482
|
-
const minDistance = Math.min(
|
|
483
|
-
distanceToLeft,
|
|
484
|
-
distanceToRight,
|
|
485
|
-
distanceToTop,
|
|
486
|
-
distanceToBottom,
|
|
487
|
-
);
|
|
488
|
-
if (minDistance === distanceToLeft) {
|
|
489
|
-
// Push left: element should not go past leftBound - elementWidth
|
|
490
|
-
const maxLeft = leftBound - width;
|
|
491
|
-
if (left > maxLeft) {
|
|
492
|
-
return [maxLeft, top];
|
|
493
|
-
}
|
|
494
|
-
} else if (minDistance === distanceToRight) {
|
|
495
|
-
// Push right: element should not go before rightBound
|
|
496
|
-
const minLeft = rightBound;
|
|
497
|
-
if (left < minLeft) {
|
|
498
|
-
return [minLeft, top];
|
|
499
|
-
}
|
|
500
|
-
} else if (minDistance === distanceToTop) {
|
|
501
|
-
// Push up: element should not go past topBound - elementHeight
|
|
502
|
-
const maxTop = topBound - height;
|
|
503
|
-
if (top > maxTop) {
|
|
504
|
-
return [left, maxTop];
|
|
505
|
-
}
|
|
506
|
-
} else if (minDistance === distanceToBottom) {
|
|
507
|
-
// Push down: element should not go before bottomBound
|
|
508
|
-
const minTop = bottomBound;
|
|
509
|
-
if (top < minTop) {
|
|
510
|
-
return [left, minTop];
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
return null;
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
return {
|
|
518
|
-
type: "obstacle",
|
|
519
|
-
name,
|
|
520
|
-
apply,
|
|
521
|
-
element,
|
|
522
|
-
bounds,
|
|
523
|
-
};
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* Rounds coordinates to prevent floating point precision issues in constraint calculations.
|
|
528
|
-
*
|
|
529
|
-
* This is critical for obstacle detection because:
|
|
530
|
-
* 1. Boundary detection relies on precise comparisons (e.g., elementRight <= obstacleLeft)
|
|
531
|
-
* 2. Floating point arithmetic can produce values like 149.99999999 instead of 150
|
|
532
|
-
* 3. This causes incorrect boundary classifications (element appears "on left" when it should be "overlapping")
|
|
533
|
-
*
|
|
534
|
-
* Scroll events are more susceptible to this issue because:
|
|
535
|
-
* - Mouse events use integer pixel coordinates from the DOM (e.g., clientX: 150)
|
|
536
|
-
* - Scroll events use element.scrollLeft which can have sub-pixel values from CSS transforms, zoom, etc.
|
|
537
|
-
* - Scroll compensation calculations (scrollDelta * ratios) amplify floating point errors
|
|
538
|
-
* - Multiple scroll events accumulate these errors over time
|
|
539
|
-
*
|
|
540
|
-
* Using 2-decimal precision maintains smooth sub-pixel positioning while ensuring
|
|
541
|
-
* reliable boundary detection for constraint systems.
|
|
542
|
-
*/
|
|
543
|
-
const roundForConstraints = (value) => {
|
|
544
|
-
return Math.round(value * 100) / 100;
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Validates constraints for logical consistency and reports issues during development.
|
|
549
|
-
* Helps catch configuration errors like inappropriate obstacle assignments.
|
|
550
|
-
*/
|
|
551
|
-
const validateConstraints = (
|
|
552
|
-
constraints,
|
|
553
|
-
{ elementWidth, elementHeight, name: dragName },
|
|
554
|
-
) => {
|
|
555
|
-
const boundsConstraints = constraints.filter((c) => c.type === "bounds");
|
|
556
|
-
const obstacleConstraints = constraints.filter((c) => c.type === "obstacle");
|
|
557
|
-
|
|
558
|
-
// Check for impossible bounds constraints
|
|
559
|
-
boundsConstraints.forEach((bounds) => {
|
|
560
|
-
if (bounds.left >= bounds.right) {
|
|
561
|
-
console.warn(
|
|
562
|
-
`Impossible bounds constraint: left (${bounds.left}) >= right (${bounds.right})`,
|
|
563
|
-
{ constraint: bounds, dragName, element: bounds.element },
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
if (bounds.top >= bounds.bottom) {
|
|
567
|
-
console.warn(
|
|
568
|
-
`Impossible bounds constraint: top (${bounds.top}) >= bottom (${bounds.bottom})`,
|
|
569
|
-
{ constraint: bounds, dragName, element: bounds.element },
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const availableWidth = bounds.right - bounds.left;
|
|
574
|
-
const availableHeight = bounds.bottom - bounds.top;
|
|
575
|
-
const roundedElementWidth = elementWidth;
|
|
576
|
-
const roundedElementHeight = elementHeight;
|
|
577
|
-
|
|
578
|
-
// Math.round because some values comes from getBoundingClientRect() (floats)
|
|
579
|
-
// and some from scrollWidth/Height (integers) causing precision issues
|
|
580
|
-
if (
|
|
581
|
-
Math.round(availableWidth) < Math.round(roundedElementWidth) &&
|
|
582
|
-
availableWidth >= 0
|
|
583
|
-
) {
|
|
584
|
-
console.warn(
|
|
585
|
-
`Bounds constraint too narrow: available width (${availableWidth.toFixed(2)}) < element width (${roundedElementWidth.toFixed(2)})`,
|
|
586
|
-
{ constraint: bounds, dragName, element: bounds.element },
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
if (
|
|
590
|
-
Math.round(availableHeight) < Math.round(roundedElementHeight) &&
|
|
591
|
-
availableHeight >= 0
|
|
592
|
-
) {
|
|
593
|
-
console.warn(
|
|
594
|
-
`Bounds constraint too short: available height (${availableHeight.toFixed(2)}) < element height (${roundedElementHeight.toFixed(2)})`,
|
|
595
|
-
{ constraint: bounds, dragName, element: bounds.element },
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// Check for problematic obstacle overlaps and inappropriate obstacle assignments
|
|
601
|
-
obstacleConstraints.forEach((obstacle, index) => {
|
|
602
|
-
// Check for impossible obstacle geometry
|
|
603
|
-
if (
|
|
604
|
-
obstacle.bounds.left > obstacle.bounds.right ||
|
|
605
|
-
obstacle.bounds.top > obstacle.bounds.bottom
|
|
606
|
-
) {
|
|
607
|
-
console.warn(
|
|
608
|
-
`Impossible obstacle geometry: left=${obstacle.bounds.left}, right=${obstacle.bounds.right}, top=${obstacle.bounds.top}, bottom=${obstacle.bounds.bottom}`,
|
|
609
|
-
{ constraint: obstacle, dragName, element: obstacle.element },
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Check for obstacles that completely block movement in all directions
|
|
614
|
-
boundsConstraints.forEach((bounds) => {
|
|
615
|
-
const obstacleWidth = obstacle.bounds.right - obstacle.bounds.left;
|
|
616
|
-
const obstacleHeight = obstacle.bounds.bottom - obstacle.bounds.top;
|
|
617
|
-
const boundsWidth = bounds.right - bounds.left;
|
|
618
|
-
const boundsHeight = bounds.bottom - bounds.top;
|
|
619
|
-
|
|
620
|
-
if (obstacleWidth >= boundsWidth && obstacleHeight >= boundsHeight) {
|
|
621
|
-
console.warn(
|
|
622
|
-
`Obstacle completely blocks bounds area: obstacle (${obstacleWidth.toFixed(2)}×${obstacleHeight.toFixed(2)}) >= bounds (${boundsWidth.toFixed(2)}×${boundsHeight.toFixed(2)})`,
|
|
623
|
-
{
|
|
624
|
-
obstacle,
|
|
625
|
-
bounds,
|
|
626
|
-
dragName,
|
|
627
|
-
obstacleElement: obstacle.element,
|
|
628
|
-
boundsElement: bounds.element,
|
|
629
|
-
},
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
// Check for overlapping obstacles that might create conflicting constraints
|
|
635
|
-
obstacleConstraints.forEach((otherObstacle, otherIndex) => {
|
|
636
|
-
if (index >= otherIndex) return; // Avoid duplicate checks
|
|
637
|
-
|
|
638
|
-
const hasOverlap = !(
|
|
639
|
-
obstacle.bounds.right <= otherObstacle.bounds.left ||
|
|
640
|
-
obstacle.bounds.left >= otherObstacle.bounds.right ||
|
|
641
|
-
obstacle.bounds.bottom <= otherObstacle.bounds.top ||
|
|
642
|
-
obstacle.bounds.top >= otherObstacle.bounds.bottom
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
if (hasOverlap) {
|
|
646
|
-
console.warn(
|
|
647
|
-
`Overlapping obstacles detected: may create conflicting constraints`,
|
|
648
|
-
{
|
|
649
|
-
obstacle1: obstacle,
|
|
650
|
-
obstacle2: otherObstacle,
|
|
651
|
-
dragName,
|
|
652
|
-
element1: obstacle.element,
|
|
653
|
-
element2: otherObstacle.element,
|
|
654
|
-
},
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
};
|