@jsenv/dom 0.17.11 → 0.17.13
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 +390 -199
- 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
|
*
|
|
@@ -9093,6 +9140,14 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9093
9140
|
onPressCancel,
|
|
9094
9141
|
onPress
|
|
9095
9142
|
}) => {
|
|
9143
|
+
/*
|
|
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.
|
|
9150
|
+
*/
|
|
9096
9151
|
waitForPressHeld(grabEvent, {
|
|
9097
9152
|
delay: longPressDelay,
|
|
9098
9153
|
slop: longPressSlop,
|
|
@@ -9102,15 +9157,14 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9102
9157
|
endPress
|
|
9103
9158
|
}) => {
|
|
9104
9159
|
onPress?.(pressEvent);
|
|
9105
|
-
// Scrolling is taken away by the gesture itself, from the moment it starts
|
|
9106
|
-
// (see markAsStarted in drag_gesture.js) — one place refuses the touchmove,
|
|
9107
|
-
// for every way a drag can begin.
|
|
9108
9160
|
const dragGesture = startDragGesture(dragGestureInitializer);
|
|
9109
9161
|
if (!dragGesture) {
|
|
9110
9162
|
endPress();
|
|
9111
9163
|
return;
|
|
9112
9164
|
}
|
|
9113
|
-
dragGesture.addReleaseCallback(
|
|
9165
|
+
dragGesture.addReleaseCallback(() => {
|
|
9166
|
+
endPress();
|
|
9167
|
+
});
|
|
9114
9168
|
}
|
|
9115
9169
|
});
|
|
9116
9170
|
};
|
|
@@ -11358,6 +11412,13 @@ const css$1 = /* css */`
|
|
|
11358
11412
|
overflow: visible;
|
|
11359
11413
|
}
|
|
11360
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
|
+
|
|
11361
11422
|
/* Ce qui a été lancé: il continue dans la direction du geste jusqu'à sortir de
|
|
11362
11423
|
l'écran, et revient par le même chemin si la réponse refuse. */
|
|
11363
11424
|
[navi-drag-clone-wrapper][data-tossed] {
|
|
@@ -12077,207 +12138,235 @@ const startDragToCarryCopy = (event, {
|
|
|
12077
12138
|
if (!isPrimaryButtonEvent(event)) {
|
|
12078
12139
|
return undefined;
|
|
12079
12140
|
}
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
const dropHintEl = canLand ? createDropSurface() : canReorder ? createDropHint() : null;
|
|
12102
|
-
if (dropHintEl) {
|
|
12103
|
-
// In the container it draws into, which is where its own vars are set: the
|
|
12104
|
-
// shape of a drop hint is a property of the list or board it belongs to,
|
|
12105
|
-
// and reading it from there is inheritance rather than a hand-off.
|
|
12106
|
-
draggedElement.parentElement.appendChild(dropHintEl);
|
|
12107
|
-
}
|
|
12108
|
-
// The hint first, the clone second: that order is what stacks them in the
|
|
12109
|
-
// top layer.
|
|
12110
|
-
dropHintEl?.showPopover();
|
|
12111
|
-
cloneWrapper.showPopover();
|
|
12112
|
-
|
|
12113
|
-
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
12114
|
-
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
12115
|
-
let currentBeforeElement;
|
|
12116
|
-
let currentReleaseElement;
|
|
12117
|
-
const clearDropHintDOM = () => {
|
|
12118
|
-
if (!dropHintEl) {
|
|
12119
|
-
return;
|
|
12120
|
-
}
|
|
12121
|
-
dropHintEl.removeAttribute("data-drop-edge");
|
|
12122
|
-
dropHintEl.removeAttribute("data-drop-over");
|
|
12123
|
-
dropHintEl.style.removeProperty("--drop-target-top");
|
|
12124
|
-
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
12125
|
-
dropHintEl.style.removeProperty("--drop-target-left");
|
|
12126
|
-
dropHintEl.style.removeProperty("--drop-target-width");
|
|
12127
|
-
dropHintEl.style.removeProperty("--drop-target-height");
|
|
12128
|
-
};
|
|
12129
|
-
const clearDropHint = () => {
|
|
12130
|
-
currentBeforeElement = undefined;
|
|
12131
|
-
currentReleaseElement = undefined;
|
|
12132
|
-
clearDropHintDOM();
|
|
12133
|
-
};
|
|
12134
|
-
dragGesture.addDragCallback(gestureInfo => {
|
|
12135
|
-
if (!dropHintEl) {
|
|
12136
|
-
return;
|
|
12137
|
-
}
|
|
12138
|
-
const allItems = [];
|
|
12139
|
-
const items = [];
|
|
12140
|
-
for (const el of containerElement.querySelectorAll(itemSelector)) {
|
|
12141
|
-
allItems.push(el);
|
|
12142
|
-
if (el !== draggedElement) {
|
|
12143
|
-
items.push(el);
|
|
12144
|
-
}
|
|
12145
|
-
}
|
|
12146
|
-
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
12147
|
-
// The edges of a LIST: above the first row means the top of it, below
|
|
12148
|
-
// the last one means the end of it. A board has no such reading — away
|
|
12149
|
-
// from every place is away from every place.
|
|
12150
|
-
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
|
|
12151
12162
|
});
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
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) {
|
|
12162
12197
|
return;
|
|
12163
12198
|
}
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
dropHintEl.
|
|
12167
|
-
dropHintEl.style.
|
|
12168
|
-
dropHintEl.style.
|
|
12169
|
-
dropHintEl.style.
|
|
12170
|
-
dropHintEl.style.
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
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`);
|
|
12227
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
|
+
});
|
|
12228
12306
|
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
//
|
|
12235
|
-
//
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
//
|
|
12246
|
-
//
|
|
12247
|
-
|
|
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);
|
|
12248
12332
|
};
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
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));
|
|
12262
12349
|
}
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
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
|
|
12270
12367
|
});
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
threshold,
|
|
12274
|
-
longPress,
|
|
12275
|
-
longPressDelay,
|
|
12276
|
-
longPressSlop,
|
|
12277
|
-
onPressStart,
|
|
12278
|
-
onPressCancel,
|
|
12279
|
-
onPress
|
|
12280
|
-
});
|
|
12368
|
+
};
|
|
12369
|
+
return startCarry(event);
|
|
12281
12370
|
};
|
|
12282
12371
|
|
|
12283
12372
|
// Viewport coordinates, as getBoundingClientRect gives them: the clone is a
|
|
@@ -12387,10 +12476,112 @@ const settleCloneBack = (cloneWrapper, sourceElement) => {
|
|
|
12387
12476
|
setTimeout(resolve, TOSS_DURATION_MS);
|
|
12388
12477
|
});
|
|
12389
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
|
+
};
|
|
12390
12576
|
const createDragClone = (element, pointerEvent) => {
|
|
12391
12577
|
const rect = element.getBoundingClientRect();
|
|
12392
12578
|
const wrapper = document.createElement("div");
|
|
12393
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);
|
|
12394
12585
|
// Manual: it is opened and closed with the drag, and must survive an Escape
|
|
12395
12586
|
// or a click elsewhere (light dismiss would take it away mid-gesture).
|
|
12396
12587
|
wrapper.setAttribute("popover", "manual");
|
|
@@ -18869,4 +19060,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
18869
19060
|
};
|
|
18870
19061
|
};
|
|
18871
19062
|
|
|
18872
|
-
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 };
|