@jsenv/dom 0.17.92 → 0.17.94
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 +221 -54
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -12491,17 +12491,9 @@ const roundForConstraints = (value) => {
|
|
|
12491
12491
|
*
|
|
12492
12492
|
* @param {Object} gestureInfo - Gesture information
|
|
12493
12493
|
* @param {Element[]} targetElements - Array of potential drop target elements
|
|
12494
|
-
* @param {object} [options]
|
|
12495
|
-
* @param {boolean} [options.fallbackToEdge=false] - When true and the drag element does
|
|
12496
|
-
* not intersect any target, falls back to the first item (if above all items) or the
|
|
12497
|
-
* last item (if below all items) so there is always a valid drop target at list edges.
|
|
12498
12494
|
* @returns {Object|null} Drop target info with elementSide or null if no valid target found
|
|
12499
12495
|
*/
|
|
12500
|
-
const getDropTargetInfo = (
|
|
12501
|
-
gestureInfo,
|
|
12502
|
-
targetElements,
|
|
12503
|
-
{ fallbackToEdge = false } = {},
|
|
12504
|
-
) => {
|
|
12496
|
+
const getDropTargetInfo = (gestureInfo, targetElements) => {
|
|
12505
12497
|
const dragElement = gestureInfo.elementImpacted || gestureInfo.element;
|
|
12506
12498
|
const dragElementRect = dragElement.getBoundingClientRect();
|
|
12507
12499
|
const intersectingTargets = [];
|
|
@@ -12522,38 +12514,6 @@ const getDropTargetInfo = (
|
|
|
12522
12514
|
}
|
|
12523
12515
|
|
|
12524
12516
|
if (intersectingTargets.length === 0) {
|
|
12525
|
-
if (fallbackToEdge) {
|
|
12526
|
-
const dragElement = gestureInfo.elementImpacted || gestureInfo.element;
|
|
12527
|
-
const dragElementRect = dragElement.getBoundingClientRect();
|
|
12528
|
-
const firstItem = targetElements[0];
|
|
12529
|
-
const lastItem = targetElements[targetElements.length - 1];
|
|
12530
|
-
if (
|
|
12531
|
-
firstItem &&
|
|
12532
|
-
dragElementRect.bottom < firstItem.getBoundingClientRect().top
|
|
12533
|
-
) {
|
|
12534
|
-
// Drag element is above all items → treat as hovering the first item from the top.
|
|
12535
|
-
return {
|
|
12536
|
-
element: firstItem,
|
|
12537
|
-
elementSide: { x: "start", y: "start" },
|
|
12538
|
-
index: 0,
|
|
12539
|
-
intersectingIndex: 0,
|
|
12540
|
-
intersecting: [firstItem],
|
|
12541
|
-
};
|
|
12542
|
-
}
|
|
12543
|
-
if (
|
|
12544
|
-
lastItem &&
|
|
12545
|
-
dragElementRect.top > lastItem.getBoundingClientRect().bottom
|
|
12546
|
-
) {
|
|
12547
|
-
// Drag element is below all items → treat as hovering the last item from the bottom.
|
|
12548
|
-
return {
|
|
12549
|
-
element: lastItem,
|
|
12550
|
-
elementSide: { x: "start", y: "end" },
|
|
12551
|
-
index: targetElements.length - 1,
|
|
12552
|
-
intersectingIndex: 0,
|
|
12553
|
-
intersecting: [lastItem],
|
|
12554
|
-
};
|
|
12555
|
-
}
|
|
12556
|
-
}
|
|
12557
12517
|
return null;
|
|
12558
12518
|
}
|
|
12559
12519
|
|
|
@@ -14668,6 +14628,22 @@ const css$1 = /* css */ `.navi_drop_hint {
|
|
|
14668
14628
|
--drop-hint-y: calc(var(--drop-target-bottom) + var(--drop-hint-margin-y, 0px));
|
|
14669
14629
|
}
|
|
14670
14630
|
|
|
14631
|
+
.navi_drop_hint[data-drop-edge="left"], .navi_drop_hint[data-drop-edge="right"] {
|
|
14632
|
+
top: calc(var(--drop-target-top) + var(--drop-hint-margin-y, 0px));
|
|
14633
|
+
left: var(--drop-hint-x);
|
|
14634
|
+
width: var(--drop-hint-size, 3px);
|
|
14635
|
+
height: calc(var(--drop-target-height) - 2 * var(--drop-hint-margin-y, 0px));
|
|
14636
|
+
transform: translateX(-50%);
|
|
14637
|
+
}
|
|
14638
|
+
|
|
14639
|
+
.navi_drop_hint[data-drop-edge="left"] {
|
|
14640
|
+
--drop-hint-x: calc(var(--drop-target-left) - var(--drop-hint-margin-x, 0px));
|
|
14641
|
+
}
|
|
14642
|
+
|
|
14643
|
+
.navi_drop_hint[data-drop-edge="right"] {
|
|
14644
|
+
--drop-hint-x: calc(var(--drop-target-right) + var(--drop-hint-margin-x, 0px));
|
|
14645
|
+
}
|
|
14646
|
+
|
|
14671
14647
|
.navi_drop_hint_cap {
|
|
14672
14648
|
color: var(--drop-hint-background-color, #4476ff);
|
|
14673
14649
|
display: flex;
|
|
@@ -14691,6 +14667,23 @@ const css$1 = /* css */ `.navi_drop_hint {
|
|
|
14691
14667
|
rotate: 90deg;
|
|
14692
14668
|
}
|
|
14693
14669
|
|
|
14670
|
+
.navi_drop_hint:is([data-drop-edge="left"], [data-drop-edge="right"]) .navi_drop_hint_cap {
|
|
14671
|
+
left: 50%;
|
|
14672
|
+
right: auto;
|
|
14673
|
+
translate: -50%;
|
|
14674
|
+
}
|
|
14675
|
+
|
|
14676
|
+
.navi_drop_hint:is([data-drop-edge="left"], [data-drop-edge="right"]) .navi_drop_hint_cap[data-side="start"] {
|
|
14677
|
+
top: calc(-1 * var(--drop-hint-arrow-size, 11px));
|
|
14678
|
+
rotate: none;
|
|
14679
|
+
}
|
|
14680
|
+
|
|
14681
|
+
.navi_drop_hint:is([data-drop-edge="left"], [data-drop-edge="right"]) .navi_drop_hint_cap[data-side="end"] {
|
|
14682
|
+
top: auto;
|
|
14683
|
+
bottom: calc(-1 * var(--drop-hint-arrow-size, 11px));
|
|
14684
|
+
rotate: 180deg;
|
|
14685
|
+
}
|
|
14686
|
+
|
|
14694
14687
|
.navi_drop_surface {
|
|
14695
14688
|
inset: auto;
|
|
14696
14689
|
top: var(--drop-target-top);
|
|
@@ -15294,6 +15287,12 @@ const createDragToMoveGestureController = ({
|
|
|
15294
15287
|
* @param {string} [options.itemSelector] What matches the items of the list.
|
|
15295
15288
|
* Left out, nothing is a place: no hint is drawn and no landing can be
|
|
15296
15289
|
* answered, which is what a drag that only ever throws the thing away asks for.
|
|
15290
|
+
* @param {"column"|"row"|"wrap"} [options.itemFlow] How the items of a
|
|
15291
|
+
* reordered list are laid out: one under the other, side by side on one line,
|
|
15292
|
+
* or side by side over as many lines as it takes (inline-blocks, a flex-wrap,
|
|
15293
|
+
* a grid). A row runs in the writing direction of `containerElement`. It
|
|
15294
|
+
* decides which side of an item the copy is read on and where the hint is
|
|
15295
|
+
* drawn. Left out, it is read from where the items stand (see readItemFlow).
|
|
15297
15296
|
* @param {function} [options.getItemId] `getItemId(element) → id`. An id rather
|
|
15298
15297
|
* than a DOM index, because the two do not match: a list draws fewer rows than
|
|
15299
15298
|
* it has, a search reorders them, and the structure may have holes.
|
|
@@ -15733,6 +15732,15 @@ const resolveDropMeaning = ({
|
|
|
15733
15732
|
return "cancel";
|
|
15734
15733
|
};
|
|
15735
15734
|
|
|
15735
|
+
// Which edge of its anchor the reorder hint is drawn at, for each way the items
|
|
15736
|
+
// can follow each other (see readItemFlow): before an item, the edge the flow
|
|
15737
|
+
// comes in by; after the last one, the edge it leaves by.
|
|
15738
|
+
const DROP_HINT_EDGES = {
|
|
15739
|
+
down: { before: "top", after: "bottom" },
|
|
15740
|
+
right: { before: "left", after: "right" },
|
|
15741
|
+
left: { before: "right", after: "left" },
|
|
15742
|
+
};
|
|
15743
|
+
|
|
15736
15744
|
/**
|
|
15737
15745
|
* A COPY of the element is carried, and the original keeps its place in the
|
|
15738
15746
|
* layout — which is what makes a reorder possible at all: nothing else moves
|
|
@@ -15762,6 +15770,7 @@ const startDragToCarryCopy = (
|
|
|
15762
15770
|
areaConstraint = canToss || canLeave ? "none" : undefined,
|
|
15763
15771
|
containerElement = draggedElement.parentElement,
|
|
15764
15772
|
itemSelector,
|
|
15773
|
+
itemFlow,
|
|
15765
15774
|
getItemId,
|
|
15766
15775
|
onReorder,
|
|
15767
15776
|
onLand,
|
|
@@ -15799,6 +15808,7 @@ const startDragToCarryCopy = (
|
|
|
15799
15808
|
if (!isPrimaryButtonEvent(event)) {
|
|
15800
15809
|
return undefined;
|
|
15801
15810
|
}
|
|
15811
|
+
const flowSaid = resolveItemFlow(itemFlow, containerElement);
|
|
15802
15812
|
// Where the hand is, for a press handed over: its `pointerdown` says where it
|
|
15803
15813
|
// WAS, before the hold and whatever the hold opened. Let go of meanwhile, it
|
|
15804
15814
|
// is not picked up after the fact.
|
|
@@ -15913,6 +15923,7 @@ const startDragToCarryCopy = (
|
|
|
15913
15923
|
dropHintEl.style.removeProperty("--drop-target-top");
|
|
15914
15924
|
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
15915
15925
|
dropHintEl.style.removeProperty("--drop-target-left");
|
|
15926
|
+
dropHintEl.style.removeProperty("--drop-target-right");
|
|
15916
15927
|
dropHintEl.style.removeProperty("--drop-target-width");
|
|
15917
15928
|
dropHintEl.style.removeProperty("--drop-target-height");
|
|
15918
15929
|
};
|
|
@@ -15954,12 +15965,10 @@ const startDragToCarryCopy = (
|
|
|
15954
15965
|
}
|
|
15955
15966
|
}
|
|
15956
15967
|
|
|
15957
|
-
const
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
fallbackToEdge: !canLand,
|
|
15962
|
-
});
|
|
15968
|
+
const flow = canLand ? undefined : flowSaid || readItemFlow(allItems);
|
|
15969
|
+
const dropTargetInfo = canLand
|
|
15970
|
+
? getDropTargetInfo(gestureInfo, items)
|
|
15971
|
+
: findPlaceInList(gestureInfo, items, flow);
|
|
15963
15972
|
gestureInfo.dropTargetInfo = dropTargetInfo || null;
|
|
15964
15973
|
if (!dropTargetInfo) {
|
|
15965
15974
|
clearDropHint();
|
|
@@ -15993,14 +16002,13 @@ const startDragToCarryCopy = (
|
|
|
15993
16002
|
);
|
|
15994
16003
|
return;
|
|
15995
16004
|
}
|
|
15996
|
-
// Convert {element,
|
|
16005
|
+
// Convert {element, side} to a beforeElement using the items array
|
|
15997
16006
|
// (not nextElementSibling, which breaks if non-item elements exist between items).
|
|
15998
|
-
//
|
|
15999
|
-
//
|
|
16000
|
-
const edge = dropTargetInfo.elementSide.y;
|
|
16007
|
+
// side "start" → insert before the hovered element
|
|
16008
|
+
// side "end" → insert before the next item (null = append at end)
|
|
16001
16009
|
const hoveredIndex = items.indexOf(dropTargetInfo.element);
|
|
16002
16010
|
const beforeElement =
|
|
16003
|
-
|
|
16011
|
+
dropTargetInfo.side === "start"
|
|
16004
16012
|
? dropTargetInfo.element
|
|
16005
16013
|
: (items[hoveredIndex + 1] ?? null);
|
|
16006
16014
|
// Detect no-op: result would leave the grabbed element in the same position.
|
|
@@ -16023,9 +16031,11 @@ const startDragToCarryCopy = (
|
|
|
16023
16031
|
currentReleaseElement = releaseElement;
|
|
16024
16032
|
// Update drop hint CSS vars.
|
|
16025
16033
|
// beforeElement = null → insert at end (hint after last item)
|
|
16026
|
-
// beforeElement = X → insert before X (hint at
|
|
16034
|
+
// beforeElement = X → insert before X (hint at the edge of X the
|
|
16035
|
+
// flow comes in by)
|
|
16027
16036
|
const anchorEl = beforeElement || items[items.length - 1];
|
|
16028
|
-
const anchorEdge =
|
|
16037
|
+
const anchorEdge =
|
|
16038
|
+
DROP_HINT_EDGES[flow][beforeElement !== null ? "before" : "after"];
|
|
16029
16039
|
// Viewport coordinates, straight from the anchor row: the hint is fixed
|
|
16030
16040
|
// in the page (see its CSS), so there is no container box to be relative
|
|
16031
16041
|
// to and no scroll offset to add back.
|
|
@@ -16043,10 +16053,18 @@ const startDragToCarryCopy = (
|
|
|
16043
16053
|
"--drop-target-left",
|
|
16044
16054
|
`${anchorRect.left}px`,
|
|
16045
16055
|
);
|
|
16056
|
+
dropHintEl.style.setProperty(
|
|
16057
|
+
"--drop-target-right",
|
|
16058
|
+
`${anchorRect.right}px`,
|
|
16059
|
+
);
|
|
16046
16060
|
dropHintEl.style.setProperty(
|
|
16047
16061
|
"--drop-target-width",
|
|
16048
16062
|
`${anchorRect.width}px`,
|
|
16049
16063
|
);
|
|
16064
|
+
dropHintEl.style.setProperty(
|
|
16065
|
+
"--drop-target-height",
|
|
16066
|
+
`${anchorRect.height}px`,
|
|
16067
|
+
);
|
|
16050
16068
|
});
|
|
16051
16069
|
|
|
16052
16070
|
dragGesture.addReleaseCallback(async (gestureInfo) => {
|
|
@@ -16307,6 +16325,155 @@ const createDropSurface = () => {
|
|
|
16307
16325
|
return div;
|
|
16308
16326
|
};
|
|
16309
16327
|
|
|
16328
|
+
/**
|
|
16329
|
+
* The way the items follow each other, from how the caller said they are laid
|
|
16330
|
+
* out (the `itemFlow` of startDragTo). A row runs the way the text of its list
|
|
16331
|
+
* does: from right to left under `direction: rtl`.
|
|
16332
|
+
*/
|
|
16333
|
+
const resolveItemFlow = (itemFlow, containerElement) => {
|
|
16334
|
+
if (itemFlow === undefined) {
|
|
16335
|
+
return undefined;
|
|
16336
|
+
}
|
|
16337
|
+
if (itemFlow === "column") {
|
|
16338
|
+
return "down";
|
|
16339
|
+
}
|
|
16340
|
+
if (itemFlow === "row" || itemFlow === "wrap") {
|
|
16341
|
+
return getComputedStyle(containerElement).direction === "rtl"
|
|
16342
|
+
? "left"
|
|
16343
|
+
: "right";
|
|
16344
|
+
}
|
|
16345
|
+
return undefined;
|
|
16346
|
+
};
|
|
16347
|
+
|
|
16348
|
+
/**
|
|
16349
|
+
* Which way the items of a list follow each other on screen: "down", or along a
|
|
16350
|
+
* line — a row, a wrapped row, a grid — "right", or "left" when it runs from
|
|
16351
|
+
* right to left. Read from where the items stand rather than declared: the
|
|
16352
|
+
* first item level with the one after it makes the list run sideways, and which
|
|
16353
|
+
* of the two is on the left says which way. Asked of the whole list, not of the
|
|
16354
|
+
* item under the copy: alone on the last line of a wrapped row, an item is level
|
|
16355
|
+
* with nothing and still part of a row.
|
|
16356
|
+
*/
|
|
16357
|
+
const readItemFlow = (items) => {
|
|
16358
|
+
let previousRect;
|
|
16359
|
+
for (const item of items) {
|
|
16360
|
+
const rect = item.getBoundingClientRect();
|
|
16361
|
+
// A box that takes no room stands on no line.
|
|
16362
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
16363
|
+
continue;
|
|
16364
|
+
}
|
|
16365
|
+
if (previousRect && areLevel(previousRect, rect)) {
|
|
16366
|
+
return rect.left < previousRect.left ? "left" : "right";
|
|
16367
|
+
}
|
|
16368
|
+
previousRect = rect;
|
|
16369
|
+
}
|
|
16370
|
+
return "down";
|
|
16371
|
+
};
|
|
16372
|
+
|
|
16373
|
+
// The middle of one within the height of the other. Overlapping is not enough:
|
|
16374
|
+
// items stacked with a negative margin (a border shared between two) overlap by
|
|
16375
|
+
// a pixel and are still one under the other.
|
|
16376
|
+
const areLevel = (a, b) => {
|
|
16377
|
+
const aMiddle = a.top + a.height / 2;
|
|
16378
|
+
const bMiddle = b.top + b.height / 2;
|
|
16379
|
+
return (
|
|
16380
|
+
(bMiddle > a.top && bMiddle < a.bottom) ||
|
|
16381
|
+
(aMiddle > b.top && aMiddle < b.bottom)
|
|
16382
|
+
);
|
|
16383
|
+
};
|
|
16384
|
+
|
|
16385
|
+
/**
|
|
16386
|
+
* The item the copy is at, and its `side` in the order of the list: "start" for
|
|
16387
|
+
* before it, "end" for after it.
|
|
16388
|
+
*
|
|
16389
|
+
* Down a list, the item under the copy, read by the copy's leading edge (see
|
|
16390
|
+
* getDropTargetInfo); over no item, above the first one is the start of the
|
|
16391
|
+
* list and below the last one its end.
|
|
16392
|
+
*
|
|
16393
|
+
* Along a row, the copy's middle decides, the way a click puts the caret in a
|
|
16394
|
+
* text: on the nearest line, before the first item whose middle it has not
|
|
16395
|
+
* reached. How high the copy is says which line and nothing more, its size
|
|
16396
|
+
* nothing at all — the leading edge assumes a copy as large as what it passes
|
|
16397
|
+
* over, and items of a row are as wide as their content: read by its edge, one
|
|
16398
|
+
* wider than its neighbour could never be put after it.
|
|
16399
|
+
*/
|
|
16400
|
+
const findPlaceInList = (gestureInfo, items, flow) => {
|
|
16401
|
+
const dragElementRect = gestureInfo.elementImpacted.getBoundingClientRect();
|
|
16402
|
+
if (flow !== "down") {
|
|
16403
|
+
return findPlaceOnNearestLine(dragElementRect, items, flow);
|
|
16404
|
+
}
|
|
16405
|
+
const dropTargetInfo = getDropTargetInfo(gestureInfo, items);
|
|
16406
|
+
if (dropTargetInfo) {
|
|
16407
|
+
return {
|
|
16408
|
+
element: dropTargetInfo.element,
|
|
16409
|
+
side: dropTargetInfo.elementSide.y,
|
|
16410
|
+
};
|
|
16411
|
+
}
|
|
16412
|
+
const firstItem = items[0];
|
|
16413
|
+
if (!firstItem) {
|
|
16414
|
+
return null;
|
|
16415
|
+
}
|
|
16416
|
+
const lastItem = items[items.length - 1];
|
|
16417
|
+
if (dragElementRect.bottom < firstItem.getBoundingClientRect().top) {
|
|
16418
|
+
return { element: firstItem, side: "start" };
|
|
16419
|
+
}
|
|
16420
|
+
if (dragElementRect.top > lastItem.getBoundingClientRect().bottom) {
|
|
16421
|
+
return { element: lastItem, side: "end" };
|
|
16422
|
+
}
|
|
16423
|
+
return null;
|
|
16424
|
+
};
|
|
16425
|
+
|
|
16426
|
+
const findPlaceOnNearestLine = (dragElementRect, items, flow) => {
|
|
16427
|
+
const lines = [];
|
|
16428
|
+
let previousRect;
|
|
16429
|
+
for (const item of items) {
|
|
16430
|
+
const rect = item.getBoundingClientRect();
|
|
16431
|
+
// A box that takes no room stands on no line (see readItemFlow).
|
|
16432
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
16433
|
+
continue;
|
|
16434
|
+
}
|
|
16435
|
+
if (!previousRect || !areLevel(previousRect, rect)) {
|
|
16436
|
+
lines.push({ top: rect.top, bottom: rect.bottom, entries: [] });
|
|
16437
|
+
}
|
|
16438
|
+
const line = lines[lines.length - 1];
|
|
16439
|
+
if (rect.top < line.top) {
|
|
16440
|
+
line.top = rect.top;
|
|
16441
|
+
}
|
|
16442
|
+
if (rect.bottom > line.bottom) {
|
|
16443
|
+
line.bottom = rect.bottom;
|
|
16444
|
+
}
|
|
16445
|
+
line.entries.push({ item, middle: rect.left + rect.width / 2 });
|
|
16446
|
+
previousRect = rect;
|
|
16447
|
+
}
|
|
16448
|
+
const dragMiddleY = dragElementRect.top + dragElementRect.height / 2;
|
|
16449
|
+
let nearestLine;
|
|
16450
|
+
let nearestDistance;
|
|
16451
|
+
for (const line of lines) {
|
|
16452
|
+
const distance =
|
|
16453
|
+
dragMiddleY < line.top
|
|
16454
|
+
? line.top - dragMiddleY
|
|
16455
|
+
: dragMiddleY > line.bottom
|
|
16456
|
+
? dragMiddleY - line.bottom
|
|
16457
|
+
: 0;
|
|
16458
|
+
if (nearestLine === undefined || distance < nearestDistance) {
|
|
16459
|
+
nearestLine = line;
|
|
16460
|
+
nearestDistance = distance;
|
|
16461
|
+
}
|
|
16462
|
+
}
|
|
16463
|
+
if (!nearestLine) {
|
|
16464
|
+
return null;
|
|
16465
|
+
}
|
|
16466
|
+
const dragMiddleX = dragElementRect.left + dragElementRect.width / 2;
|
|
16467
|
+
for (const { item, middle } of nearestLine.entries) {
|
|
16468
|
+
const notReached =
|
|
16469
|
+
flow === "right" ? middle > dragMiddleX : middle < dragMiddleX;
|
|
16470
|
+
if (notReached) {
|
|
16471
|
+
return { element: item, side: "start" };
|
|
16472
|
+
}
|
|
16473
|
+
}
|
|
16474
|
+
return { element: nearestLine.entries.at(-1).item, side: "end" };
|
|
16475
|
+
};
|
|
16476
|
+
|
|
16310
16477
|
/**
|
|
16311
16478
|
* The copy leaves the screen the way it was thrown, and the caller says what that
|
|
16312
16479
|
* meant. Resolves true when it is really gone.
|