@jsenv/dom 0.10.4 → 0.11.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 +1117 -497
- package/package.json +3 -2
package/dist/jsenv_dom.js
CHANGED
|
@@ -141,6 +141,81 @@ const looksLikeGeneratedId = (id) => {
|
|
|
141
141
|
return /^[A-Z][0-9]+-[0-9]+$|^:[a-z][0-9]*:$/.test(id);
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Navi uses three categories of custom events:
|
|
146
|
+
*
|
|
147
|
+
* 1. **Internal events** (`dispatchInternalCustomEvent`) — a component communicates
|
|
148
|
+
* with other navi components internally. Not meant to be observed from outside.
|
|
149
|
+
* They do not bubble so they stay contained within the subtree that handles them.
|
|
150
|
+
* Names often reflect their internal nature (e.g. `navi_pseudo_state_request_check`).
|
|
151
|
+
*
|
|
152
|
+
* 2. **Public events** (`dispatchPublicCustomEvent`) — a component exposes information
|
|
153
|
+
* about something that happened (e.g. `navi_list_select`). They bubble so any
|
|
154
|
+
* ancestor can observe them. These are part of the public API and should be documented.
|
|
155
|
+
*
|
|
156
|
+
* 3. **Request events** (`dispatchCustomEvent`) — code *outside* a component asks it
|
|
157
|
+
* to perform an action (e.g. `navi_list_request_open`). They are cancelable so the
|
|
158
|
+
* component can signal whether it handled the request. Names are prefixed
|
|
159
|
+
* with `request_` by convention.
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Dispatches an internal event on `el`.
|
|
164
|
+
* Does not bubble — stays within the local subtree.
|
|
165
|
+
*/
|
|
166
|
+
const dispatchInternalCustomEvent = (
|
|
167
|
+
el,
|
|
168
|
+
customEventName,
|
|
169
|
+
customEventDetail,
|
|
170
|
+
) => {
|
|
171
|
+
const customEvent = new CustomEvent(customEventName, {
|
|
172
|
+
detail: customEventDetail,
|
|
173
|
+
});
|
|
174
|
+
return el.dispatchEvent(customEvent);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Dispatches a public event from `el`, announcing something that happened.
|
|
179
|
+
* Bubbles so any ancestor can observe it.
|
|
180
|
+
*/
|
|
181
|
+
const dispatchPublicCustomEvent = (
|
|
182
|
+
el,
|
|
183
|
+
customEventName,
|
|
184
|
+
customEventDetail,
|
|
185
|
+
) => {
|
|
186
|
+
const customEvent = new CustomEvent(customEventName, {
|
|
187
|
+
detail: resolveEventDetail(customEventDetail),
|
|
188
|
+
bubbles: true,
|
|
189
|
+
});
|
|
190
|
+
return el.dispatchEvent(customEvent);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Dispatches a request event *at* `el`, asking it to perform an action.
|
|
195
|
+
* Cancelable — returns `false` if the component called `preventDefault()`,
|
|
196
|
+
* indicating it did not (or could not) handle the request.
|
|
197
|
+
* Names are conventionally prefixed with `request_` (e.g. `navi_list_request_open`).
|
|
198
|
+
*/
|
|
199
|
+
const dispatchCustomEvent = (el, customEventName, customEventDetail) => {
|
|
200
|
+
const customEvent = new CustomEvent(customEventName, {
|
|
201
|
+
detail: resolveEventDetail(customEventDetail),
|
|
202
|
+
cancelable: true,
|
|
203
|
+
});
|
|
204
|
+
const result = el.dispatchEvent(customEvent);
|
|
205
|
+
return result;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const resolveEventDetail = (customEventDetail) => {
|
|
209
|
+
const { event, ...rest } = customEventDetail ?? {};
|
|
210
|
+
let resolvedEvent;
|
|
211
|
+
if (event?.detail?.event !== undefined) {
|
|
212
|
+
resolvedEvent = event.detail.event;
|
|
213
|
+
} else if (event !== undefined) {
|
|
214
|
+
resolvedEvent = event;
|
|
215
|
+
}
|
|
216
|
+
return { ...rest, event: resolvedEvent };
|
|
217
|
+
};
|
|
218
|
+
|
|
144
219
|
const createIterableWeakSet = () => {
|
|
145
220
|
const objectWeakRefSet = new Set();
|
|
146
221
|
|
|
@@ -1721,13 +1796,16 @@ const stringifyCSSTransform = (transformObj, normalize) => {
|
|
|
1721
1796
|
);
|
|
1722
1797
|
transforms.push(`${key}(${normalizedTransformPartValue})`);
|
|
1723
1798
|
}
|
|
1799
|
+
if (transforms.length === 0) {
|
|
1800
|
+
return "none";
|
|
1801
|
+
}
|
|
1724
1802
|
return transforms.join(" ");
|
|
1725
1803
|
};
|
|
1726
1804
|
|
|
1727
1805
|
// Parse transform CSS string into object
|
|
1728
1806
|
const parseCSSTransform = (transformString, normalize) => {
|
|
1729
1807
|
if (!transformString || transformString === "none") {
|
|
1730
|
-
return
|
|
1808
|
+
return {};
|
|
1731
1809
|
}
|
|
1732
1810
|
|
|
1733
1811
|
const transformObj = {};
|
|
@@ -2086,9 +2164,6 @@ const normalizeStyle = (
|
|
|
2086
2164
|
if (propertyName === "transform") {
|
|
2087
2165
|
if (context === "js") {
|
|
2088
2166
|
if (typeof value === "string") {
|
|
2089
|
-
if (isCSSKeyword(value)) {
|
|
2090
|
-
return value;
|
|
2091
|
-
}
|
|
2092
2167
|
// For js context, prefer objects
|
|
2093
2168
|
return parseCSSTransform(value, normalizeStyle);
|
|
2094
2169
|
}
|
|
@@ -6358,42 +6433,75 @@ const findSelfOrAncestorFixedPosition = (element) => {
|
|
|
6358
6433
|
/**
|
|
6359
6434
|
* Creates a coordinate system positioner for drag operations.
|
|
6360
6435
|
*
|
|
6361
|
-
*
|
|
6362
|
-
*
|
|
6363
|
-
*
|
|
6436
|
+
* PURPOSE:
|
|
6437
|
+
* During a drag gesture, the system tracks mouse movement as "scrollable coordinates"
|
|
6438
|
+
* relative to the scroll container. This function converts those coordinates into
|
|
6439
|
+
* the actual CSS transform values needed to visually move an element (or a separate
|
|
6440
|
+
* elementToMove) to follow the mouse.
|
|
6441
|
+
*
|
|
6442
|
+
* PARAMETERS:
|
|
6443
|
+
* - element: The element being grabbed / tracked for drag detection and auto-scroll.
|
|
6444
|
+
* - referenceElement: Optional. The element whose coordinate system defines the input space.
|
|
6445
|
+
* When provided, scrollable coords are relative to its scroll container.
|
|
6446
|
+
* Defaults to element itself.
|
|
6447
|
+
* - elementToMove: Optional. A different element to apply the transform to (e.g. a clone
|
|
6448
|
+
* or a table that moves as a whole when a column is dragged).
|
|
6449
|
+
* When provided, its offsetParent is used as the positioning context.
|
|
6450
|
+
*
|
|
6451
|
+
* THE COORDINATE PIPELINE:
|
|
6452
|
+
*
|
|
6453
|
+
* Mouse position
|
|
6454
|
+
* → scrollable coords (relative to referenceScrollContainer, scroll-independent)
|
|
6455
|
+
* → positioned coords (relative to elementToMove's offsetParent, for CSS transform)
|
|
6456
|
+
*
|
|
6457
|
+
* Two types of offsets bridge these spaces:
|
|
6364
6458
|
*
|
|
6365
|
-
*
|
|
6366
|
-
*
|
|
6367
|
-
*
|
|
6459
|
+
* 1. POSITION OFFSETS (getPositionOffsets):
|
|
6460
|
+
* Compensate for the fact that positionedParent and referencePositionedParent
|
|
6461
|
+
* may differ. For example, if `element` lives inside a <table> and `elementToMove`
|
|
6462
|
+
* is a full table clone, their offsetParents are different elements.
|
|
6463
|
+
* This offset is the spatial difference between those two positioned ancestors.
|
|
6464
|
+
* Called dynamically because parents can move (e.g. overlay elements).
|
|
6368
6465
|
*
|
|
6369
|
-
*
|
|
6370
|
-
*
|
|
6371
|
-
*
|
|
6372
|
-
*
|
|
6466
|
+
* 2. SCROLL OFFSETS (getScrollOffsets):
|
|
6467
|
+
* Account for the scroll position of the relevant scroll container(s).
|
|
6468
|
+
* The math ensures that at grab time, the transform delta is zero (element
|
|
6469
|
+
* stays at its visual position), and subsequent mouse movement maps 1:1
|
|
6470
|
+
* to transform change.
|
|
6471
|
+
*
|
|
6472
|
+
* CRITICAL CASE — positionedParent outside referenceScrollContainer:
|
|
6473
|
+
* When elementToMove's offsetParent is NOT inside the referenceScrollContainer
|
|
6474
|
+
* (e.g. a clone appended to document.body while tracking an element inside
|
|
6475
|
+
* an overflow:auto div), the scroll offset must be FROZEN at grab time.
|
|
6476
|
+
* Using a live scroll value would double-move the clone during auto-scroll:
|
|
6477
|
+
* the scrollable coordinate decreases (element appears to move up) AND the
|
|
6478
|
+
* live scroll value increases — both applied to the same transform.
|
|
6479
|
+
* Freezing the scroll at grab time cancels this out while still correctly
|
|
6480
|
+
* placing the clone at the right initial position.
|
|
6373
6481
|
*
|
|
6374
6482
|
* KEY SCENARIOS SUPPORTED:
|
|
6375
|
-
* 1. Same positioned parent, same scroll container
|
|
6376
|
-
* 2. Different positioned parents, same scroll container
|
|
6377
|
-
* 3. Same positioned parent, different scroll containers
|
|
6378
|
-
* 4. Different positioned parents, different
|
|
6379
|
-
* 5. Overlay elements
|
|
6380
|
-
* 6. Fixed
|
|
6483
|
+
* 1. Same positioned parent, same scroll container — minimal offsets
|
|
6484
|
+
* 2. Different positioned parents, same scroll container — position offset compensation
|
|
6485
|
+
* 3. Same positioned parent, different scroll containers — scroll offset bridging
|
|
6486
|
+
* 4. Different positioned parents, different containers — full offset compensation
|
|
6487
|
+
* 5. Overlay elements (data-overlay-for) — specialized offset path
|
|
6488
|
+
* 6. Fixed positioned elements — special scroll handling
|
|
6489
|
+
* 7. elementToMove outside referenceScrollContainer — frozen scroll offset at grab
|
|
6381
6490
|
*
|
|
6382
6491
|
* API CONTRACT:
|
|
6383
6492
|
* Returns [scrollableLeft, scrollableTop, convertScrollablePosition] where:
|
|
6384
6493
|
*
|
|
6385
6494
|
* - scrollableLeft/scrollableTop:
|
|
6386
|
-
*
|
|
6387
|
-
*
|
|
6388
|
-
* - convertScrollablePosition:
|
|
6389
|
-
* Converts reference coordinate system positions to DOM positioning coordinates
|
|
6390
|
-
* Applies both position and scroll offsets for accurate element placement
|
|
6495
|
+
* The element's current position in the reference coordinate system at grab time.
|
|
6496
|
+
* Used as the layout starting point (layoutScrollableLeft/Top) by the gesture system.
|
|
6391
6497
|
*
|
|
6392
|
-
*
|
|
6393
|
-
*
|
|
6394
|
-
*
|
|
6498
|
+
* - convertScrollablePosition(scrollableLeft, scrollableTop):
|
|
6499
|
+
* Converts a scrollable coordinate (from the gesture layout) into a positioned
|
|
6500
|
+
* coordinate suitable for CSS transform. The gesture system computes:
|
|
6501
|
+
* topDelta = convertScrollablePosition(layout.scrollableTop) - topAtGrab
|
|
6502
|
+
* and applies that as translateY. At grab time, delta = 0. As the mouse moves,
|
|
6503
|
+
* delta tracks the movement exactly, regardless of scroll context differences.
|
|
6395
6504
|
*/
|
|
6396
|
-
|
|
6397
6505
|
const createDragElementPositioner = (
|
|
6398
6506
|
element,
|
|
6399
6507
|
referenceElement,
|
|
@@ -6411,11 +6519,11 @@ const createDragElementPositioner = (
|
|
|
6411
6519
|
positionedParent,
|
|
6412
6520
|
referencePositionedParent: referenceElement
|
|
6413
6521
|
? referenceElement.offsetParent
|
|
6414
|
-
:
|
|
6522
|
+
: positionedParent,
|
|
6415
6523
|
scrollContainer,
|
|
6416
6524
|
referenceScrollContainer: referenceElement
|
|
6417
6525
|
? getScrollContainer(referenceElement)
|
|
6418
|
-
:
|
|
6526
|
+
: scrollContainer,
|
|
6419
6527
|
});
|
|
6420
6528
|
|
|
6421
6529
|
{
|
|
@@ -6465,9 +6573,9 @@ const getScrollablePosition = (element, scrollContainer) => {
|
|
|
6465
6573
|
|
|
6466
6574
|
const createGetOffsets = ({
|
|
6467
6575
|
positionedParent,
|
|
6468
|
-
referencePositionedParent
|
|
6576
|
+
referencePositionedParent,
|
|
6469
6577
|
scrollContainer,
|
|
6470
|
-
referenceScrollContainer
|
|
6578
|
+
referenceScrollContainer,
|
|
6471
6579
|
}) => {
|
|
6472
6580
|
const samePositionedParent = positionedParent === referencePositionedParent;
|
|
6473
6581
|
const getScrollOffsets = createGetScrollOffsets(
|
|
@@ -6700,6 +6808,19 @@ const createGetScrollOffsets = (
|
|
|
6700
6808
|
return getScrollOffsetsFixed;
|
|
6701
6809
|
}
|
|
6702
6810
|
}
|
|
6811
|
+
const positionedParentIsInsideScrollContainer =
|
|
6812
|
+
referenceScrollContainer === documentElement ||
|
|
6813
|
+
referenceScrollContainer.contains(positionedParent);
|
|
6814
|
+
if (!positionedParentIsInsideScrollContainer) {
|
|
6815
|
+
// positionedParent is outside the scroll container (e.g. clone in document.body
|
|
6816
|
+
// while tracking an element inside a custom scroll container).
|
|
6817
|
+
// We must add the scroll at grab time as a frozen offset so that:
|
|
6818
|
+
// - initial topDelta = 0 (clone starts at correct position)
|
|
6819
|
+
// - auto-scroll doesn't double-move the clone (scroll changes cancel out in layout)
|
|
6820
|
+
const scrollLeftAtGrab = referenceScrollContainer.scrollLeft;
|
|
6821
|
+
const scrollTopAtGrab = referenceScrollContainer.scrollTop;
|
|
6822
|
+
return () => [scrollLeft + scrollLeftAtGrab, scrollTop + scrollTopAtGrab];
|
|
6823
|
+
}
|
|
6703
6824
|
const getScrollOffsets = () => {
|
|
6704
6825
|
const leftScrollToAdd = scrollLeft + referenceScrollContainer.scrollLeft;
|
|
6705
6826
|
const topScrollToAdd = scrollTop + referenceScrollContainer.scrollTop;
|
|
@@ -6956,6 +7077,13 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
6956
7077
|
* donc juste x/y ca seras surement mieux
|
|
6957
7078
|
*
|
|
6958
7079
|
*/
|
|
7080
|
+
const css$4 = /* css */`
|
|
7081
|
+
.navi_drag_gesture_backdrop {
|
|
7082
|
+
position: fixed;
|
|
7083
|
+
inset: 0;
|
|
7084
|
+
user-select: none;
|
|
7085
|
+
}
|
|
7086
|
+
`;
|
|
6959
7087
|
const createDragGestureController = (options = {}) => {
|
|
6960
7088
|
const {
|
|
6961
7089
|
name,
|
|
@@ -7058,6 +7186,10 @@ const createDragGestureController = (options = {}) => {
|
|
|
7058
7186
|
isGoingDown: undefined,
|
|
7059
7187
|
isGoingLeft: undefined,
|
|
7060
7188
|
isGoingRight: undefined,
|
|
7189
|
+
intentGoingUp: false,
|
|
7190
|
+
intentGoingDown: false,
|
|
7191
|
+
intentGoingLeft: false,
|
|
7192
|
+
intentGoingRight: false,
|
|
7061
7193
|
// metadata about interaction sources
|
|
7062
7194
|
grabEvent: event,
|
|
7063
7195
|
dragEvent: null,
|
|
@@ -7102,6 +7234,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
7102
7234
|
|
|
7103
7235
|
// 2. VISUAL CONTROL: Backdrop for consistent cursor and pointer event blocking
|
|
7104
7236
|
if (backdrop) {
|
|
7237
|
+
import.meta.css = [css$4, "@jsenv/dom/src/interaction/drag/drag_gesture.js"];
|
|
7105
7238
|
const backdropElement = document.createElement("div");
|
|
7106
7239
|
backdropElement.className = "navi_drag_gesture_backdrop";
|
|
7107
7240
|
backdropElement.ariaHidden = "true";
|
|
@@ -7304,10 +7437,31 @@ const createDragGestureController = (options = {}) => {
|
|
|
7304
7437
|
const layoutPrevious = gestureInfo.layout;
|
|
7305
7438
|
// previousGestureInfo = { ...gestureInfo };
|
|
7306
7439
|
Object.assign(gestureInfo, dragData);
|
|
7440
|
+
if (gestureInfo.isGoingDown) {
|
|
7441
|
+
gestureInfo.intentGoingDown = true;
|
|
7442
|
+
gestureInfo.intentGoingUp = false;
|
|
7443
|
+
} else if (gestureInfo.isGoingUp) {
|
|
7444
|
+
gestureInfo.intentGoingUp = true;
|
|
7445
|
+
gestureInfo.intentGoingDown = false;
|
|
7446
|
+
}
|
|
7447
|
+
if (gestureInfo.isGoingRight) {
|
|
7448
|
+
gestureInfo.intentGoingRight = true;
|
|
7449
|
+
gestureInfo.intentGoingLeft = false;
|
|
7450
|
+
} else if (gestureInfo.isGoingLeft) {
|
|
7451
|
+
gestureInfo.intentGoingLeft = true;
|
|
7452
|
+
gestureInfo.intentGoingRight = false;
|
|
7453
|
+
}
|
|
7307
7454
|
if (!startedPrevious && gestureInfo.started) {
|
|
7455
|
+
dispatchPublicCustomEvent(element, "navi_drag_start", {
|
|
7456
|
+
gestureInfo
|
|
7457
|
+
});
|
|
7308
7458
|
onDragStart?.(gestureInfo);
|
|
7309
7459
|
}
|
|
7310
7460
|
const someLayoutChange = gestureInfo.layout !== layoutPrevious;
|
|
7461
|
+
dispatchPublicCustomEvent(element, "navi_drag", {
|
|
7462
|
+
gestureInfo,
|
|
7463
|
+
someLayoutChange
|
|
7464
|
+
});
|
|
7311
7465
|
publishDrag(gestureInfo,
|
|
7312
7466
|
// we still publish drag event even when unchanged
|
|
7313
7467
|
// because UI might need to adjust when document scrolls
|
|
@@ -7324,8 +7478,14 @@ const createDragGestureController = (options = {}) => {
|
|
|
7324
7478
|
event,
|
|
7325
7479
|
isRelease: true
|
|
7326
7480
|
});
|
|
7481
|
+
dispatchPublicCustomEvent(element, "navi_drag_release", {
|
|
7482
|
+
gestureInfo
|
|
7483
|
+
});
|
|
7327
7484
|
publishRelease(gestureInfo);
|
|
7328
7485
|
};
|
|
7486
|
+
dispatchPublicCustomEvent(element, "navi_drag_grab", {
|
|
7487
|
+
gestureInfo
|
|
7488
|
+
});
|
|
7329
7489
|
onGrab?.(gestureInfo);
|
|
7330
7490
|
const dragGesture = {
|
|
7331
7491
|
gestureInfo,
|
|
@@ -7458,15 +7618,24 @@ const definePropertyAsReadOnly = (object, propertyName) => {
|
|
|
7458
7618
|
value: object[propertyName]
|
|
7459
7619
|
});
|
|
7460
7620
|
};
|
|
7461
|
-
|
|
7462
|
-
|
|
7621
|
+
|
|
7622
|
+
installImportMetaCssBuild(import.meta);const css$3 = /* css */`
|
|
7623
|
+
.navi_constraint_feedback_line {
|
|
7463
7624
|
position: fixed;
|
|
7464
|
-
|
|
7465
|
-
|
|
7625
|
+
z-index: 9998;
|
|
7626
|
+
border-top: 2px dotted rgba(59, 130, 246, 0.7);
|
|
7627
|
+
visibility: hidden;
|
|
7628
|
+
transform-origin: left center;
|
|
7629
|
+
transition: opacity 0.15s ease;
|
|
7630
|
+
pointer-events: none;
|
|
7466
7631
|
}
|
|
7467
|
-
`, "@jsenv/dom/src/interaction/drag/drag_gesture.js"];
|
|
7468
7632
|
|
|
7469
|
-
|
|
7633
|
+
.navi_constraint_feedback_line[data-visible] {
|
|
7634
|
+
visibility: visible;
|
|
7635
|
+
}
|
|
7636
|
+
`;
|
|
7637
|
+
const setupConstraintFeedbackLine = () => {
|
|
7638
|
+
import.meta.css = [css$3, "@jsenv/dom/src/interaction/drag/constraint_feedback_line.js"];
|
|
7470
7639
|
const constraintFeedbackLine = createConstraintFeedbackLine();
|
|
7471
7640
|
|
|
7472
7641
|
// Track last known mouse position for constraint feedback line during scroll
|
|
@@ -7539,21 +7708,6 @@ const createConstraintFeedbackLine = () => {
|
|
|
7539
7708
|
document.body.appendChild(line);
|
|
7540
7709
|
return line;
|
|
7541
7710
|
};
|
|
7542
|
-
import.meta.css = [/* css */`
|
|
7543
|
-
.navi_constraint_feedback_line {
|
|
7544
|
-
position: fixed;
|
|
7545
|
-
z-index: 9998;
|
|
7546
|
-
border-top: 2px dotted rgba(59, 130, 246, 0.7);
|
|
7547
|
-
visibility: hidden;
|
|
7548
|
-
transform-origin: left center;
|
|
7549
|
-
transition: opacity 0.15s ease;
|
|
7550
|
-
pointer-events: none;
|
|
7551
|
-
}
|
|
7552
|
-
|
|
7553
|
-
.navi_constraint_feedback_line[data-visible] {
|
|
7554
|
-
visibility: visible;
|
|
7555
|
-
}
|
|
7556
|
-
`, "@jsenv/dom/src/interaction/drag/constraint_feedback_line.js"];
|
|
7557
7711
|
|
|
7558
7712
|
installImportMetaCssBuild(import.meta);// Keep visual markers (debug markers, obstacle markers, constraint feedback line) in DOM after drag ends
|
|
7559
7713
|
const MARKER_SIZE = 12;
|
|
@@ -7561,135 +7715,323 @@ let currentDebugMarkers = [];
|
|
|
7561
7715
|
let currentConstraintMarkers = [];
|
|
7562
7716
|
let currentReferenceElementMarker = null;
|
|
7563
7717
|
let currentElementMarker = null;
|
|
7564
|
-
const
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7718
|
+
const css$2 = /* css */`
|
|
7719
|
+
.navi_debug_markers_container {
|
|
7720
|
+
position: fixed;
|
|
7721
|
+
top: 0;
|
|
7722
|
+
left: 0;
|
|
7723
|
+
z-index: 999998;
|
|
7724
|
+
width: 100vw;
|
|
7725
|
+
height: 100vh;
|
|
7726
|
+
pointer-events: none;
|
|
7727
|
+
overflow: hidden;
|
|
7728
|
+
--marker-size: ${MARKER_SIZE}px;
|
|
7574
7729
|
}
|
|
7575
|
-
const {
|
|
7576
|
-
direction,
|
|
7577
|
-
scrollContainer
|
|
7578
|
-
} = dragGesture.gestureInfo;
|
|
7579
|
-
return {
|
|
7580
|
-
onConstraints: (constraints, {
|
|
7581
|
-
left,
|
|
7582
|
-
top,
|
|
7583
|
-
right,
|
|
7584
|
-
bottom,
|
|
7585
|
-
autoScrollArea
|
|
7586
|
-
}) => {
|
|
7587
|
-
// Schedule removal of previous markers if they exist
|
|
7588
|
-
const previousDebugMarkers = [...currentDebugMarkers];
|
|
7589
|
-
const previousConstraintMarkers = [...currentConstraintMarkers];
|
|
7590
|
-
const previousReferenceElementMarker = currentReferenceElementMarker;
|
|
7591
|
-
const previousElementMarker = currentElementMarker;
|
|
7592
|
-
if (previousDebugMarkers.length > 0 || previousConstraintMarkers.length > 0 || previousReferenceElementMarker || previousElementMarker) {
|
|
7593
|
-
setTimeout(() => {
|
|
7594
|
-
previousDebugMarkers.forEach(marker => marker.remove());
|
|
7595
|
-
previousConstraintMarkers.forEach(marker => marker.remove());
|
|
7596
|
-
if (previousReferenceElementMarker) {
|
|
7597
|
-
previousReferenceElementMarker.remove();
|
|
7598
|
-
}
|
|
7599
|
-
if (previousElementMarker) {
|
|
7600
|
-
previousElementMarker.remove();
|
|
7601
|
-
}
|
|
7602
|
-
}, 100);
|
|
7603
|
-
}
|
|
7604
7730
|
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
currentElementMarker = null;
|
|
7731
|
+
.navi_debug_marker {
|
|
7732
|
+
position: absolute;
|
|
7733
|
+
pointer-events: none;
|
|
7734
|
+
}
|
|
7610
7735
|
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7736
|
+
/* Markers based on side rather than orientation */
|
|
7737
|
+
.navi_debug_marker[data-left],
|
|
7738
|
+
.navi_debug_marker[data-right] {
|
|
7739
|
+
width: var(--marker-size);
|
|
7740
|
+
height: 100vh;
|
|
7741
|
+
}
|
|
7616
7742
|
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
scrollContainer,
|
|
7623
|
-
label: elementLabel,
|
|
7624
|
-
color: elementColor
|
|
7625
|
-
});
|
|
7743
|
+
.navi_debug_marker[data-top],
|
|
7744
|
+
.navi_debug_marker[data-bottom] {
|
|
7745
|
+
width: 100vw;
|
|
7746
|
+
height: var(--marker-size);
|
|
7747
|
+
}
|
|
7626
7748
|
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7749
|
+
/* Gradient directions based on side, using CSS custom properties for color */
|
|
7750
|
+
.navi_debug_marker[data-left] {
|
|
7751
|
+
background: linear-gradient(
|
|
7752
|
+
to right,
|
|
7753
|
+
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
7754
|
+
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
7755
|
+
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
7756
|
+
rgba(from var(--marker-color) r g b / 0) 100%
|
|
7757
|
+
);
|
|
7758
|
+
}
|
|
7637
7759
|
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
// green
|
|
7648
|
-
side: "left"
|
|
7649
|
-
});
|
|
7650
|
-
markersToCreate.push({
|
|
7651
|
-
name: autoScrollArea.paddingRight ? `autoscroll.right + padding(${autoScrollArea.paddingRight})` : "autoscroll.right",
|
|
7652
|
-
x: autoScrollArea.right,
|
|
7653
|
-
y: 0,
|
|
7654
|
-
color: "0 128 0",
|
|
7655
|
-
// green
|
|
7656
|
-
side: "right"
|
|
7657
|
-
});
|
|
7658
|
-
}
|
|
7659
|
-
if (direction.y) {
|
|
7660
|
-
markersToCreate.push({
|
|
7661
|
-
name: autoScrollArea.paddingTop ? `autoscroll.top + padding(${autoScrollArea.paddingTop})` : "autoscroll.top",
|
|
7662
|
-
x: 0,
|
|
7663
|
-
y: autoScrollArea.top,
|
|
7664
|
-
color: "255 0 0",
|
|
7665
|
-
// red
|
|
7666
|
-
side: "top"
|
|
7667
|
-
});
|
|
7668
|
-
markersToCreate.push({
|
|
7669
|
-
name: autoScrollArea.paddingBottom ? `autoscroll.bottom + padding(${autoScrollArea.paddingBottom})` : "autoscroll.bottom",
|
|
7670
|
-
x: 0,
|
|
7671
|
-
y: autoScrollArea.bottom,
|
|
7672
|
-
color: "255 165 0",
|
|
7673
|
-
// orange
|
|
7674
|
-
side: "bottom"
|
|
7675
|
-
});
|
|
7676
|
-
}
|
|
7677
|
-
}
|
|
7760
|
+
.navi_debug_marker[data-right] {
|
|
7761
|
+
background: linear-gradient(
|
|
7762
|
+
to left,
|
|
7763
|
+
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
7764
|
+
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
7765
|
+
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
7766
|
+
rgba(from var(--marker-color) r g b / 0) 100%
|
|
7767
|
+
);
|
|
7768
|
+
}
|
|
7678
7769
|
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7770
|
+
.navi_debug_marker[data-top] {
|
|
7771
|
+
background: linear-gradient(
|
|
7772
|
+
to bottom,
|
|
7773
|
+
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
7774
|
+
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
7775
|
+
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
7776
|
+
rgba(from var(--marker-color) r g b / 0) 100%
|
|
7777
|
+
);
|
|
7778
|
+
}
|
|
7685
7779
|
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7780
|
+
.navi_debug_marker[data-bottom] {
|
|
7781
|
+
background: linear-gradient(
|
|
7782
|
+
to top,
|
|
7783
|
+
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
7784
|
+
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
7785
|
+
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
7786
|
+
rgba(from var(--marker-color) r g b / 0) 100%
|
|
7787
|
+
);
|
|
7788
|
+
}
|
|
7789
|
+
|
|
7790
|
+
.navi_debug_marker_label {
|
|
7791
|
+
position: absolute;
|
|
7792
|
+
padding: 2px 6px;
|
|
7793
|
+
color: rgb(from var(--marker-color) r g b / 1);
|
|
7794
|
+
font-weight: bold;
|
|
7795
|
+
font-size: 12px;
|
|
7796
|
+
white-space: nowrap;
|
|
7797
|
+
background: rgba(255, 255, 255, 0.9);
|
|
7798
|
+
border: 1px solid;
|
|
7799
|
+
border-color: rgb(from var(--marker-color) r g b / 1);
|
|
7800
|
+
border-radius: 3px;
|
|
7801
|
+
pointer-events: none;
|
|
7802
|
+
}
|
|
7803
|
+
|
|
7804
|
+
/* Label positioning based on side data attributes */
|
|
7805
|
+
|
|
7806
|
+
/* Left side markers - vertical with 90° rotation */
|
|
7807
|
+
.navi_debug_marker[data-left] .navi_debug_marker_label {
|
|
7808
|
+
top: 20px;
|
|
7809
|
+
left: 10px;
|
|
7810
|
+
transform: rotate(90deg);
|
|
7811
|
+
transform-origin: left center;
|
|
7812
|
+
}
|
|
7813
|
+
|
|
7814
|
+
/* Right side markers - vertical with -90° rotation */
|
|
7815
|
+
.navi_debug_marker[data-right] .navi_debug_marker_label {
|
|
7816
|
+
top: 20px;
|
|
7817
|
+
right: 10px;
|
|
7818
|
+
left: auto;
|
|
7819
|
+
transform: rotate(-90deg);
|
|
7820
|
+
transform-origin: right center;
|
|
7821
|
+
}
|
|
7822
|
+
|
|
7823
|
+
/* Top side markers - horizontal, label on the line */
|
|
7824
|
+
.navi_debug_marker[data-top] .navi_debug_marker_label {
|
|
7825
|
+
top: 0px;
|
|
7826
|
+
left: 20px;
|
|
7827
|
+
}
|
|
7828
|
+
|
|
7829
|
+
/* Bottom side markers - horizontal, label on the line */
|
|
7830
|
+
.navi_debug_marker[data-bottom] .navi_debug_marker_label {
|
|
7831
|
+
top: auto;
|
|
7832
|
+
bottom: 0px;
|
|
7833
|
+
left: 20px;
|
|
7834
|
+
}
|
|
7835
|
+
|
|
7836
|
+
.navi_obstacle_marker {
|
|
7837
|
+
position: absolute;
|
|
7838
|
+
z-index: 9999;
|
|
7839
|
+
background-color: orange;
|
|
7840
|
+
opacity: 0.6;
|
|
7841
|
+
pointer-events: none;
|
|
7842
|
+
}
|
|
7843
|
+
|
|
7844
|
+
.navi_obstacle_marker_label {
|
|
7845
|
+
position: absolute;
|
|
7846
|
+
top: 50%;
|
|
7847
|
+
left: 50%;
|
|
7848
|
+
color: white;
|
|
7849
|
+
font-weight: bold;
|
|
7850
|
+
font-size: 12px;
|
|
7851
|
+
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
|
|
7852
|
+
transform: translate(-50%, -50%);
|
|
7853
|
+
pointer-events: none;
|
|
7854
|
+
}
|
|
7855
|
+
|
|
7856
|
+
.navi_element_marker {
|
|
7857
|
+
position: absolute;
|
|
7858
|
+
z-index: 9997;
|
|
7859
|
+
background-color: var(--element-color-alpha, rgba(255, 0, 150, 0.3));
|
|
7860
|
+
border: 2px solid var(--element-color, rgb(255, 0, 150));
|
|
7861
|
+
opacity: 0.9;
|
|
7862
|
+
pointer-events: none;
|
|
7863
|
+
}
|
|
7864
|
+
|
|
7865
|
+
.navi_element_marker_label {
|
|
7866
|
+
position: absolute;
|
|
7867
|
+
top: -25px;
|
|
7868
|
+
right: 0;
|
|
7869
|
+
padding: 2px 6px;
|
|
7870
|
+
color: var(--element-color, rgb(255, 0, 150));
|
|
7871
|
+
font-weight: bold;
|
|
7872
|
+
font-size: 11px;
|
|
7873
|
+
white-space: nowrap;
|
|
7874
|
+
background: rgba(255, 255, 255, 0.9);
|
|
7875
|
+
border: 1px solid var(--element-color, rgb(255, 0, 150));
|
|
7876
|
+
border-radius: 3px;
|
|
7877
|
+
pointer-events: none;
|
|
7878
|
+
}
|
|
7879
|
+
|
|
7880
|
+
.navi_reference_element_marker {
|
|
7881
|
+
position: absolute;
|
|
7882
|
+
z-index: 9998;
|
|
7883
|
+
background-color: rgba(0, 150, 255, 0.3);
|
|
7884
|
+
border: 2px dashed rgba(0, 150, 255, 0.7);
|
|
7885
|
+
opacity: 0.8;
|
|
7886
|
+
pointer-events: none;
|
|
7887
|
+
}
|
|
7888
|
+
|
|
7889
|
+
.navi_reference_element_marker_label {
|
|
7890
|
+
position: absolute;
|
|
7891
|
+
top: -25px;
|
|
7892
|
+
left: 0;
|
|
7893
|
+
padding: 2px 6px;
|
|
7894
|
+
color: rgba(0, 150, 255, 1);
|
|
7895
|
+
font-weight: bold;
|
|
7896
|
+
font-size: 11px;
|
|
7897
|
+
white-space: nowrap;
|
|
7898
|
+
background: rgba(255, 255, 255, 0.9);
|
|
7899
|
+
border: 1px solid rgba(0, 150, 255, 0.7);
|
|
7900
|
+
border-radius: 3px;
|
|
7901
|
+
pointer-events: none;
|
|
7902
|
+
}
|
|
7903
|
+
`;
|
|
7904
|
+
const setupDragDebugMarkers = (dragGesture, {
|
|
7905
|
+
referenceElement
|
|
7906
|
+
}) => {
|
|
7907
|
+
import.meta.css = [css$2, "@jsenv/dom/src/interaction/drag/drag_debug_markers.js"];
|
|
7908
|
+
|
|
7909
|
+
// Clean up any existing persistent markers from previous drag gestures
|
|
7910
|
+
{
|
|
7911
|
+
// Remove any existing markers from previous gestures
|
|
7912
|
+
const container = document.getElementById("navi_debug_markers_container");
|
|
7913
|
+
if (container) {
|
|
7914
|
+
container.innerHTML = ""; // Clear all markers efficiently
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7917
|
+
const {
|
|
7918
|
+
direction,
|
|
7919
|
+
scrollContainer
|
|
7920
|
+
} = dragGesture.gestureInfo;
|
|
7921
|
+
return {
|
|
7922
|
+
onConstraints: (constraints, {
|
|
7923
|
+
left,
|
|
7924
|
+
top,
|
|
7925
|
+
right,
|
|
7926
|
+
bottom,
|
|
7927
|
+
autoScrollArea
|
|
7928
|
+
}) => {
|
|
7929
|
+
// Schedule removal of previous markers if they exist
|
|
7930
|
+
const previousDebugMarkers = [...currentDebugMarkers];
|
|
7931
|
+
const previousConstraintMarkers = [...currentConstraintMarkers];
|
|
7932
|
+
const previousReferenceElementMarker = currentReferenceElementMarker;
|
|
7933
|
+
const previousElementMarker = currentElementMarker;
|
|
7934
|
+
if (previousDebugMarkers.length > 0 || previousConstraintMarkers.length > 0 || previousReferenceElementMarker || previousElementMarker) {
|
|
7935
|
+
setTimeout(() => {
|
|
7936
|
+
previousDebugMarkers.forEach(marker => marker.remove());
|
|
7937
|
+
previousConstraintMarkers.forEach(marker => marker.remove());
|
|
7938
|
+
if (previousReferenceElementMarker) {
|
|
7939
|
+
previousReferenceElementMarker.remove();
|
|
7940
|
+
}
|
|
7941
|
+
if (previousElementMarker) {
|
|
7942
|
+
previousElementMarker.remove();
|
|
7943
|
+
}
|
|
7944
|
+
}, 100);
|
|
7945
|
+
}
|
|
7946
|
+
|
|
7947
|
+
// Clear current marker arrays
|
|
7948
|
+
currentDebugMarkers.length = 0;
|
|
7949
|
+
currentConstraintMarkers.length = 0;
|
|
7950
|
+
currentReferenceElementMarker = null;
|
|
7951
|
+
currentElementMarker = null;
|
|
7952
|
+
|
|
7953
|
+
// Create element marker (always show the dragged element)
|
|
7954
|
+
// When there's a reference element, show it as "Dragged Element"
|
|
7955
|
+
// When there's no reference element, show it as "Element"
|
|
7956
|
+
const elementLabel = referenceElement ? "Dragged Element" : "Element";
|
|
7957
|
+
const elementColor = referenceElement ? "255, 0, 150" : "0, 200, 0"; // Pink when with reference, green when standalone
|
|
7958
|
+
|
|
7959
|
+
currentElementMarker = createElementMarker({
|
|
7960
|
+
left,
|
|
7961
|
+
top,
|
|
7962
|
+
right,
|
|
7963
|
+
bottom,
|
|
7964
|
+
scrollContainer,
|
|
7965
|
+
label: elementLabel,
|
|
7966
|
+
color: elementColor
|
|
7967
|
+
});
|
|
7968
|
+
|
|
7969
|
+
// Create reference element marker if reference element exists
|
|
7970
|
+
if (referenceElement) {
|
|
7971
|
+
currentReferenceElementMarker = createReferenceElementMarker({
|
|
7972
|
+
left,
|
|
7973
|
+
top,
|
|
7974
|
+
right,
|
|
7975
|
+
bottom,
|
|
7976
|
+
scrollContainer
|
|
7977
|
+
});
|
|
7978
|
+
}
|
|
7979
|
+
|
|
7980
|
+
// Collect all markers to be created, then merge duplicates
|
|
7981
|
+
const markersToCreate = [];
|
|
7982
|
+
{
|
|
7983
|
+
if (direction.x) {
|
|
7984
|
+
markersToCreate.push({
|
|
7985
|
+
name: autoScrollArea.paddingLeft ? `autoscroll.left + padding(${autoScrollArea.paddingLeft})` : "autoscroll.left",
|
|
7986
|
+
x: autoScrollArea.left,
|
|
7987
|
+
y: 0,
|
|
7988
|
+
color: "0 128 0",
|
|
7989
|
+
// green
|
|
7990
|
+
side: "left"
|
|
7991
|
+
});
|
|
7992
|
+
markersToCreate.push({
|
|
7993
|
+
name: autoScrollArea.paddingRight ? `autoscroll.right + padding(${autoScrollArea.paddingRight})` : "autoscroll.right",
|
|
7994
|
+
x: autoScrollArea.right,
|
|
7995
|
+
y: 0,
|
|
7996
|
+
color: "0 128 0",
|
|
7997
|
+
// green
|
|
7998
|
+
side: "right"
|
|
7999
|
+
});
|
|
8000
|
+
}
|
|
8001
|
+
if (direction.y) {
|
|
8002
|
+
markersToCreate.push({
|
|
8003
|
+
name: autoScrollArea.paddingTop ? `autoscroll.top + padding(${autoScrollArea.paddingTop})` : "autoscroll.top",
|
|
8004
|
+
x: 0,
|
|
8005
|
+
y: autoScrollArea.top,
|
|
8006
|
+
color: "255 0 0",
|
|
8007
|
+
// red
|
|
8008
|
+
side: "top"
|
|
8009
|
+
});
|
|
8010
|
+
markersToCreate.push({
|
|
8011
|
+
name: autoScrollArea.paddingBottom ? `autoscroll.bottom + padding(${autoScrollArea.paddingBottom})` : "autoscroll.bottom",
|
|
8012
|
+
x: 0,
|
|
8013
|
+
y: autoScrollArea.bottom,
|
|
8014
|
+
color: "255 165 0",
|
|
8015
|
+
// orange
|
|
8016
|
+
side: "bottom"
|
|
8017
|
+
});
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
// Process each constraint individually to preserve names
|
|
8022
|
+
for (const constraint of constraints) {
|
|
8023
|
+
if (constraint.type === "bounds") {
|
|
8024
|
+
const {
|
|
8025
|
+
bounds
|
|
8026
|
+
} = constraint;
|
|
8027
|
+
|
|
8028
|
+
// Create individual markers for each bound with constraint name
|
|
8029
|
+
if (direction.x) {
|
|
8030
|
+
if (bounds.left !== undefined) {
|
|
8031
|
+
markersToCreate.push({
|
|
8032
|
+
name: `${constraint.name}.left`,
|
|
8033
|
+
x: bounds.left,
|
|
8034
|
+
y: 0,
|
|
7693
8035
|
color: "128 0 128",
|
|
7694
8036
|
// purple
|
|
7695
8037
|
side: "left"
|
|
@@ -7929,213 +8271,27 @@ const createReferenceElementMarker = ({
|
|
|
7929
8271
|
bottom,
|
|
7930
8272
|
scrollContainer
|
|
7931
8273
|
}) => {
|
|
7932
|
-
const width = right - left;
|
|
7933
|
-
const height = bottom - top;
|
|
7934
|
-
// Convert document-relative coordinates to viewport coordinates
|
|
7935
|
-
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "reference");
|
|
7936
|
-
const marker = document.createElement("div");
|
|
7937
|
-
marker.className = "navi_reference_element_marker";
|
|
7938
|
-
marker.style.left = `${x}px`;
|
|
7939
|
-
marker.style.top = `${y}px`;
|
|
7940
|
-
marker.style.width = `${width}px`;
|
|
7941
|
-
marker.style.height = `${height}px`;
|
|
7942
|
-
marker.title = "Reference Element";
|
|
7943
|
-
|
|
7944
|
-
// Add label
|
|
7945
|
-
const label = document.createElement("div");
|
|
7946
|
-
label.className = "navi_reference_element_marker_label";
|
|
7947
|
-
label.textContent = "Reference Element";
|
|
7948
|
-
marker.appendChild(label);
|
|
7949
|
-
const container = getMarkersContainer();
|
|
7950
|
-
container.appendChild(marker);
|
|
7951
|
-
return marker;
|
|
7952
|
-
};
|
|
7953
|
-
import.meta.css = [/* css */`
|
|
7954
|
-
.navi_debug_markers_container {
|
|
7955
|
-
position: fixed;
|
|
7956
|
-
top: 0;
|
|
7957
|
-
left: 0;
|
|
7958
|
-
z-index: 999998;
|
|
7959
|
-
width: 100vw;
|
|
7960
|
-
height: 100vh;
|
|
7961
|
-
pointer-events: none;
|
|
7962
|
-
overflow: hidden;
|
|
7963
|
-
--marker-size: ${MARKER_SIZE}px;
|
|
7964
|
-
}
|
|
7965
|
-
|
|
7966
|
-
.navi_debug_marker {
|
|
7967
|
-
position: absolute;
|
|
7968
|
-
pointer-events: none;
|
|
7969
|
-
}
|
|
7970
|
-
|
|
7971
|
-
/* Markers based on side rather than orientation */
|
|
7972
|
-
.navi_debug_marker[data-left],
|
|
7973
|
-
.navi_debug_marker[data-right] {
|
|
7974
|
-
width: var(--marker-size);
|
|
7975
|
-
height: 100vh;
|
|
7976
|
-
}
|
|
7977
|
-
|
|
7978
|
-
.navi_debug_marker[data-top],
|
|
7979
|
-
.navi_debug_marker[data-bottom] {
|
|
7980
|
-
width: 100vw;
|
|
7981
|
-
height: var(--marker-size);
|
|
7982
|
-
}
|
|
7983
|
-
|
|
7984
|
-
/* Gradient directions based on side, using CSS custom properties for color */
|
|
7985
|
-
.navi_debug_marker[data-left] {
|
|
7986
|
-
background: linear-gradient(
|
|
7987
|
-
to right,
|
|
7988
|
-
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
7989
|
-
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
7990
|
-
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
7991
|
-
rgba(from var(--marker-color) r g b / 0) 100%
|
|
7992
|
-
);
|
|
7993
|
-
}
|
|
7994
|
-
|
|
7995
|
-
.navi_debug_marker[data-right] {
|
|
7996
|
-
background: linear-gradient(
|
|
7997
|
-
to left,
|
|
7998
|
-
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
7999
|
-
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
8000
|
-
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
8001
|
-
rgba(from var(--marker-color) r g b / 0) 100%
|
|
8002
|
-
);
|
|
8003
|
-
}
|
|
8004
|
-
|
|
8005
|
-
.navi_debug_marker[data-top] {
|
|
8006
|
-
background: linear-gradient(
|
|
8007
|
-
to bottom,
|
|
8008
|
-
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
8009
|
-
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
8010
|
-
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
8011
|
-
rgba(from var(--marker-color) r g b / 0) 100%
|
|
8012
|
-
);
|
|
8013
|
-
}
|
|
8014
|
-
|
|
8015
|
-
.navi_debug_marker[data-bottom] {
|
|
8016
|
-
background: linear-gradient(
|
|
8017
|
-
to top,
|
|
8018
|
-
rgba(from var(--marker-color) r g b / 0.9) 0%,
|
|
8019
|
-
rgba(from var(--marker-color) r g b / 0.7) 30%,
|
|
8020
|
-
rgba(from var(--marker-color) r g b / 0.3) 70%,
|
|
8021
|
-
rgba(from var(--marker-color) r g b / 0) 100%
|
|
8022
|
-
);
|
|
8023
|
-
}
|
|
8024
|
-
|
|
8025
|
-
.navi_debug_marker_label {
|
|
8026
|
-
position: absolute;
|
|
8027
|
-
padding: 2px 6px;
|
|
8028
|
-
color: rgb(from var(--marker-color) r g b / 1);
|
|
8029
|
-
font-weight: bold;
|
|
8030
|
-
font-size: 12px;
|
|
8031
|
-
white-space: nowrap;
|
|
8032
|
-
background: rgba(255, 255, 255, 0.9);
|
|
8033
|
-
border: 1px solid;
|
|
8034
|
-
border-color: rgb(from var(--marker-color) r g b / 1);
|
|
8035
|
-
border-radius: 3px;
|
|
8036
|
-
pointer-events: none;
|
|
8037
|
-
}
|
|
8038
|
-
|
|
8039
|
-
/* Label positioning based on side data attributes */
|
|
8040
|
-
|
|
8041
|
-
/* Left side markers - vertical with 90° rotation */
|
|
8042
|
-
.navi_debug_marker[data-left] .navi_debug_marker_label {
|
|
8043
|
-
top: 20px;
|
|
8044
|
-
left: 10px;
|
|
8045
|
-
transform: rotate(90deg);
|
|
8046
|
-
transform-origin: left center;
|
|
8047
|
-
}
|
|
8048
|
-
|
|
8049
|
-
/* Right side markers - vertical with -90° rotation */
|
|
8050
|
-
.navi_debug_marker[data-right] .navi_debug_marker_label {
|
|
8051
|
-
top: 20px;
|
|
8052
|
-
right: 10px;
|
|
8053
|
-
left: auto;
|
|
8054
|
-
transform: rotate(-90deg);
|
|
8055
|
-
transform-origin: right center;
|
|
8056
|
-
}
|
|
8057
|
-
|
|
8058
|
-
/* Top side markers - horizontal, label on the line */
|
|
8059
|
-
.navi_debug_marker[data-top] .navi_debug_marker_label {
|
|
8060
|
-
top: 0px;
|
|
8061
|
-
left: 20px;
|
|
8062
|
-
}
|
|
8063
|
-
|
|
8064
|
-
/* Bottom side markers - horizontal, label on the line */
|
|
8065
|
-
.navi_debug_marker[data-bottom] .navi_debug_marker_label {
|
|
8066
|
-
top: auto;
|
|
8067
|
-
bottom: 0px;
|
|
8068
|
-
left: 20px;
|
|
8069
|
-
}
|
|
8070
|
-
|
|
8071
|
-
.navi_obstacle_marker {
|
|
8072
|
-
position: absolute;
|
|
8073
|
-
z-index: 9999;
|
|
8074
|
-
background-color: orange;
|
|
8075
|
-
opacity: 0.6;
|
|
8076
|
-
pointer-events: none;
|
|
8077
|
-
}
|
|
8078
|
-
|
|
8079
|
-
.navi_obstacle_marker_label {
|
|
8080
|
-
position: absolute;
|
|
8081
|
-
top: 50%;
|
|
8082
|
-
left: 50%;
|
|
8083
|
-
color: white;
|
|
8084
|
-
font-weight: bold;
|
|
8085
|
-
font-size: 12px;
|
|
8086
|
-
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
|
|
8087
|
-
transform: translate(-50%, -50%);
|
|
8088
|
-
pointer-events: none;
|
|
8089
|
-
}
|
|
8090
|
-
|
|
8091
|
-
.navi_element_marker {
|
|
8092
|
-
position: absolute;
|
|
8093
|
-
z-index: 9997;
|
|
8094
|
-
background-color: var(--element-color-alpha, rgba(255, 0, 150, 0.3));
|
|
8095
|
-
border: 2px solid var(--element-color, rgb(255, 0, 150));
|
|
8096
|
-
opacity: 0.9;
|
|
8097
|
-
pointer-events: none;
|
|
8098
|
-
}
|
|
8099
|
-
|
|
8100
|
-
.navi_element_marker_label {
|
|
8101
|
-
position: absolute;
|
|
8102
|
-
top: -25px;
|
|
8103
|
-
right: 0;
|
|
8104
|
-
padding: 2px 6px;
|
|
8105
|
-
color: var(--element-color, rgb(255, 0, 150));
|
|
8106
|
-
font-weight: bold;
|
|
8107
|
-
font-size: 11px;
|
|
8108
|
-
white-space: nowrap;
|
|
8109
|
-
background: rgba(255, 255, 255, 0.9);
|
|
8110
|
-
border: 1px solid var(--element-color, rgb(255, 0, 150));
|
|
8111
|
-
border-radius: 3px;
|
|
8112
|
-
pointer-events: none;
|
|
8113
|
-
}
|
|
8114
|
-
|
|
8115
|
-
.navi_reference_element_marker {
|
|
8116
|
-
position: absolute;
|
|
8117
|
-
z-index: 9998;
|
|
8118
|
-
background-color: rgba(0, 150, 255, 0.3);
|
|
8119
|
-
border: 2px dashed rgba(0, 150, 255, 0.7);
|
|
8120
|
-
opacity: 0.8;
|
|
8121
|
-
pointer-events: none;
|
|
8122
|
-
}
|
|
8123
|
-
|
|
8124
|
-
.navi_reference_element_marker_label {
|
|
8125
|
-
position: absolute;
|
|
8126
|
-
top: -25px;
|
|
8127
|
-
left: 0;
|
|
8128
|
-
padding: 2px 6px;
|
|
8129
|
-
color: rgba(0, 150, 255, 1);
|
|
8130
|
-
font-weight: bold;
|
|
8131
|
-
font-size: 11px;
|
|
8132
|
-
white-space: nowrap;
|
|
8133
|
-
background: rgba(255, 255, 255, 0.9);
|
|
8134
|
-
border: 1px solid rgba(0, 150, 255, 0.7);
|
|
8135
|
-
border-radius: 3px;
|
|
8136
|
-
pointer-events: none;
|
|
8137
|
-
}
|
|
8138
|
-
`, "@jsenv/dom/src/interaction/drag/drag_debug_markers.js"];
|
|
8274
|
+
const width = right - left;
|
|
8275
|
+
const height = bottom - top;
|
|
8276
|
+
// Convert document-relative coordinates to viewport coordinates
|
|
8277
|
+
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "reference");
|
|
8278
|
+
const marker = document.createElement("div");
|
|
8279
|
+
marker.className = "navi_reference_element_marker";
|
|
8280
|
+
marker.style.left = `${x}px`;
|
|
8281
|
+
marker.style.top = `${y}px`;
|
|
8282
|
+
marker.style.width = `${width}px`;
|
|
8283
|
+
marker.style.height = `${height}px`;
|
|
8284
|
+
marker.title = "Reference Element";
|
|
8285
|
+
|
|
8286
|
+
// Add label
|
|
8287
|
+
const label = document.createElement("div");
|
|
8288
|
+
label.className = "navi_reference_element_marker_label";
|
|
8289
|
+
label.textContent = "Reference Element";
|
|
8290
|
+
marker.appendChild(label);
|
|
8291
|
+
const container = getMarkersContainer();
|
|
8292
|
+
container.appendChild(marker);
|
|
8293
|
+
return marker;
|
|
8294
|
+
};
|
|
8139
8295
|
|
|
8140
8296
|
const initDragConstraints = (
|
|
8141
8297
|
dragGesture,
|
|
@@ -8766,47 +8922,99 @@ const createStickyFrontierOnAxis = (
|
|
|
8766
8922
|
|
|
8767
8923
|
const dragStyleController = createStyleController("drag_to_move");
|
|
8768
8924
|
|
|
8925
|
+
/**
|
|
8926
|
+
* Creates a gesture controller that moves elements via drag.
|
|
8927
|
+
*
|
|
8928
|
+
* Wraps `createDragGestureController` and adds:
|
|
8929
|
+
* - Element translation via CSS transform (translate only; other existing transforms are preserved)
|
|
8930
|
+
* - Auto-scroll while dragging near scroll-container edges
|
|
8931
|
+
* - Constraints (area boundaries, obstacle elements)
|
|
8932
|
+
*
|
|
8933
|
+
* The returned controller exposes a `grab(options)` / `grabViaPointer(event, options)` method.
|
|
8934
|
+
* Key grab options:
|
|
8935
|
+
* - `element`: the element whose position drives layout calculations (scroll-container detection,
|
|
8936
|
+
* constraints, auto-scroll). Sets `data-grabbed` during the drag.
|
|
8937
|
+
* - `referenceElement`: optional sticky-frontier / obstacle reference, defaults to `element`.
|
|
8938
|
+
* - `elementToMove`: optional different element to actually translate (e.g. a drag clone).
|
|
8939
|
+
* If omitted, `element` is translated. The translate is read from `dragStyleController`
|
|
8940
|
+
* at grab time so any pre-existing translate is accumulated rather than reset.
|
|
8941
|
+
*
|
|
8942
|
+
* @param {object} [options]
|
|
8943
|
+
* @param {boolean} [options.stickyFrontiers=true]
|
|
8944
|
+
* Shrinks the auto-scroll area at sticky boundaries (elements with `data-sticky-left` /
|
|
8945
|
+
* `data-sticky-top`).
|
|
8946
|
+
* @param {number} [options.autoScrollAreaPadding=0]
|
|
8947
|
+
* Extra padding (px) subtracted from each edge of the auto-scroll trigger area.
|
|
8948
|
+
* @param {string|object|function} [options.areaConstraint="scroll"]
|
|
8949
|
+
* Constrains where the element can be dragged.
|
|
8950
|
+
* `"scroll"` — bounded by the full scroll area.
|
|
8951
|
+
* `"scrollport"` — bounded by the visible viewport of the scroll container.
|
|
8952
|
+
* `"none"` — no area constraint.
|
|
8953
|
+
* `{left, top, right, bottom}` — fixed bounds (values may be functions receiving context).
|
|
8954
|
+
* `function` — called each drag frame, must return a `{left,top,right,bottom}` object.
|
|
8955
|
+
* @param {Element} [options.obstaclesContainer]
|
|
8956
|
+
* Container to look for obstacle elements in. Defaults to the scroll container.
|
|
8957
|
+
* @param {string} [options.obstacleAttributeName="data-drag-obstacle"]
|
|
8958
|
+
* Attribute that marks obstacle elements.
|
|
8959
|
+
* @param {boolean} [options.showConstraintFeedbackLine=false]
|
|
8960
|
+
* Renders a visual line when the pointer deviates from the element due to constraints.
|
|
8961
|
+
* @param {boolean} [options.showDebugMarkers=false]
|
|
8962
|
+
* Renders debug markers for constraint regions.
|
|
8963
|
+
* @param {"commit"|"cancel"|"manual"} [options.releasePositionEffect="commit"]
|
|
8964
|
+
* Controls what happens to the translated position on release.
|
|
8965
|
+
* - `"commit"`: bakes the translate into inline styles so the element stays put (default).
|
|
8966
|
+
* - `"cancel"`: discards the translate so the element snaps back to its original position.
|
|
8967
|
+
* - `"manual"`: does nothing — the caller is responsible for clearing or committing
|
|
8968
|
+
* the transform via `dragStyleController`.
|
|
8969
|
+
* @returns {object} Drag gesture controller with augmented `grab()` / `grabViaPointer()` methods.
|
|
8970
|
+
*/
|
|
8769
8971
|
const createDragToMoveGestureController = ({
|
|
8770
8972
|
stickyFrontiers = true,
|
|
8771
|
-
// Padding to reduce the area used to autoscroll by this amount (applied after sticky frontiers)
|
|
8772
|
-
// This creates an invisible space around the area where elements cannot be dragged
|
|
8773
8973
|
autoScrollAreaPadding = 0,
|
|
8774
|
-
|
|
8775
|
-
areaConstraint = "scroll", // "scroll" | "scrollport" | "none" | {left,top,right,bottom} | function
|
|
8974
|
+
areaConstraint = "scroll",
|
|
8776
8975
|
obstaclesContainer,
|
|
8777
8976
|
obstacleAttributeName = "data-drag-obstacle",
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
// initially grabbed the element, but moves with the element to show the current anchor position.
|
|
8782
|
-
// It becomes visible when there's a significant distance between mouse and grab point.
|
|
8783
|
-
showConstraintFeedbackLine = true,
|
|
8784
|
-
showDebugMarkers = true,
|
|
8785
|
-
resetPositionAfterRelease = false,
|
|
8977
|
+
showConstraintFeedbackLine = false,
|
|
8978
|
+
showDebugMarkers = false,
|
|
8979
|
+
releasePositionEffect = "commit",
|
|
8786
8980
|
...options
|
|
8787
8981
|
} = {}) => {
|
|
8788
8982
|
const initGrabToMoveElement = (
|
|
8789
8983
|
dragGesture,
|
|
8790
8984
|
{ element, referenceElement, elementToMove, convertScrollablePosition },
|
|
8791
8985
|
) => {
|
|
8792
|
-
const direction = dragGesture.gestureInfo.direction;
|
|
8793
|
-
// const dragGestureName = dragGesture.gestureInfo.name;
|
|
8794
8986
|
const scrollContainer = dragGesture.gestureInfo.scrollContainer;
|
|
8987
|
+
|
|
8988
|
+
const direction = dragGesture.gestureInfo.direction;
|
|
8989
|
+
// elementImpacted is either an externally provided elementToMove (e.g. a drag clone)
|
|
8795
8990
|
const elementImpacted = elementToMove || element;
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
const
|
|
8991
|
+
// elementImpacted is either an externally provided elementToMove
|
|
8992
|
+
// (e.g. a drag clone passed by the caller) or the element itself.
|
|
8993
|
+
// Capture any pre-existing translate so we can accumulate on top of it
|
|
8994
|
+
// rather than resetting it to zero on the first drag event.
|
|
8995
|
+
const transformAtGrab = dragStyleController.getUnderlyingValue(
|
|
8801
8996
|
elementImpacted,
|
|
8802
|
-
"transform
|
|
8997
|
+
"transform",
|
|
8803
8998
|
);
|
|
8999
|
+
const translateXAtGrab = transformAtGrab.translateX;
|
|
9000
|
+
const translateYAtGrab = transformAtGrab.translateY;
|
|
9001
|
+
|
|
9002
|
+
const cancelPosition = () => {
|
|
9003
|
+
dragStyleController.clear(elementImpacted);
|
|
9004
|
+
};
|
|
9005
|
+
const commitPosition = () => {
|
|
9006
|
+
dragStyleController.commit(elementImpacted);
|
|
9007
|
+
};
|
|
9008
|
+
dragGesture.gestureInfo.cancelPosition = cancelPosition;
|
|
9009
|
+
dragGesture.gestureInfo.commitPosition = commitPosition;
|
|
9010
|
+
|
|
8804
9011
|
dragGesture.addReleaseCallback(() => {
|
|
8805
|
-
if (
|
|
8806
|
-
|
|
8807
|
-
} else {
|
|
8808
|
-
|
|
9012
|
+
if (releasePositionEffect === "cancel") {
|
|
9013
|
+
cancelPosition();
|
|
9014
|
+
} else if (releasePositionEffect === "commit") {
|
|
9015
|
+
commitPosition();
|
|
8809
9016
|
}
|
|
9017
|
+
// "manual": caller handles cleanup, do nothing.
|
|
8810
9018
|
});
|
|
8811
9019
|
|
|
8812
9020
|
let elementWidth;
|
|
@@ -8823,8 +9031,8 @@ const createDragToMoveGestureController = ({
|
|
|
8823
9031
|
|
|
8824
9032
|
let scrollArea;
|
|
8825
9033
|
{
|
|
8826
|
-
//
|
|
8827
|
-
//
|
|
9034
|
+
// Snapshot at grab time so that DOM mutations during dragging
|
|
9035
|
+
// (e.g. items shifting) don't change the scrollable boundary mid-drag.
|
|
8828
9036
|
scrollArea = {
|
|
8829
9037
|
left: 0,
|
|
8830
9038
|
top: 0,
|
|
@@ -8836,8 +9044,8 @@ const createDragToMoveGestureController = ({
|
|
|
8836
9044
|
let scrollport;
|
|
8837
9045
|
let autoScrollArea;
|
|
8838
9046
|
{
|
|
8839
|
-
//
|
|
8840
|
-
//
|
|
9047
|
+
// scrollBox is the fixed bounding rect of the scroll container viewport.
|
|
9048
|
+
// scrollport is recomputed before each drag event to account for scrolling.
|
|
8841
9049
|
const scrollBox = getScrollBox(scrollContainer);
|
|
8842
9050
|
const updateScrollportAndAutoScrollArea = () => {
|
|
8843
9051
|
scrollport = getScrollport(scrollBox, scrollContainer);
|
|
@@ -8992,7 +9200,10 @@ const createDragToMoveGestureController = ({
|
|
|
8992
9200
|
scrollableLeft,
|
|
8993
9201
|
scrollableTop,
|
|
8994
9202
|
);
|
|
8995
|
-
|
|
9203
|
+
// Build the transform to apply, preserving any transforms that were
|
|
9204
|
+
// already on the element before the grab (e.g. rotate from another
|
|
9205
|
+
// controller), and accumulating from the pre-grab translate baseline.
|
|
9206
|
+
const transform = { ...transformAtGrab };
|
|
8996
9207
|
if (direction.x) {
|
|
8997
9208
|
const leftTarget = positionedLeft;
|
|
8998
9209
|
const leftAtGrab = dragGesture.gestureInfo.leftAtGrab;
|
|
@@ -9001,12 +9212,6 @@ const createDragToMoveGestureController = ({
|
|
|
9001
9212
|
? translateXAtGrab + leftDelta
|
|
9002
9213
|
: leftDelta;
|
|
9003
9214
|
transform.translateX = translateX;
|
|
9004
|
-
// console.log({
|
|
9005
|
-
// leftAtGrab,
|
|
9006
|
-
// scrollableLeft,
|
|
9007
|
-
// left,
|
|
9008
|
-
// leftTarget,
|
|
9009
|
-
// });
|
|
9010
9215
|
}
|
|
9011
9216
|
if (direction.y) {
|
|
9012
9217
|
const topTarget = positionedTop;
|
|
@@ -9031,6 +9236,7 @@ const createDragToMoveGestureController = ({
|
|
|
9031
9236
|
element,
|
|
9032
9237
|
referenceElement,
|
|
9033
9238
|
elementToMove,
|
|
9239
|
+
event,
|
|
9034
9240
|
...rest
|
|
9035
9241
|
} = {}) => {
|
|
9036
9242
|
const scrollContainer = getScrollContainer(referenceElement || element);
|
|
@@ -9044,6 +9250,7 @@ const createDragToMoveGestureController = ({
|
|
|
9044
9250
|
scrollContainer,
|
|
9045
9251
|
layoutScrollableLeft: elementScrollableLeft,
|
|
9046
9252
|
layoutScrollableTop: elementScrollableTop,
|
|
9253
|
+
event,
|
|
9047
9254
|
...rest,
|
|
9048
9255
|
});
|
|
9049
9256
|
initGrabToMoveElement(dragGesture, {
|
|
@@ -9058,82 +9265,26 @@ const createDragToMoveGestureController = ({
|
|
|
9058
9265
|
return dragGestureController;
|
|
9059
9266
|
};
|
|
9060
9267
|
|
|
9061
|
-
const startDragToResizeGesture = (
|
|
9062
|
-
pointerdownEvent,
|
|
9063
|
-
{ onDragStart, onDrag, onRelease, ...options },
|
|
9064
|
-
) => {
|
|
9065
|
-
const target = pointerdownEvent.target;
|
|
9066
|
-
if (!target.closest) {
|
|
9067
|
-
return null;
|
|
9068
|
-
}
|
|
9069
|
-
const elementWithDataResizeHandle = target.closest("[data-resize-handle]");
|
|
9070
|
-
if (!elementWithDataResizeHandle) {
|
|
9071
|
-
return null;
|
|
9072
|
-
}
|
|
9073
|
-
let elementToResize;
|
|
9074
|
-
const dataResizeHandle =
|
|
9075
|
-
elementWithDataResizeHandle.getAttribute("data-resize-handle");
|
|
9076
|
-
if (!dataResizeHandle || dataResizeHandle === "true") {
|
|
9077
|
-
elementToResize = elementWithDataResizeHandle.closest("[data-resize]");
|
|
9078
|
-
} else {
|
|
9079
|
-
elementToResize = document.querySelector(`#${dataResizeHandle}`);
|
|
9080
|
-
}
|
|
9081
|
-
if (!elementToResize) {
|
|
9082
|
-
console.warn("No element to resize found");
|
|
9083
|
-
return null;
|
|
9084
|
-
}
|
|
9085
|
-
// inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/resize
|
|
9086
|
-
// "horizontal", "vertical", "both"
|
|
9087
|
-
const resizeDirection = getResizeDirection(elementToResize);
|
|
9088
|
-
if (!resizeDirection.x && !resizeDirection.y) {
|
|
9089
|
-
return null;
|
|
9090
|
-
}
|
|
9091
|
-
|
|
9092
|
-
const dragToResizeGestureController = createDragGestureController({
|
|
9093
|
-
onDragStart: (...args) => {
|
|
9094
|
-
onDragStart?.(...args);
|
|
9095
|
-
},
|
|
9096
|
-
onDrag,
|
|
9097
|
-
onRelease: (...args) => {
|
|
9098
|
-
elementWithDataResizeHandle.removeAttribute("data-active");
|
|
9099
|
-
onRelease?.(...args);
|
|
9100
|
-
},
|
|
9101
|
-
});
|
|
9102
|
-
elementWithDataResizeHandle.setAttribute("data-active", "");
|
|
9103
|
-
const dragToResizeGesture = dragToResizeGestureController.grabViaPointer(
|
|
9104
|
-
pointerdownEvent,
|
|
9105
|
-
{
|
|
9106
|
-
element: elementToResize,
|
|
9107
|
-
direction: resizeDirection,
|
|
9108
|
-
cursor:
|
|
9109
|
-
resizeDirection.x && resizeDirection.y
|
|
9110
|
-
? "nwse-resize"
|
|
9111
|
-
: resizeDirection.x
|
|
9112
|
-
? "ew-resize"
|
|
9113
|
-
: "ns-resize",
|
|
9114
|
-
...options,
|
|
9115
|
-
},
|
|
9116
|
-
);
|
|
9117
|
-
return dragToResizeGesture;
|
|
9118
|
-
};
|
|
9119
|
-
|
|
9120
|
-
const getResizeDirection = (element) => {
|
|
9121
|
-
const direction = element.getAttribute("data-resize");
|
|
9122
|
-
const x = direction === "horizontal" || direction === "both";
|
|
9123
|
-
const y = direction === "vertical" || direction === "both";
|
|
9124
|
-
return { x, y };
|
|
9125
|
-
};
|
|
9126
|
-
|
|
9127
9268
|
/**
|
|
9128
9269
|
* Detects the drop target based on what element is actually under the mouse cursor.
|
|
9129
9270
|
* Uses document.elementsFromPoint() to respect visual stacking order naturally.
|
|
9130
9271
|
*
|
|
9131
9272
|
* @param {Object} gestureInfo - Gesture information
|
|
9132
9273
|
* @param {Element[]} targetElements - Array of potential drop target elements
|
|
9274
|
+
* @param {object} [options]
|
|
9275
|
+
* @param {Element} [options.dragElement] - The element being dragged. When provided and
|
|
9276
|
+
* `fallbackToEdge` is true, used to compute the fallback rect.
|
|
9277
|
+
* @param {boolean} [options.fallbackToEdge=false] - When true and the drag element does
|
|
9278
|
+
* not intersect any target, falls back to the first item (if above all items) or the
|
|
9279
|
+
* last item (if below all items) so there is always a valid drop target at list edges.
|
|
9133
9280
|
* @returns {Object|null} Drop target info with elementSide or null if no valid target found
|
|
9134
9281
|
*/
|
|
9135
|
-
const getDropTargetInfo = (
|
|
9136
|
-
|
|
9282
|
+
const getDropTargetInfo = (
|
|
9283
|
+
gestureInfo,
|
|
9284
|
+
targetElements,
|
|
9285
|
+
{ fallbackToEdge = false } = {},
|
|
9286
|
+
) => {
|
|
9287
|
+
const dragElement = gestureInfo.elementImpacted || gestureInfo.element;
|
|
9137
9288
|
const dragElementRect = dragElement.getBoundingClientRect();
|
|
9138
9289
|
const intersectingTargets = [];
|
|
9139
9290
|
let someTargetIsCol;
|
|
@@ -9153,6 +9304,38 @@ const getDropTargetInfo = (gestureInfo, targetElements) => {
|
|
|
9153
9304
|
}
|
|
9154
9305
|
|
|
9155
9306
|
if (intersectingTargets.length === 0) {
|
|
9307
|
+
if (fallbackToEdge) {
|
|
9308
|
+
const dragElement = gestureInfo.elementImpacted || gestureInfo.element;
|
|
9309
|
+
const dragElementRect = dragElement.getBoundingClientRect();
|
|
9310
|
+
const firstItem = targetElements[0];
|
|
9311
|
+
const lastItem = targetElements[targetElements.length - 1];
|
|
9312
|
+
if (
|
|
9313
|
+
firstItem &&
|
|
9314
|
+
dragElementRect.bottom < firstItem.getBoundingClientRect().top
|
|
9315
|
+
) {
|
|
9316
|
+
// Drag element is above all items → treat as hovering the first item from the top.
|
|
9317
|
+
return {
|
|
9318
|
+
element: firstItem,
|
|
9319
|
+
elementSide: { x: "start", y: "start" },
|
|
9320
|
+
index: 0,
|
|
9321
|
+
intersectingIndex: 0,
|
|
9322
|
+
intersecting: [firstItem],
|
|
9323
|
+
};
|
|
9324
|
+
}
|
|
9325
|
+
if (
|
|
9326
|
+
lastItem &&
|
|
9327
|
+
dragElementRect.top > lastItem.getBoundingClientRect().bottom
|
|
9328
|
+
) {
|
|
9329
|
+
// Drag element is below all items → treat as hovering the last item from the bottom.
|
|
9330
|
+
return {
|
|
9331
|
+
element: lastItem,
|
|
9332
|
+
elementSide: { x: "start", y: "end" },
|
|
9333
|
+
index: targetElements.length - 1,
|
|
9334
|
+
intersectingIndex: 0,
|
|
9335
|
+
intersecting: [lastItem],
|
|
9336
|
+
};
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9156
9339
|
return null;
|
|
9157
9340
|
}
|
|
9158
9341
|
|
|
@@ -9178,12 +9361,13 @@ const getDropTargetInfo = (gestureInfo, targetElements) => {
|
|
|
9178
9361
|
const elementsUnderDragElement = document.elementsFromPoint(clientX, clientY);
|
|
9179
9362
|
let targetElement = null;
|
|
9180
9363
|
let targetIndex = -1;
|
|
9364
|
+
let intersectingIndex = -1;
|
|
9181
9365
|
for (const element of elementsUnderDragElement) {
|
|
9182
9366
|
// First, check if the element itself is a target
|
|
9183
9367
|
const directIndex = intersectingTargets.indexOf(element);
|
|
9184
9368
|
if (directIndex !== -1) {
|
|
9185
9369
|
targetElement = element;
|
|
9186
|
-
|
|
9370
|
+
intersectingIndex = directIndex;
|
|
9187
9371
|
break;
|
|
9188
9372
|
}
|
|
9189
9373
|
// Special case: if element is <td> or <th> and not in targets,
|
|
@@ -9204,7 +9388,7 @@ const getDropTargetInfo = (gestureInfo, targetElements) => {
|
|
|
9204
9388
|
break try_col;
|
|
9205
9389
|
}
|
|
9206
9390
|
targetElement = tableCellCol;
|
|
9207
|
-
|
|
9391
|
+
intersectingIndex = colIndex;
|
|
9208
9392
|
break;
|
|
9209
9393
|
}
|
|
9210
9394
|
try_tr: {
|
|
@@ -9217,26 +9401,60 @@ const getDropTargetInfo = (gestureInfo, targetElements) => {
|
|
|
9217
9401
|
break try_tr;
|
|
9218
9402
|
}
|
|
9219
9403
|
targetElement = tableRow;
|
|
9220
|
-
|
|
9404
|
+
intersectingIndex = intersectingTargets.indexOf(tableRow);
|
|
9221
9405
|
break;
|
|
9222
9406
|
}
|
|
9223
9407
|
}
|
|
9224
9408
|
if (!targetElement) {
|
|
9225
9409
|
targetElement = intersectingTargets[0];
|
|
9226
|
-
|
|
9227
|
-
}
|
|
9228
|
-
|
|
9229
|
-
|
|
9410
|
+
intersectingIndex = 0;
|
|
9411
|
+
}
|
|
9412
|
+
targetIndex = targetElements.indexOf(targetElement);
|
|
9413
|
+
|
|
9414
|
+
// Determine position within the target for both axes.
|
|
9415
|
+
//
|
|
9416
|
+
// Use the leading edge of the dragged element (in the direction of movement)
|
|
9417
|
+
// compared against the target's center:
|
|
9418
|
+
// - Dragging down: "after" as soon as the bottom crosses the target center.
|
|
9419
|
+
// - Dragging up: "before" as soon as the top crosses the target center.
|
|
9420
|
+
// - Not moving: center-vs-center fallback.
|
|
9421
|
+
//
|
|
9422
|
+
// This gives consistent, predictable thresholds regardless of element size.
|
|
9230
9423
|
const targetRect = targetElement.getBoundingClientRect();
|
|
9231
9424
|
const targetCenterX = targetRect.left + targetRect.width / 2;
|
|
9232
9425
|
const targetCenterY = targetRect.top + targetRect.height / 2;
|
|
9426
|
+
const { intentGoingDown, intentGoingUp, intentGoingRight, intentGoingLeft } =
|
|
9427
|
+
gestureInfo;
|
|
9428
|
+
let sideY;
|
|
9429
|
+
if (intentGoingDown) {
|
|
9430
|
+
sideY = dragElementRect.bottom > targetCenterY ? "end" : "start";
|
|
9431
|
+
} else if (intentGoingUp) {
|
|
9432
|
+
sideY = dragElementRect.top < targetCenterY ? "start" : "end";
|
|
9433
|
+
} else {
|
|
9434
|
+
sideY = dragElementCenterY < targetCenterY ? "start" : "end";
|
|
9435
|
+
}
|
|
9436
|
+
let sideX;
|
|
9437
|
+
if (intentGoingRight) {
|
|
9438
|
+
sideX = dragElementRect.right > targetCenterX ? "end" : "start";
|
|
9439
|
+
} else if (intentGoingLeft) {
|
|
9440
|
+
sideX = dragElementRect.left < targetCenterX ? "start" : "end";
|
|
9441
|
+
} else {
|
|
9442
|
+
sideX = dragElementCenterX < targetCenterX ? "start" : "end";
|
|
9443
|
+
}
|
|
9233
9444
|
const result = {
|
|
9445
|
+
// NOTE: avoid relying on `index` in application code. The targetElements
|
|
9446
|
+
// array may be dynamically filtered (e.g. excluding the grabbed element),
|
|
9447
|
+
// making this index inconsistent with the full list. Use `element` instead
|
|
9448
|
+
// and look up its position yourself from your own data source.
|
|
9234
9449
|
index: targetIndex,
|
|
9235
9450
|
element: targetElement,
|
|
9236
9451
|
elementSide: {
|
|
9237
|
-
x:
|
|
9238
|
-
y:
|
|
9452
|
+
x: sideX,
|
|
9453
|
+
y: sideY,
|
|
9239
9454
|
},
|
|
9455
|
+
// Index within the intersecting subset — could be useful to know how many
|
|
9456
|
+
// elements were overlapping, but rarely needed in practice
|
|
9457
|
+
intersectingIndex,
|
|
9240
9458
|
intersecting: intersectingTargets,
|
|
9241
9459
|
};
|
|
9242
9460
|
return result;
|
|
@@ -9273,6 +9491,407 @@ const findTableCellCol = (cellElement) => {
|
|
|
9273
9491
|
return correspondingCol;
|
|
9274
9492
|
};
|
|
9275
9493
|
|
|
9494
|
+
// Temporarily attach to the element so inherited CSS vars resolve correctly,
|
|
9495
|
+
// then snapshot all drop-hint custom properties onto the scroll container
|
|
9496
|
+
// so they survive once the element moves to the scroll container.
|
|
9497
|
+
const moveCSSVars = (vars, fromEl, toEl) => {
|
|
9498
|
+
const fromComputedStyle = getComputedStyle(fromEl);
|
|
9499
|
+
const savedVars = {};
|
|
9500
|
+
for (const varName of vars) {
|
|
9501
|
+
const value = fromComputedStyle.getPropertyValue(varName).trim();
|
|
9502
|
+
if (value) {
|
|
9503
|
+
savedVars[varName] = toEl.style.getPropertyValue(varName);
|
|
9504
|
+
toEl.style.setProperty(varName, value);
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
|
|
9508
|
+
return () => {
|
|
9509
|
+
for (const varName of vars) {
|
|
9510
|
+
if (varName in savedVars) {
|
|
9511
|
+
if (savedVars[varName]) {
|
|
9512
|
+
toEl.style.setProperty(varName, savedVars[varName]);
|
|
9513
|
+
} else {
|
|
9514
|
+
toEl.style.removeProperty(varName);
|
|
9515
|
+
}
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9518
|
+
};
|
|
9519
|
+
};
|
|
9520
|
+
|
|
9521
|
+
installImportMetaCssBuild(import.meta);const css$1 = /* css */`
|
|
9522
|
+
.navi_drop_hint {
|
|
9523
|
+
position: absolute;
|
|
9524
|
+
top: var(--drop-hint-y);
|
|
9525
|
+
left: calc(var(--drop-target-left) + var(--drop-hint-margin-x, 0px));
|
|
9526
|
+
z-index: 10;
|
|
9527
|
+
display: none;
|
|
9528
|
+
width: calc(var(--drop-target-width) - 2 * var(--drop-hint-margin-x, 0px));
|
|
9529
|
+
height: var(--drop-hint-size, 3px);
|
|
9530
|
+
background: var(--drop-hint-background-color, #4476ff);
|
|
9531
|
+
border-radius: var(--drop-hint-border-radius, 2px);
|
|
9532
|
+
transform: translateY(-50%);
|
|
9533
|
+
pointer-events: none;
|
|
9534
|
+
}
|
|
9535
|
+
[data-drop-edge="top"] > .navi_drop_hint {
|
|
9536
|
+
display: block;
|
|
9537
|
+
--drop-hint-y: calc(
|
|
9538
|
+
var(--drop-target-top) - var(--drop-hint-margin-y, 0px)
|
|
9539
|
+
);
|
|
9540
|
+
}
|
|
9541
|
+
[data-drop-edge="bottom"] > .navi_drop_hint {
|
|
9542
|
+
display: block;
|
|
9543
|
+
--drop-hint-y: calc(
|
|
9544
|
+
var(--drop-target-bottom) + var(--drop-hint-margin-y, 0px)
|
|
9545
|
+
);
|
|
9546
|
+
}
|
|
9547
|
+
|
|
9548
|
+
[navi-drag-clone-source] {
|
|
9549
|
+
visibility: hidden;
|
|
9550
|
+
}
|
|
9551
|
+
|
|
9552
|
+
[navi-drag-clone-wrapper] {
|
|
9553
|
+
position: absolute;
|
|
9554
|
+
top: var(--clone-top);
|
|
9555
|
+
left: var(--clone-left);
|
|
9556
|
+
z-index: 9999;
|
|
9557
|
+
width: var(--clone-width);
|
|
9558
|
+
height: var(--clone-height);
|
|
9559
|
+
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
|
|
9560
|
+
opacity: 0.95;
|
|
9561
|
+
transition: box-shadow 0.15s ease;
|
|
9562
|
+
pointer-events: none;
|
|
9563
|
+
}
|
|
9564
|
+
|
|
9565
|
+
[navi-drag-clone] {
|
|
9566
|
+
transform: scale(var(--drag-clone-scale, 1.03));
|
|
9567
|
+
transform-origin: var(--drag-origin);
|
|
9568
|
+
transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
9569
|
+
}
|
|
9570
|
+
|
|
9571
|
+
@starting-style {
|
|
9572
|
+
[navi-drag-clone-wrapper] {
|
|
9573
|
+
box-shadow: none;
|
|
9574
|
+
}
|
|
9575
|
+
|
|
9576
|
+
[navi-drag-clone] {
|
|
9577
|
+
transform: scale(1);
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
`;
|
|
9581
|
+
const dragCSSVars = ["--drop-hint-size", "--drop-hint-background-color", "--drop-hint-border-radius", "--drop-hint-margin-x", "--drop-hint-margin-y", "--drag-clone-scale"];
|
|
9582
|
+
|
|
9583
|
+
/**
|
|
9584
|
+
* Starts a drag-to-reorder interaction on a list item.
|
|
9585
|
+
*
|
|
9586
|
+
* Handles the full reorder UX:
|
|
9587
|
+
* - Activates only after a short movement threshold (avoids accidental reorders on clicks).
|
|
9588
|
+
* - Clones the grabbed element and moves the clone while the original stays hidden in place
|
|
9589
|
+
* (keeps the layout intact so other items don't shift during the drag).
|
|
9590
|
+
* - CSS vars (`--drop-hint-size`, `--drop-hint-background-color`, etc.) are read from the
|
|
9591
|
+
* dragged element and moved to `document.documentElement` for the duration of the drag so
|
|
9592
|
+
* the drop-hint and clone — both in `document.body` — can inherit them.
|
|
9593
|
+
* - Shows a drop-hint line indicating where the item will land.
|
|
9594
|
+
* - Drop-target detection is intersection-based: the clone's bounding rect is compared
|
|
9595
|
+
* against every item that matches `itemSelector` in the scroll container.
|
|
9596
|
+
* - No-ops are filtered: releasing on the grabbed element itself, or in a position that
|
|
9597
|
+
* would leave it at exactly the same index, never triggers `onReorder`.
|
|
9598
|
+
* - On a valid drop, the clone animates to the drop position via the View Transitions API,
|
|
9599
|
+
* `onReorder` is called inside the transition callback so the DOM update and the animation
|
|
9600
|
+
* are captured together, then the clone is removed.
|
|
9601
|
+
* - On a cancelled drop (pointer released with no valid target), the clone is removed
|
|
9602
|
+
* immediately without calling `onReorder`.
|
|
9603
|
+
*
|
|
9604
|
+
* IDs are used as the bridge between DOM elements and JS state because:
|
|
9605
|
+
* - Not all DOM elements matching `itemSelector` may be valid drop targets
|
|
9606
|
+
* (holes in the structure), so DOM indices don't reliably map to state indices.
|
|
9607
|
+
* - Virtual lists render fewer DOM nodes than the total item count, so
|
|
9608
|
+
* DOM-index-based counting would be wrong.
|
|
9609
|
+
*
|
|
9610
|
+
* @param {PointerEvent} event
|
|
9611
|
+
* The `pointerdown` event from the drag handle.
|
|
9612
|
+
* @param {Element} draggedElement
|
|
9613
|
+
* The list item element to drag. Typically `event.currentTarget`.
|
|
9614
|
+
* @param {object} options
|
|
9615
|
+
* @param {string} options.itemSelector
|
|
9616
|
+
* CSS selector that matches all list items inside the scroll container.
|
|
9617
|
+
* Used for drop-target detection and no-op filtering.
|
|
9618
|
+
* @param {function} options.getItemId
|
|
9619
|
+
* Returns the stable ID for a given DOM element.
|
|
9620
|
+
* Signature: `getItemId(element) → id`.
|
|
9621
|
+
* @param {function} options.onReorder
|
|
9622
|
+
* Called when the user drops the item in a new position.
|
|
9623
|
+
* Signature: `onReorder(fromId, toId)`.
|
|
9624
|
+
* - `fromId`: stable ID of the dragged item.
|
|
9625
|
+
* - `toId`: stable ID of the item to insert before, or `null` to append at the end.
|
|
9626
|
+
* Called inside `document.startViewTransition` so the resulting DOM mutation is
|
|
9627
|
+
* animated by the View Transitions API.
|
|
9628
|
+
* @param {object} [options.direction={ x: false, y: true }]
|
|
9629
|
+
* Axes along which dragging is allowed. Passed to `createDragToMoveGestureController`.
|
|
9630
|
+
* @param {...*} [options]
|
|
9631
|
+
* Any remaining options are forwarded to `createDragToMoveGestureController`
|
|
9632
|
+
* (e.g. `areaConstraint`, `autoScrollAreaPadding`, `stickyFrontiers`).
|
|
9633
|
+
* `releasePositionEffect` is always set to `"manual"` internally and cannot be overridden.
|
|
9634
|
+
*/
|
|
9635
|
+
const startDragToReorder = (event, draggedElement, {
|
|
9636
|
+
itemSelector,
|
|
9637
|
+
containerElement,
|
|
9638
|
+
getItemId,
|
|
9639
|
+
onReorder,
|
|
9640
|
+
direction = {
|
|
9641
|
+
x: false,
|
|
9642
|
+
y: true
|
|
9643
|
+
},
|
|
9644
|
+
...options
|
|
9645
|
+
}) => {
|
|
9646
|
+
import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to_reorder.js"];
|
|
9647
|
+
event.preventDefault();
|
|
9648
|
+
return dragAfterThreshold(event, () => {
|
|
9649
|
+
const cloneWrapper = createDragClone(draggedElement, event);
|
|
9650
|
+
draggedElement.setAttribute("navi-drag-clone-source", "");
|
|
9651
|
+
// Move drag related CSS vars from the element to the document
|
|
9652
|
+
// so they're accessible to .navi_drop_hint and the clone (which are both in document.body)
|
|
9653
|
+
const restoreCSSVars = moveCSSVars(dragCSSVars, draggedElement, document.documentElement);
|
|
9654
|
+
const gestureController = createDragToMoveGestureController({
|
|
9655
|
+
direction,
|
|
9656
|
+
releasePositionEffect: "manual",
|
|
9657
|
+
...options
|
|
9658
|
+
});
|
|
9659
|
+
const dragGesture = gestureController.grabViaPointer(event, {
|
|
9660
|
+
element: draggedElement,
|
|
9661
|
+
elementToMove: cloneWrapper
|
|
9662
|
+
});
|
|
9663
|
+
// getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
|
|
9664
|
+
// Point it at the clone so drop detection tracks the clone's current position.
|
|
9665
|
+
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
9666
|
+
const scrollContainer = dragGesture.gestureInfo.scrollContainer;
|
|
9667
|
+
const dropHintEl = document.createElement("div");
|
|
9668
|
+
dropHintEl.className = "navi_drop_hint";
|
|
9669
|
+
scrollContainer.appendChild(dropHintEl);
|
|
9670
|
+
|
|
9671
|
+
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
9672
|
+
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
9673
|
+
let currentBeforeElement;
|
|
9674
|
+
let currentReleaseElement;
|
|
9675
|
+
const clearDropHintDOM = () => {
|
|
9676
|
+
scrollContainer.removeAttribute("data-drop-edge");
|
|
9677
|
+
scrollContainer.style.removeProperty("--drop-target-top");
|
|
9678
|
+
scrollContainer.style.removeProperty("--drop-target-bottom");
|
|
9679
|
+
scrollContainer.style.removeProperty("--drop-target-left");
|
|
9680
|
+
scrollContainer.style.removeProperty("--drop-target-width");
|
|
9681
|
+
};
|
|
9682
|
+
const clearDropHint = () => {
|
|
9683
|
+
currentBeforeElement = undefined;
|
|
9684
|
+
currentReleaseElement = undefined;
|
|
9685
|
+
clearDropHintDOM();
|
|
9686
|
+
};
|
|
9687
|
+
const itemsContainer = containerElement || draggedElement.parentElement;
|
|
9688
|
+
dragGesture.addDragCallback(gestureInfo => {
|
|
9689
|
+
const allItems = [];
|
|
9690
|
+
const items = [];
|
|
9691
|
+
for (const el of itemsContainer.querySelectorAll(itemSelector)) {
|
|
9692
|
+
allItems.push(el);
|
|
9693
|
+
if (el !== draggedElement) {
|
|
9694
|
+
items.push(el);
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
9698
|
+
fallbackToEdge: true
|
|
9699
|
+
});
|
|
9700
|
+
gestureInfo.dropTargetInfo = dropTargetInfo || null;
|
|
9701
|
+
if (!dropTargetInfo) {
|
|
9702
|
+
clearDropHint();
|
|
9703
|
+
return;
|
|
9704
|
+
}
|
|
9705
|
+
// Convert {element, edge} to a beforeElement using the items array
|
|
9706
|
+
// (not nextElementSibling, which breaks if non-item elements exist between items).
|
|
9707
|
+
// edge "start" → insert before the hovered element
|
|
9708
|
+
// edge "end" → insert before the next item (null = append at end)
|
|
9709
|
+
const edge = dropTargetInfo.elementSide.y;
|
|
9710
|
+
const hoveredIndex = items.indexOf(dropTargetInfo.element);
|
|
9711
|
+
const beforeElement = edge === "start" ? dropTargetInfo.element : items[hoveredIndex + 1] ?? null;
|
|
9712
|
+
// Detect no-op: result would leave the grabbed element in the same position.
|
|
9713
|
+
const elementIndex = allItems.indexOf(draggedElement);
|
|
9714
|
+
const elementNextItem = allItems[elementIndex + 1] ?? null;
|
|
9715
|
+
const isNoop = beforeElement === elementNextItem;
|
|
9716
|
+
if (isNoop) {
|
|
9717
|
+
clearDropHint();
|
|
9718
|
+
return;
|
|
9719
|
+
}
|
|
9720
|
+
// Early return if nothing changed.
|
|
9721
|
+
const releaseElement = dropTargetInfo.element;
|
|
9722
|
+
if (beforeElement === currentBeforeElement && releaseElement === currentReleaseElement) {
|
|
9723
|
+
return;
|
|
9724
|
+
}
|
|
9725
|
+
currentBeforeElement = beforeElement;
|
|
9726
|
+
currentReleaseElement = releaseElement;
|
|
9727
|
+
// Update drop hint CSS vars.
|
|
9728
|
+
// beforeElement = null → insert at end (hint after last item)
|
|
9729
|
+
// beforeElement = X → insert before X (hint at top edge of X)
|
|
9730
|
+
const anchorEl = beforeElement || items[items.length - 1];
|
|
9731
|
+
const anchorEdge = beforeElement !== null ? "top" : "bottom";
|
|
9732
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
9733
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
9734
|
+
const isPositioned = getComputedStyle(scrollContainer).position !== "static";
|
|
9735
|
+
const scrollOffsetLeft = isPositioned ? scrollContainer.scrollLeft : 0;
|
|
9736
|
+
const scrollOffsetTop = isPositioned ? scrollContainer.scrollTop : 0;
|
|
9737
|
+
scrollContainer.setAttribute("data-drop-edge", anchorEdge);
|
|
9738
|
+
scrollContainer.style.setProperty("--drop-target-top", `${anchorRect.top - containerRect.top + scrollOffsetTop}px`);
|
|
9739
|
+
scrollContainer.style.setProperty("--drop-target-bottom", `${anchorRect.bottom - containerRect.top + scrollOffsetTop}px`);
|
|
9740
|
+
scrollContainer.style.setProperty("--drop-target-left", `${anchorRect.left - containerRect.left + scrollOffsetLeft}px`);
|
|
9741
|
+
scrollContainer.style.setProperty("--drop-target-width", `${anchorRect.width}px`);
|
|
9742
|
+
});
|
|
9743
|
+
dragGesture.addReleaseCallback(async gestureInfo => {
|
|
9744
|
+
clearDropHintDOM();
|
|
9745
|
+
dropHintEl.remove();
|
|
9746
|
+
restoreCSSVars();
|
|
9747
|
+
if (currentBeforeElement !== undefined) {
|
|
9748
|
+
const clone = cloneWrapper.firstElementChild;
|
|
9749
|
+
// Bake the current visual position (transform included) into the CSS vars
|
|
9750
|
+
// so the clone stays where the user released it when we clear the transform.
|
|
9751
|
+
setCloneDocumentRect(cloneWrapper, cloneWrapper);
|
|
9752
|
+
gestureInfo.cancelPosition();
|
|
9753
|
+
const fromId = getItemId(draggedElement);
|
|
9754
|
+
const toId = currentBeforeElement ? getItemId(currentBeforeElement) : null;
|
|
9755
|
+
// provide onReorder a way to synchronously move the clone to the drop target
|
|
9756
|
+
// (meant to be used inside a startViewTransition callback)
|
|
9757
|
+
const syncCloneWithDropTarget = () => {
|
|
9758
|
+
// Snap the CSS-var position to the drop target rect so the browser
|
|
9759
|
+
// captures the "new" state at the landing position.
|
|
9760
|
+
setCloneDocumentRect(cloneWrapper, currentReleaseElement);
|
|
9761
|
+
// Removing this attr drops the CSS scale(1.15), so the browser
|
|
9762
|
+
// captures the clone at scale 1 as the "new" state.
|
|
9763
|
+
clone.removeAttribute("navi-drag-clone");
|
|
9764
|
+
};
|
|
9765
|
+
await onReorder(fromId, toId, syncCloneWithDropTarget);
|
|
9766
|
+
}
|
|
9767
|
+
draggedElement.removeAttribute("navi-drag-clone-source");
|
|
9768
|
+
cloneWrapper.remove();
|
|
9769
|
+
});
|
|
9770
|
+
return dragGesture;
|
|
9771
|
+
});
|
|
9772
|
+
};
|
|
9773
|
+
|
|
9774
|
+
// getBoundingClientRect() returns viewport-relative coords.
|
|
9775
|
+
// The clone wrapper is position:absolute inside document.body, so we need
|
|
9776
|
+
// document-relative coords (viewport coords + current page scroll).
|
|
9777
|
+
const setCloneDocumentRect = (cloneWrapper, el) => {
|
|
9778
|
+
const rect = el.getBoundingClientRect();
|
|
9779
|
+
const scrollLeft = document.documentElement.scrollLeft;
|
|
9780
|
+
const scrollTop = document.documentElement.scrollTop;
|
|
9781
|
+
cloneWrapper.style.setProperty("--clone-top", `${rect.top + scrollTop}px`);
|
|
9782
|
+
cloneWrapper.style.setProperty("--clone-left", `${rect.left + scrollLeft}px`);
|
|
9783
|
+
cloneWrapper.style.setProperty("--clone-width", `${rect.width}px`);
|
|
9784
|
+
cloneWrapper.style.setProperty("--clone-height", `${rect.height}px`);
|
|
9785
|
+
};
|
|
9786
|
+
|
|
9787
|
+
// Creates the two-layer clone structure used for drag-to-reorder.
|
|
9788
|
+
//
|
|
9789
|
+
// Layer 1 — wrapper (navi-drag-clone-wrapper):
|
|
9790
|
+
// Positioned absolutely via --clone-top/--clone-left CSS vars.
|
|
9791
|
+
// Carries the box-shadow and size. Moved every drag frame via dragStyleController.
|
|
9792
|
+
// Has a view-transition-name so the View Transitions API can animate it on release.
|
|
9793
|
+
//
|
|
9794
|
+
// Layer 2 — inner clone (navi-drag-clone):
|
|
9795
|
+
// A deep clone of the grabbed element.
|
|
9796
|
+
// Applies transform: scale(1.15) via the CSS rule for [navi-drag-clone],
|
|
9797
|
+
// giving the "lifted" feel. The transform-origin is set to the grab point
|
|
9798
|
+
// so the element expands naturally from where the user clicked.
|
|
9799
|
+
// On release, the `navi-drag-clone` attribute is removed inside
|
|
9800
|
+
// startViewTransition to drop the scale back to 1 as the "new" state.
|
|
9801
|
+
const createDragClone = (element, pointerEvent) => {
|
|
9802
|
+
const rect = element.getBoundingClientRect();
|
|
9803
|
+
const wrapper = document.createElement("div");
|
|
9804
|
+
wrapper.setAttribute("navi-drag-clone-wrapper", "");
|
|
9805
|
+
wrapper.viewTransitionName = "navi-drag-clone-wrapper";
|
|
9806
|
+
setCloneDocumentRect(wrapper, element);
|
|
9807
|
+
// Grab point within the element — used as transform-origin so the
|
|
9808
|
+
// scale(1.15) expands from where the user clicked, not the element center.
|
|
9809
|
+
// These offsets are element-relative so viewport coords are correct here.
|
|
9810
|
+
wrapper.style.setProperty("--drag-origin", `${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`);
|
|
9811
|
+
// The clone is appended to document.body, so it loses inherited styles
|
|
9812
|
+
// from the original parent. Copy the computed inherited properties that
|
|
9813
|
+
// are most likely to affect visual appearance.
|
|
9814
|
+
const computedStyle = getComputedStyle(element.parentElement);
|
|
9815
|
+
for (const property of INHERITED_PROPERTIES_TO_COPY_SET) {
|
|
9816
|
+
wrapper.style.setProperty(property, computedStyle.getPropertyValue(property));
|
|
9817
|
+
}
|
|
9818
|
+
const elementClone = element.cloneNode(true);
|
|
9819
|
+
elementClone.setAttribute("navi-drag-clone", "");
|
|
9820
|
+
elementClone.style.viewTransitionName = "navi-drag-clone";
|
|
9821
|
+
wrapper.appendChild(elementClone);
|
|
9822
|
+
document.body.appendChild(wrapper);
|
|
9823
|
+
return wrapper;
|
|
9824
|
+
};
|
|
9825
|
+
const INHERITED_PROPERTIES_TO_COPY_SET = new Set(["color", "font-family", "font-size", "font-weight", "font-style", "line-height", "letter-spacing",
|
|
9826
|
+
// in case the item has border-radius: inherit. The clone can inherit too
|
|
9827
|
+
"border-radius"]);
|
|
9828
|
+
|
|
9829
|
+
const startDragToResizeGesture = (
|
|
9830
|
+
pointerdownEvent,
|
|
9831
|
+
{ onDragStart, onDrag, onRelease, ...options },
|
|
9832
|
+
) => {
|
|
9833
|
+
const target = pointerdownEvent.target;
|
|
9834
|
+
if (!target.closest) {
|
|
9835
|
+
return null;
|
|
9836
|
+
}
|
|
9837
|
+
const elementWithDataResizeHandle = target.closest("[data-resize-handle]");
|
|
9838
|
+
if (!elementWithDataResizeHandle) {
|
|
9839
|
+
return null;
|
|
9840
|
+
}
|
|
9841
|
+
let elementToResize;
|
|
9842
|
+
const dataResizeHandle =
|
|
9843
|
+
elementWithDataResizeHandle.getAttribute("data-resize-handle");
|
|
9844
|
+
if (!dataResizeHandle || dataResizeHandle === "true") {
|
|
9845
|
+
elementToResize = elementWithDataResizeHandle.closest("[data-resize]");
|
|
9846
|
+
} else {
|
|
9847
|
+
elementToResize = document.querySelector(`#${dataResizeHandle}`);
|
|
9848
|
+
}
|
|
9849
|
+
if (!elementToResize) {
|
|
9850
|
+
console.warn("No element to resize found");
|
|
9851
|
+
return null;
|
|
9852
|
+
}
|
|
9853
|
+
// inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/resize
|
|
9854
|
+
// "horizontal", "vertical", "both"
|
|
9855
|
+
const resizeDirection = getResizeDirection(elementToResize);
|
|
9856
|
+
if (!resizeDirection.x && !resizeDirection.y) {
|
|
9857
|
+
return null;
|
|
9858
|
+
}
|
|
9859
|
+
|
|
9860
|
+
const dragToResizeGestureController = createDragGestureController({
|
|
9861
|
+
onDragStart: (...args) => {
|
|
9862
|
+
onDragStart?.(...args);
|
|
9863
|
+
},
|
|
9864
|
+
onDrag,
|
|
9865
|
+
onRelease: (...args) => {
|
|
9866
|
+
elementWithDataResizeHandle.removeAttribute("data-active");
|
|
9867
|
+
onRelease?.(...args);
|
|
9868
|
+
},
|
|
9869
|
+
});
|
|
9870
|
+
elementWithDataResizeHandle.setAttribute("data-active", "");
|
|
9871
|
+
const dragToResizeGesture = dragToResizeGestureController.grabViaPointer(
|
|
9872
|
+
pointerdownEvent,
|
|
9873
|
+
{
|
|
9874
|
+
element: elementToResize,
|
|
9875
|
+
direction: resizeDirection,
|
|
9876
|
+
cursor:
|
|
9877
|
+
resizeDirection.x && resizeDirection.y
|
|
9878
|
+
? "nwse-resize"
|
|
9879
|
+
: resizeDirection.x
|
|
9880
|
+
? "ew-resize"
|
|
9881
|
+
: "ns-resize",
|
|
9882
|
+
...options,
|
|
9883
|
+
},
|
|
9884
|
+
);
|
|
9885
|
+
return dragToResizeGesture;
|
|
9886
|
+
};
|
|
9887
|
+
|
|
9888
|
+
const getResizeDirection = (element) => {
|
|
9889
|
+
const direction = element.getAttribute("data-resize");
|
|
9890
|
+
const x = direction === "horizontal" || direction === "both";
|
|
9891
|
+
const y = direction === "vertical" || direction === "both";
|
|
9892
|
+
return { x, y };
|
|
9893
|
+
};
|
|
9894
|
+
|
|
9276
9895
|
const getPositionedParent = (element) => {
|
|
9277
9896
|
let parent = element.parentElement;
|
|
9278
9897
|
while (parent && parent !== document.body) {
|
|
@@ -9321,15 +9940,16 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
9321
9940
|
*
|
|
9322
9941
|
* The element should have a CSS "top" value specified (e.g., top: 10px).
|
|
9323
9942
|
*/
|
|
9324
|
-
|
|
9943
|
+
const css = /* css */`
|
|
9325
9944
|
[data-position-sticky-placeholder] {
|
|
9326
9945
|
position: static !important;
|
|
9327
9946
|
width: auto !important;
|
|
9328
9947
|
height: auto !important;
|
|
9329
9948
|
opacity: 0 !important;
|
|
9330
9949
|
}
|
|
9331
|
-
|
|
9950
|
+
`;
|
|
9332
9951
|
const initPositionSticky = element => {
|
|
9952
|
+
import.meta.css = [css, "@jsenv/dom/src/position/position_sticky.js"];
|
|
9333
9953
|
const computedStyle = getComputedStyle(element);
|
|
9334
9954
|
const topCssValue = computedStyle.top;
|
|
9335
9955
|
const top = parseFloat(topCssValue);
|
|
@@ -13371,4 +13991,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
13371
13991
|
};
|
|
13372
13992
|
};
|
|
13373
13993
|
|
|
13374
|
-
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, canInterceptKeys, captureScrollState, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureScrollbar, mergeOneStyle, mergeTwoStyles, normalizeStyle, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|
|
13994
|
+
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, canInterceptKeys, captureScrollState, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureScrollbar, mergeOneStyle, mergeTwoStyles, normalizeStyle, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|