@jsenv/dom 0.17.12 → 0.17.14
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 +386 -235
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -8969,7 +8969,9 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
8969
8969
|
every preventDefault from then on is a "Unable to preventDefault inside
|
|
8970
8970
|
passive event listener" intervention — on Android, a scroll that runs away
|
|
8971
8971
|
with the object. Any explicit value other than `auto` is enough: `pan-y` still
|
|
8972
|
-
lets the page scroll and still makes the refusal effective
|
|
8972
|
+
lets the page scroll and still makes the refusal effective — provided the
|
|
8973
|
+
listener that will refuse is already known too, which is markDragSource's
|
|
8974
|
+
half of the same rule. */
|
|
8973
8975
|
const css$4 = /* css */`
|
|
8974
8976
|
[data-drag-handle],
|
|
8975
8977
|
[data-drag-source] {
|
|
@@ -8998,6 +9000,51 @@ const css$4 = /* css */`
|
|
|
8998
9000
|
`;
|
|
8999
9001
|
import.meta.css = [css$4, "@jsenv/dom/src/interaction/drag/drag_after_intent.js"];
|
|
9000
9002
|
|
|
9003
|
+
/*
|
|
9004
|
+
* A press that may become a drag has to be refusable before anyone knows it is
|
|
9005
|
+
* one. WHETHER a touchmove can be refused at all is decided when the touch
|
|
9006
|
+
* BEGINS, from the non-passive listeners the browser knows about at that
|
|
9007
|
+
* moment — and on the long press path the gesture, which is what refuses it
|
|
9008
|
+
* (see preventTouchScroll in drag_gesture.js), is only born once the wait is
|
|
9009
|
+
* over. Put down from the pointerdown it is already too late: every touchmove
|
|
9010
|
+
* handed over is `cancelable: false`, the refusal does nothing, and the page
|
|
9011
|
+
* scrolls away with the object still under the finger — until the touch is
|
|
9012
|
+
* taken for a scroll and the pointer stream is cancelled, which is the drag
|
|
9013
|
+
* dying mid-gesture, released where it stood.
|
|
9014
|
+
*
|
|
9015
|
+
* So it goes down with the element, next to the attribute the stylesheet above
|
|
9016
|
+
* reads: same rule, same moment. It refuses nothing itself — a press that is
|
|
9017
|
+
* still only a press must leave the scroll alone, which is exactly what the wait
|
|
9018
|
+
* is there to tell apart. Being there is the whole of it.
|
|
9019
|
+
*
|
|
9020
|
+
* On the element and not on the window, so the rest of the page keeps its
|
|
9021
|
+
* touches on the compositor's fast path.
|
|
9022
|
+
*/
|
|
9023
|
+
const keepTouchRefusable = () => {
|
|
9024
|
+
// Being registered IS the whole of it — see above.
|
|
9025
|
+
};
|
|
9026
|
+
|
|
9027
|
+
/**
|
|
9028
|
+
* Says an element is something a drag can start from.
|
|
9029
|
+
*
|
|
9030
|
+
* @param {Element} element
|
|
9031
|
+
* @param {string} [axes]
|
|
9032
|
+
* Which way the SURROUNDINGS scroll, so the other axis is left to them until
|
|
9033
|
+
* the grab: `"x"` for a source inside something travelling sideways, anything
|
|
9034
|
+
* else for the usual vertical page.
|
|
9035
|
+
* @returns {function} Takes the mark back off.
|
|
9036
|
+
*/
|
|
9037
|
+
const markDragSource = (element, axes) => {
|
|
9038
|
+
element.setAttribute("data-drag-source", axes === "x" ? "x" : "");
|
|
9039
|
+
element.addEventListener("touchmove", keepTouchRefusable, {
|
|
9040
|
+
passive: false
|
|
9041
|
+
});
|
|
9042
|
+
return () => {
|
|
9043
|
+
element.removeAttribute("data-drag-source");
|
|
9044
|
+
element.removeEventListener("touchmove", keepTouchRefusable);
|
|
9045
|
+
};
|
|
9046
|
+
};
|
|
9047
|
+
|
|
9001
9048
|
/**
|
|
9002
9049
|
* Waits for the user to mean it, then starts a drag gesture.
|
|
9003
9050
|
*
|
|
@@ -9094,61 +9141,28 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9094
9141
|
onPress
|
|
9095
9142
|
}) => {
|
|
9096
9143
|
/*
|
|
9097
|
-
*
|
|
9098
|
-
*
|
|
9099
|
-
*
|
|
9100
|
-
*
|
|
9101
|
-
*
|
|
9102
|
-
*
|
|
9103
|
-
* the object still under the finger — until the touch is taken for a scroll and
|
|
9104
|
-
* the pointer stream is cancelled, which is the drag dying mid-gesture.
|
|
9105
|
-
*
|
|
9106
|
-
* So the listener the decision looks for is put down with the FINGER, before
|
|
9107
|
-
* anyone knows whether this press is a drag. It refuses nothing — a press that
|
|
9108
|
-
* is still only a press must leave the scroll alone, which is exactly what the
|
|
9109
|
-
* wait is there to tell apart. Its only job is to be there, so that the
|
|
9110
|
-
* refusal made later is one the browser still listens to.
|
|
9144
|
+
* Nothing is done here to keep the touch refusable: whether it can be refused
|
|
9145
|
+
* at all was settled when the finger landed, from what the element already
|
|
9146
|
+
* carried (see markDragSource). Scrolling is then taken away by the gesture
|
|
9147
|
+
* itself, from the moment it starts (see preventTouchScroll in
|
|
9148
|
+
* drag_gesture.js) — one place refuses the touchmove, for every way a drag can
|
|
9149
|
+
* begin.
|
|
9111
9150
|
*/
|
|
9112
|
-
const keepTouchRefusable = () => {
|
|
9113
|
-
// Being registered IS the whole of it — see above.
|
|
9114
|
-
};
|
|
9115
|
-
const grabTarget = grabEvent.target;
|
|
9116
|
-
window.addEventListener("touchmove", keepTouchRefusable, {
|
|
9117
|
-
passive: false,
|
|
9118
|
-
capture: true
|
|
9119
|
-
});
|
|
9120
|
-
grabTarget.addEventListener("touchmove", keepTouchRefusable, {
|
|
9121
|
-
passive: false
|
|
9122
|
-
});
|
|
9123
|
-
const stopKeepingTouchRefusable = () => {
|
|
9124
|
-
window.removeEventListener("touchmove", keepTouchRefusable, {
|
|
9125
|
-
capture: true
|
|
9126
|
-
});
|
|
9127
|
-
grabTarget.removeEventListener("touchmove", keepTouchRefusable);
|
|
9128
|
-
};
|
|
9129
9151
|
waitForPressHeld(grabEvent, {
|
|
9130
9152
|
delay: longPressDelay,
|
|
9131
9153
|
slop: longPressSlop,
|
|
9132
9154
|
onPressStart,
|
|
9133
|
-
onPressCancel
|
|
9134
|
-
stopKeepingTouchRefusable();
|
|
9135
|
-
onPressCancel?.(pointerEvent);
|
|
9136
|
-
},
|
|
9155
|
+
onPressCancel,
|
|
9137
9156
|
onPressHeld: (pressEvent, {
|
|
9138
9157
|
endPress
|
|
9139
9158
|
}) => {
|
|
9140
9159
|
onPress?.(pressEvent);
|
|
9141
|
-
// Scrolling is taken away by the gesture itself, from the moment it starts
|
|
9142
|
-
// (see markAsStarted in drag_gesture.js) — one place refuses the touchmove,
|
|
9143
|
-
// for every way a drag can begin.
|
|
9144
9160
|
const dragGesture = startDragGesture(dragGestureInitializer);
|
|
9145
9161
|
if (!dragGesture) {
|
|
9146
|
-
stopKeepingTouchRefusable();
|
|
9147
9162
|
endPress();
|
|
9148
9163
|
return;
|
|
9149
9164
|
}
|
|
9150
9165
|
dragGesture.addReleaseCallback(() => {
|
|
9151
|
-
stopKeepingTouchRefusable();
|
|
9152
9166
|
endPress();
|
|
9153
9167
|
});
|
|
9154
9168
|
}
|
|
@@ -11398,6 +11412,13 @@ const css$1 = /* css */`
|
|
|
11398
11412
|
overflow: visible;
|
|
11399
11413
|
}
|
|
11400
11414
|
|
|
11415
|
+
/* On its way home and still the object: the hand can reach for it there, so
|
|
11416
|
+
there it takes the pointer — which it must not do at any other moment of the
|
|
11417
|
+
gesture, or it would hide what it is being dropped on. */
|
|
11418
|
+
[navi-drag-clone-wrapper][data-catchable] {
|
|
11419
|
+
pointer-events: auto;
|
|
11420
|
+
}
|
|
11421
|
+
|
|
11401
11422
|
/* Ce qui a été lancé: il continue dans la direction du geste jusqu'à sortir de
|
|
11402
11423
|
l'écran, et revient par le même chemin si la réponse refuse. */
|
|
11403
11424
|
[navi-drag-clone-wrapper][data-tossed] {
|
|
@@ -12117,207 +12138,235 @@ const startDragToCarryCopy = (event, {
|
|
|
12117
12138
|
if (!isPrimaryButtonEvent(event)) {
|
|
12118
12139
|
return undefined;
|
|
12119
12140
|
}
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
const dropHintEl = canLand ? createDropSurface() : canReorder ? createDropHint() : null;
|
|
12142
|
-
if (dropHintEl) {
|
|
12143
|
-
// In the container it draws into, which is where its own vars are set: the
|
|
12144
|
-
// shape of a drop hint is a property of the list or board it belongs to,
|
|
12145
|
-
// and reading it from there is inheritance rather than a hand-off.
|
|
12146
|
-
draggedElement.parentElement.appendChild(dropHintEl);
|
|
12147
|
-
}
|
|
12148
|
-
// The hint first, the clone second: that order is what stacks them in the
|
|
12149
|
-
// top layer.
|
|
12150
|
-
dropHintEl?.showPopover();
|
|
12151
|
-
cloneWrapper.showPopover();
|
|
12152
|
-
|
|
12153
|
-
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
12154
|
-
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
12155
|
-
let currentBeforeElement;
|
|
12156
|
-
let currentReleaseElement;
|
|
12157
|
-
const clearDropHintDOM = () => {
|
|
12158
|
-
if (!dropHintEl) {
|
|
12159
|
-
return;
|
|
12160
|
-
}
|
|
12161
|
-
dropHintEl.removeAttribute("data-drop-edge");
|
|
12162
|
-
dropHintEl.removeAttribute("data-drop-over");
|
|
12163
|
-
dropHintEl.style.removeProperty("--drop-target-top");
|
|
12164
|
-
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
12165
|
-
dropHintEl.style.removeProperty("--drop-target-left");
|
|
12166
|
-
dropHintEl.style.removeProperty("--drop-target-width");
|
|
12167
|
-
dropHintEl.style.removeProperty("--drop-target-height");
|
|
12168
|
-
};
|
|
12169
|
-
const clearDropHint = () => {
|
|
12170
|
-
currentBeforeElement = undefined;
|
|
12171
|
-
currentReleaseElement = undefined;
|
|
12172
|
-
clearDropHintDOM();
|
|
12173
|
-
};
|
|
12174
|
-
dragGesture.addDragCallback(gestureInfo => {
|
|
12175
|
-
if (!dropHintEl) {
|
|
12176
|
-
return;
|
|
12177
|
-
}
|
|
12178
|
-
const allItems = [];
|
|
12179
|
-
const items = [];
|
|
12180
|
-
for (const el of containerElement.querySelectorAll(itemSelector)) {
|
|
12181
|
-
allItems.push(el);
|
|
12182
|
-
if (el !== draggedElement) {
|
|
12183
|
-
items.push(el);
|
|
12184
|
-
}
|
|
12185
|
-
}
|
|
12186
|
-
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
12187
|
-
// The edges of a LIST: above the first row means the top of it, below
|
|
12188
|
-
// the last one means the end of it. A board has no such reading — away
|
|
12189
|
-
// from every place is away from every place.
|
|
12190
|
-
fallbackToEdge: !canLand
|
|
12141
|
+
// One press, one carry — and the same carry over again when the hand comes back
|
|
12142
|
+
// for the copy while it is still flying home (see settleCloneBack). Nothing is
|
|
12143
|
+
// made twice in that case: it is the same copy, taken in hand again where it
|
|
12144
|
+
// had got to.
|
|
12145
|
+
const startCarry = (pointerEvent, cloneWrapperCaught, onCarryStart) => {
|
|
12146
|
+
pointerEvent.preventDefault();
|
|
12147
|
+
return dragAfterIntent(pointerEvent, () => {
|
|
12148
|
+
// Here and nowhere else is where a press has turned into a carry — the
|
|
12149
|
+
// one moment both ways in (a finger held still, a mouse travelled)
|
|
12150
|
+
// agree on.
|
|
12151
|
+
onCarryStart?.();
|
|
12152
|
+
const cloneWrapper = cloneWrapperCaught || createDragClone(draggedElement, pointerEvent);
|
|
12153
|
+
if (cloneWrapperCaught) {
|
|
12154
|
+
liftDragClone(cloneWrapperCaught, pointerEvent);
|
|
12155
|
+
}
|
|
12156
|
+
draggedElement.setAttribute("navi-drag-clone-source", "");
|
|
12157
|
+
const gestureController = createDragToMoveGestureController({
|
|
12158
|
+
direction,
|
|
12159
|
+
releasePositionEffect: "manual",
|
|
12160
|
+
areaConstraint,
|
|
12161
|
+
...options
|
|
12191
12162
|
});
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12163
|
+
const dragGesture = gestureController.grabViaPointer(pointerEvent, {
|
|
12164
|
+
element: draggedElement,
|
|
12165
|
+
elementToMove: cloneWrapper
|
|
12166
|
+
});
|
|
12167
|
+
// getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
|
|
12168
|
+
// Point it at the clone so drop detection tracks the clone's current position.
|
|
12169
|
+
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
12170
|
+
|
|
12171
|
+
// No place to land, no hint: an element that can only be thrown away has
|
|
12172
|
+
// nowhere to be put. What the hint LOOKS like follows what a place is here —
|
|
12173
|
+
// a line in the gap between two items, or the place itself lit up.
|
|
12174
|
+
const dropHintEl = canLand ? createDropSurface() : canReorder ? createDropHint() : null;
|
|
12175
|
+
if (dropHintEl) {
|
|
12176
|
+
// In the container it draws into, which is where its own vars are set: the
|
|
12177
|
+
// shape of a drop hint is a property of the list or board it belongs to,
|
|
12178
|
+
// and reading it from there is inheritance rather than a hand-off.
|
|
12179
|
+
draggedElement.parentElement.appendChild(dropHintEl);
|
|
12180
|
+
}
|
|
12181
|
+
// The hint first, the clone second: that order is what stacks them in the
|
|
12182
|
+
// top layer. A copy taken back in hand is already up there, and the hint
|
|
12183
|
+
// just went above it — shown again it returns to the top, where what the
|
|
12184
|
+
// hand carries belongs.
|
|
12185
|
+
dropHintEl?.showPopover();
|
|
12186
|
+
if (cloneWrapperCaught) {
|
|
12187
|
+
cloneWrapper.hidePopover();
|
|
12188
|
+
}
|
|
12189
|
+
cloneWrapper.showPopover();
|
|
12190
|
+
|
|
12191
|
+
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
12192
|
+
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
12193
|
+
let currentBeforeElement;
|
|
12194
|
+
let currentReleaseElement;
|
|
12195
|
+
const clearDropHintDOM = () => {
|
|
12196
|
+
if (!dropHintEl) {
|
|
12202
12197
|
return;
|
|
12203
12198
|
}
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
dropHintEl.
|
|
12207
|
-
dropHintEl.style.
|
|
12208
|
-
dropHintEl.style.
|
|
12209
|
-
dropHintEl.style.
|
|
12210
|
-
dropHintEl.style.
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12199
|
+
dropHintEl.removeAttribute("data-drop-edge");
|
|
12200
|
+
dropHintEl.removeAttribute("data-drop-over");
|
|
12201
|
+
dropHintEl.style.removeProperty("--drop-target-top");
|
|
12202
|
+
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
12203
|
+
dropHintEl.style.removeProperty("--drop-target-left");
|
|
12204
|
+
dropHintEl.style.removeProperty("--drop-target-width");
|
|
12205
|
+
dropHintEl.style.removeProperty("--drop-target-height");
|
|
12206
|
+
};
|
|
12207
|
+
const clearDropHint = () => {
|
|
12208
|
+
currentBeforeElement = undefined;
|
|
12209
|
+
currentReleaseElement = undefined;
|
|
12210
|
+
clearDropHintDOM();
|
|
12211
|
+
};
|
|
12212
|
+
dragGesture.addDragCallback(gestureInfo => {
|
|
12213
|
+
if (!dropHintEl) {
|
|
12214
|
+
return;
|
|
12215
|
+
}
|
|
12216
|
+
const allItems = [];
|
|
12217
|
+
const items = [];
|
|
12218
|
+
for (const el of containerElement.querySelectorAll(itemSelector)) {
|
|
12219
|
+
allItems.push(el);
|
|
12220
|
+
if (el !== draggedElement) {
|
|
12221
|
+
items.push(el);
|
|
12222
|
+
}
|
|
12223
|
+
}
|
|
12224
|
+
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
12225
|
+
// The edges of a LIST: above the first row means the top of it, below
|
|
12226
|
+
// the last one means the end of it. A board has no such reading — away
|
|
12227
|
+
// from every place is away from every place.
|
|
12228
|
+
fallbackToEdge: !canLand
|
|
12229
|
+
});
|
|
12230
|
+
gestureInfo.dropTargetInfo = dropTargetInfo || null;
|
|
12231
|
+
if (!dropTargetInfo) {
|
|
12232
|
+
clearDropHint();
|
|
12233
|
+
return;
|
|
12234
|
+
}
|
|
12235
|
+
if (canLand) {
|
|
12236
|
+
// The whole element is the target, so which of its edges the copy came
|
|
12237
|
+
// in by says nothing: there is no gap to be on one side of.
|
|
12238
|
+
const dropElement = dropTargetInfo.element;
|
|
12239
|
+
if (dropElement === currentReleaseElement) {
|
|
12240
|
+
return;
|
|
12241
|
+
}
|
|
12242
|
+
currentReleaseElement = dropElement;
|
|
12243
|
+
const dropRect = dropElement.getBoundingClientRect();
|
|
12244
|
+
dropHintEl.setAttribute("data-drop-over", "");
|
|
12245
|
+
dropHintEl.style.setProperty("--drop-target-top", `${dropRect.top}px`);
|
|
12246
|
+
dropHintEl.style.setProperty("--drop-target-left", `${dropRect.left}px`);
|
|
12247
|
+
dropHintEl.style.setProperty("--drop-target-width", `${dropRect.width}px`);
|
|
12248
|
+
dropHintEl.style.setProperty("--drop-target-height", `${dropRect.height}px`);
|
|
12249
|
+
return;
|
|
12250
|
+
}
|
|
12251
|
+
// Convert {element, edge} to a beforeElement using the items array
|
|
12252
|
+
// (not nextElementSibling, which breaks if non-item elements exist between items).
|
|
12253
|
+
// edge "start" → insert before the hovered element
|
|
12254
|
+
// edge "end" → insert before the next item (null = append at end)
|
|
12255
|
+
const edge = dropTargetInfo.elementSide.y;
|
|
12256
|
+
const hoveredIndex = items.indexOf(dropTargetInfo.element);
|
|
12257
|
+
const beforeElement = edge === "start" ? dropTargetInfo.element : items[hoveredIndex + 1] ?? null;
|
|
12258
|
+
// Detect no-op: result would leave the grabbed element in the same position.
|
|
12259
|
+
const elementIndex = allItems.indexOf(draggedElement);
|
|
12260
|
+
const elementNextItem = allItems[elementIndex + 1] ?? null;
|
|
12261
|
+
const isNoop = beforeElement === elementNextItem;
|
|
12262
|
+
if (isNoop) {
|
|
12263
|
+
clearDropHint();
|
|
12264
|
+
return;
|
|
12265
|
+
}
|
|
12266
|
+
// Early return if nothing changed.
|
|
12267
|
+
const releaseElement = dropTargetInfo.element;
|
|
12268
|
+
if (beforeElement === currentBeforeElement && releaseElement === currentReleaseElement) {
|
|
12269
|
+
return;
|
|
12270
|
+
}
|
|
12271
|
+
currentBeforeElement = beforeElement;
|
|
12272
|
+
currentReleaseElement = releaseElement;
|
|
12273
|
+
// Update drop hint CSS vars.
|
|
12274
|
+
// beforeElement = null → insert at end (hint after last item)
|
|
12275
|
+
// beforeElement = X → insert before X (hint at top edge of X)
|
|
12276
|
+
const anchorEl = beforeElement || items[items.length - 1];
|
|
12277
|
+
const anchorEdge = beforeElement !== null ? "top" : "bottom";
|
|
12278
|
+
// Viewport coordinates, straight from the anchor row: the hint is fixed
|
|
12279
|
+
// in the page (see its CSS), so there is no container box to be relative
|
|
12280
|
+
// to and no scroll offset to add back.
|
|
12281
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
12282
|
+
dropHintEl.setAttribute("data-drop-edge", anchorEdge);
|
|
12283
|
+
dropHintEl.style.setProperty("--drop-target-top", `${anchorRect.top}px`);
|
|
12284
|
+
dropHintEl.style.setProperty("--drop-target-bottom", `${anchorRect.bottom}px`);
|
|
12285
|
+
dropHintEl.style.setProperty("--drop-target-left", `${anchorRect.left}px`);
|
|
12286
|
+
dropHintEl.style.setProperty("--drop-target-width", `${anchorRect.width}px`);
|
|
12267
12287
|
});
|
|
12288
|
+
dragGesture.addReleaseCallback(async gestureInfo => {
|
|
12289
|
+
clearDropHintDOM();
|
|
12290
|
+
dropHintEl?.remove();
|
|
12291
|
+
|
|
12292
|
+
// What THIS release means, from what the element said it can answer. A
|
|
12293
|
+
// throw is asked about first: it is the more insistent of the two, and a
|
|
12294
|
+
// hand that sent the thing across the screen has not asked for it to swap
|
|
12295
|
+
// places with whatever it happened to fly over.
|
|
12296
|
+
const hasDropTarget = canLand ? currentReleaseElement !== undefined : currentBeforeElement !== undefined;
|
|
12297
|
+
const dropMeans = resolveDropMeaning({
|
|
12298
|
+
gestureInfo,
|
|
12299
|
+
hasDropTarget,
|
|
12300
|
+
canReorder,
|
|
12301
|
+
canToss,
|
|
12302
|
+
canLand,
|
|
12303
|
+
tossDistance,
|
|
12304
|
+
tossSpeed
|
|
12305
|
+
});
|
|
12268
12306
|
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
//
|
|
12275
|
-
//
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
//
|
|
12286
|
-
//
|
|
12287
|
-
|
|
12307
|
+
// Let go of and still on the screen: from here until it is taken away
|
|
12308
|
+
// the copy can be taken back in hand (see letCopyBeCaught).
|
|
12309
|
+
const copyLetGoOf = letCopyBeCaught(cloneWrapper, (pointerDownEvent, whenCarried) => startCarry(pointerDownEvent, cloneWrapper, whenCarried));
|
|
12310
|
+
|
|
12311
|
+
// The copy stops where the hand left it, and the answer is given a way to
|
|
12312
|
+
// take it the rest of the way — synchronously, inside a view transition, so
|
|
12313
|
+
// it is captured where it lands rather than where it was let go of.
|
|
12314
|
+
const landCopyOn = async (targetElement, answer) => {
|
|
12315
|
+
const clone = cloneWrapper.firstElementChild;
|
|
12316
|
+
// Bake the current visual position (transform included) into the CSS vars
|
|
12317
|
+
// so the copy stays where the user released it when the transform goes.
|
|
12318
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
12319
|
+
gestureInfo.cancelPosition();
|
|
12320
|
+
// Where the copy comes down is not always the thing it came down ON: a
|
|
12321
|
+
// place of a board can be larger than what stands on it, and the copy
|
|
12322
|
+
// has to keep its own size and land where the item will be. Said with
|
|
12323
|
+
// an element, because the caller has one — the piece already standing
|
|
12324
|
+
// there, the empty slot waiting.
|
|
12325
|
+
const syncCloneWithDropTarget = (landingElement = targetElement) => {
|
|
12326
|
+
setCloneViewportRect(cloneWrapper, landingElement);
|
|
12327
|
+
// Removing this attr drops the CSS scale, so the browser captures the
|
|
12328
|
+
// copy at scale 1 as the "new" state.
|
|
12329
|
+
clone.removeAttribute("navi-drag-clone");
|
|
12330
|
+
};
|
|
12331
|
+
await answer(syncCloneWithDropTarget);
|
|
12288
12332
|
};
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12333
|
+
if (dropMeans === "toss") {
|
|
12334
|
+
// Bake the position the hand left it at, so the flight starts from
|
|
12335
|
+
// there rather than from where the clone was declared.
|
|
12336
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
12337
|
+
gestureInfo.cancelPosition();
|
|
12338
|
+
const gone = await tossCloneAway(cloneWrapper, gestureInfo, onToss);
|
|
12339
|
+
if (!gone) {
|
|
12340
|
+
// It still exists, so the screen has to say so: the copy comes back
|
|
12341
|
+
// over the original, and taking it away then reveals the row in
|
|
12342
|
+
// place.
|
|
12343
|
+
await settleCloneBack(cloneWrapper, draggedElement);
|
|
12344
|
+
}
|
|
12345
|
+
} else if (dropMeans === "land") {
|
|
12346
|
+
await landCopyOn(currentReleaseElement, syncCloneWithDropTarget => onLand(getItemId(draggedElement), getItemId(currentReleaseElement), syncCloneWithDropTarget));
|
|
12347
|
+
} else if (dropMeans === "reorder") {
|
|
12348
|
+
await landCopyOn(currentReleaseElement, syncCloneWithDropTarget => onReorder(getItemId(draggedElement), currentBeforeElement ? getItemId(currentBeforeElement) : null, syncCloneWithDropTarget));
|
|
12302
12349
|
}
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12350
|
+
if (await copyLetGoOf.settled()) {
|
|
12351
|
+
// In a hand again: the copy, and the place kept for it, are the new
|
|
12352
|
+
// gesture's — this one has nothing left to take away.
|
|
12353
|
+
return;
|
|
12354
|
+
}
|
|
12355
|
+
draggedElement.removeAttribute("navi-drag-clone-source");
|
|
12356
|
+
cloneWrapper.remove();
|
|
12357
|
+
});
|
|
12358
|
+
return dragGesture;
|
|
12359
|
+
}, {
|
|
12360
|
+
threshold,
|
|
12361
|
+
longPress,
|
|
12362
|
+
longPressDelay,
|
|
12363
|
+
longPressSlop,
|
|
12364
|
+
onPressStart,
|
|
12365
|
+
onPressCancel,
|
|
12366
|
+
onPress
|
|
12310
12367
|
});
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
threshold,
|
|
12314
|
-
longPress,
|
|
12315
|
-
longPressDelay,
|
|
12316
|
-
longPressSlop,
|
|
12317
|
-
onPressStart,
|
|
12318
|
-
onPressCancel,
|
|
12319
|
-
onPress
|
|
12320
|
-
});
|
|
12368
|
+
};
|
|
12369
|
+
return startCarry(event);
|
|
12321
12370
|
};
|
|
12322
12371
|
|
|
12323
12372
|
// Viewport coordinates, as getBoundingClientRect gives them: the clone is a
|
|
@@ -12427,10 +12476,112 @@ const settleCloneBack = (cloneWrapper, sourceElement) => {
|
|
|
12427
12476
|
setTimeout(resolve, TOSS_DURATION_MS);
|
|
12428
12477
|
});
|
|
12429
12478
|
};
|
|
12479
|
+
|
|
12480
|
+
/**
|
|
12481
|
+
* The copy is let go of, and it is still there: flying home, coming down on a
|
|
12482
|
+
* place, waiting on an answer. It is still the object for all that time — a hand
|
|
12483
|
+
* reaching for it there is reaching for the thing, and the answer is to give it
|
|
12484
|
+
* back.
|
|
12485
|
+
*
|
|
12486
|
+
* Left alone the press finds nothing: the copy does not take the pointer (it must
|
|
12487
|
+
* not, while it is carried, or it would hide what it is being dropped on) and the
|
|
12488
|
+
* original is hidden underneath it. The press falls through to the page, which
|
|
12489
|
+
* answers a held finger with the system context menu — on the very gesture that
|
|
12490
|
+
* meant "I am taking it back". So the copy takes the pointer for exactly this
|
|
12491
|
+
* stretch of the gesture, and for no other.
|
|
12492
|
+
*
|
|
12493
|
+
* It is not stopped where it is caught: it finishes what it was doing under the
|
|
12494
|
+
* hand and is picked up from wherever it got to, which is where it visibly is —
|
|
12495
|
+
* a press has to be held a moment before it counts as a carry, about as long as
|
|
12496
|
+
* these journeys last. What catching it does change is that the copy is not taken
|
|
12497
|
+
* away while a hand is on it: `settled()` waits the press out, so a carry has the
|
|
12498
|
+
* time to be born.
|
|
12499
|
+
*
|
|
12500
|
+
* THE PRESS IS READ AT THE DOCUMENT, and the copy's own box is only the shape it
|
|
12501
|
+
* is matched against. A press landing on it does not always reach it: an answer
|
|
12502
|
+
* that runs a view transition (which is the usual answer — it is what makes a
|
|
12503
|
+
* landing continuous) has the browser cover the page with its pictures, and every
|
|
12504
|
+
* press then goes to the document root whatever those pictures are told about
|
|
12505
|
+
* pointer events. Read from the document and matched against the box, it is
|
|
12506
|
+
* caught either way. Same reason, same shape as the box of travelling pages
|
|
12507
|
+
* (see route_travel.jsx in navi).
|
|
12508
|
+
*
|
|
12509
|
+
* @returns {{settled: function}} `settled()` resolves to whether the copy was
|
|
12510
|
+
* taken back in hand — and then it belongs to the new gesture, not to this one.
|
|
12511
|
+
*/
|
|
12512
|
+
const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
12513
|
+
let caught = false;
|
|
12514
|
+
let pressIsOver = Promise.resolve();
|
|
12515
|
+
const onPointerDown = pointerDownEvent => {
|
|
12516
|
+
const {
|
|
12517
|
+
left,
|
|
12518
|
+
right,
|
|
12519
|
+
top,
|
|
12520
|
+
bottom
|
|
12521
|
+
} = cloneWrapper.getBoundingClientRect();
|
|
12522
|
+
const {
|
|
12523
|
+
clientX,
|
|
12524
|
+
clientY
|
|
12525
|
+
} = pointerDownEvent;
|
|
12526
|
+
if (clientX < left || clientX > right || clientY < top || clientY > bottom) {
|
|
12527
|
+
// Somewhere else on the page: this press is not about the copy.
|
|
12528
|
+
return;
|
|
12529
|
+
}
|
|
12530
|
+
let pressIsOverResolve;
|
|
12531
|
+
pressIsOver = new Promise(resolve => {
|
|
12532
|
+
pressIsOverResolve = resolve;
|
|
12533
|
+
});
|
|
12534
|
+
const onPointerEnd = () => {
|
|
12535
|
+
window.removeEventListener("pointerup", onPointerEnd, true);
|
|
12536
|
+
window.removeEventListener("pointercancel", onPointerEnd, true);
|
|
12537
|
+
pressIsOverResolve();
|
|
12538
|
+
};
|
|
12539
|
+
window.addEventListener("pointerup", onPointerEnd, true);
|
|
12540
|
+
window.addEventListener("pointercancel", onPointerEnd, true);
|
|
12541
|
+
carryAgain(pointerDownEvent, () => {
|
|
12542
|
+
caught = true;
|
|
12543
|
+
onPointerEnd();
|
|
12544
|
+
});
|
|
12545
|
+
};
|
|
12546
|
+
// The attribute is what gives the copy the pointer (see the stylesheet): read
|
|
12547
|
+
// at the document or not, a press meant for the copy must land ON it, so the
|
|
12548
|
+
// gesture holds what it grabbed rather than whatever was behind.
|
|
12549
|
+
cloneWrapper.setAttribute("data-catchable", "");
|
|
12550
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
12551
|
+
return {
|
|
12552
|
+
settled: async () => {
|
|
12553
|
+
// A hand that lets go and presses again while the copy is still there is
|
|
12554
|
+
// one more press to wait out, not a press that was already over.
|
|
12555
|
+
let awaited;
|
|
12556
|
+
while (awaited !== pressIsOver) {
|
|
12557
|
+
awaited = pressIsOver;
|
|
12558
|
+
await awaited;
|
|
12559
|
+
}
|
|
12560
|
+
cloneWrapper.removeAttribute("data-catchable");
|
|
12561
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
12562
|
+
return caught;
|
|
12563
|
+
}
|
|
12564
|
+
};
|
|
12565
|
+
};
|
|
12566
|
+
|
|
12567
|
+
// What a copy is given when it is picked up: the point it lifts FROM, and the
|
|
12568
|
+
// lift itself. Both are lost by a copy that has already been let go of — a
|
|
12569
|
+
// landing drops the lift (see syncCloneWithDropTarget) and the hand catching it
|
|
12570
|
+
// again is somewhere else on it — so taking one back hands it both again.
|
|
12571
|
+
const liftDragClone = (cloneWrapper, pointerEvent) => {
|
|
12572
|
+
const rect = cloneWrapper.getBoundingClientRect();
|
|
12573
|
+
cloneWrapper.style.setProperty("--drag-origin", `${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`);
|
|
12574
|
+
cloneWrapper.firstElementChild.setAttribute("navi-drag-clone", "");
|
|
12575
|
+
};
|
|
12430
12576
|
const createDragClone = (element, pointerEvent) => {
|
|
12431
12577
|
const rect = element.getBoundingClientRect();
|
|
12432
12578
|
const wrapper = document.createElement("div");
|
|
12433
12579
|
wrapper.setAttribute("navi-drag-clone-wrapper", "");
|
|
12580
|
+
// A copy can be caught on its way home (see settleCloneBack), which is a press
|
|
12581
|
+
// that may become a drag — so it carries what such a press needs, and it
|
|
12582
|
+
// carries it from the moment it exists: what a touch may do is decided when the
|
|
12583
|
+
// touch begins, and by then this has to have been true for a while.
|
|
12584
|
+
markDragSource(wrapper);
|
|
12434
12585
|
// Manual: it is opened and closed with the drag, and must survive an Escape
|
|
12435
12586
|
// or a click elsewhere (light dismiss would take it away mid-gesture).
|
|
12436
12587
|
wrapper.setAttribute("popover", "manual");
|
|
@@ -18909,4 +19060,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
18909
19060
|
};
|
|
18910
19061
|
};
|
|
18911
19062
|
|
|
18912
|
-
export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, captureScrollState, chainEvent, claimWheelGesture, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterIntent, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, findSelfOrAncestorFixedPosition, formatEventSideEffect, getAncestorOpenType, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPositioningScrollOffset, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isPrimaryButtonEvent, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, releaseWheelGesture, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, scrollRoomTowards, setAttribute, setAttributes, setStyles, snapToPixel, startDragTo, startDragToResizeGesture, startDragToTravel, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, suppressClickAfterGesture, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect, waitForPressHeld, watchWheelTravel, wheelGestureIsTakenFrom };
|
|
19063
|
+
export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, captureScrollState, chainEvent, claimWheelGesture, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterIntent, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, findSelfOrAncestorFixedPosition, formatEventSideEffect, getAncestorOpenType, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPositioningScrollOffset, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isPrimaryButtonEvent, isSameColor, isScrollable, markDragSource, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, releaseWheelGesture, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, scrollRoomTowards, setAttribute, setAttributes, setStyles, snapToPixel, startDragTo, startDragToResizeGesture, startDragToTravel, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, suppressClickAfterGesture, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect, waitForPressHeld, watchWheelTravel, wheelGestureIsTakenFrom };
|