@jsenv/dom 0.17.10 → 0.17.11
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 +293 -147
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -8623,6 +8623,72 @@ const createDragGestureController = (options = {}) => {
|
|
|
8623
8623
|
};
|
|
8624
8624
|
dragGesture.dragViaPointer = dragViaPointer;
|
|
8625
8625
|
dragGesture.releaseViaPointer = releaseViaPointer;
|
|
8626
|
+
/*
|
|
8627
|
+
* A press that starts a drag is not a press that starts a selection: the
|
|
8628
|
+
* browser sees a pointer going down on text and moving, and that is its
|
|
8629
|
+
* own gesture — the words under the finger turn blue while the element
|
|
8630
|
+
* travels, and the selection outlives the release.
|
|
8631
|
+
*
|
|
8632
|
+
* Refused from the grab, before any threshold: whether the press becomes a
|
|
8633
|
+
* drag is decided a few pixels later, but the selection is decided at the
|
|
8634
|
+
* FIRST move, and by then it is too late to say no.
|
|
8635
|
+
*
|
|
8636
|
+
* `user-select: none` would say it in CSS, but it would say it to
|
|
8637
|
+
* everybody: the element would stop being selectable even when nobody is
|
|
8638
|
+
* dragging it. Here it is refused for the length of one gesture.
|
|
8639
|
+
*/
|
|
8640
|
+
const preventSelectStart = selectStartEvent => {
|
|
8641
|
+
selectStartEvent.preventDefault();
|
|
8642
|
+
};
|
|
8643
|
+
document.addEventListener("selectstart", preventSelectStart);
|
|
8644
|
+
// A press also puts an end to the selection the page was already holding,
|
|
8645
|
+
// the way the browser's own press does: refusing selectstart keeps a new
|
|
8646
|
+
// selection from being made, it says nothing about the one painted before
|
|
8647
|
+
// — which would otherwise sit there through a gesture that has nothing to
|
|
8648
|
+
// do with it.
|
|
8649
|
+
collapseSelection();
|
|
8650
|
+
dragGesture.addReleaseCallback(() => {
|
|
8651
|
+
document.removeEventListener("selectstart", preventSelectStart);
|
|
8652
|
+
});
|
|
8653
|
+
/*
|
|
8654
|
+
* Refusing every selection also refuses the one a press is entitled to
|
|
8655
|
+
* make: a double click selects the word under it, and that selection is
|
|
8656
|
+
* over before the pointer has gone anywhere. It is made here instead,
|
|
8657
|
+
* spelled out (see selectWordAtPoint) rather than left to a browser
|
|
8658
|
+
* heuristic that cannot tell a drag from a click.
|
|
8659
|
+
*
|
|
8660
|
+
* On the document and outliving the gesture, because the gesture is
|
|
8661
|
+
* already over when the second click completes: dblclick comes after
|
|
8662
|
+
* mouseup, the gesture ends at pointerup. It is dropped when it fires, and
|
|
8663
|
+
* otherwise when the next press installs its own — a listener waiting for
|
|
8664
|
+
* a double click that never comes costs nothing until then.
|
|
8665
|
+
*/
|
|
8666
|
+
removePendingDoubleClickListener();
|
|
8667
|
+
const onDoubleClick = dblclickEvent => {
|
|
8668
|
+
removePendingDoubleClickListener = NOOP;
|
|
8669
|
+
// A drag that happened is a gesture, not a click: the second press of a
|
|
8670
|
+
// double click can be the one that drags, and what it drags must not end
|
|
8671
|
+
// up selected too.
|
|
8672
|
+
if (dragGesture.gestureInfo.started) {
|
|
8673
|
+
return;
|
|
8674
|
+
}
|
|
8675
|
+
// Text the page says is not selectable stays not selectable: a
|
|
8676
|
+
// programmatic selection goes through `user-select: none` in every
|
|
8677
|
+
// engine — it is a rule about what the USER may start, and the browser
|
|
8678
|
+
// does not read it back when asked directly. Read here so that doing the
|
|
8679
|
+
// browser's work does not also undo what the page asked of it.
|
|
8680
|
+
if (!isSelectable(dblclickEvent.target)) {
|
|
8681
|
+
return;
|
|
8682
|
+
}
|
|
8683
|
+
selectWordAtPoint(dblclickEvent.clientX, dblclickEvent.clientY);
|
|
8684
|
+
};
|
|
8685
|
+
document.addEventListener("dblclick", onDoubleClick, {
|
|
8686
|
+
once: true
|
|
8687
|
+
});
|
|
8688
|
+
removePendingDoubleClickListener = () => {
|
|
8689
|
+
removePendingDoubleClickListener = NOOP;
|
|
8690
|
+
document.removeEventListener("dblclick", onDoubleClick);
|
|
8691
|
+
};
|
|
8626
8692
|
const cleanup = initializer({
|
|
8627
8693
|
onMove: dragViaPointer,
|
|
8628
8694
|
onRelease: releaseViaPointer,
|
|
@@ -8815,6 +8881,57 @@ const definePropertyAsReadOnly = (object, propertyName) => {
|
|
|
8815
8881
|
value: object[propertyName]
|
|
8816
8882
|
});
|
|
8817
8883
|
};
|
|
8884
|
+
const NOOP = () => {};
|
|
8885
|
+
let removePendingDoubleClickListener = NOOP;
|
|
8886
|
+
|
|
8887
|
+
// What a double click selects when the browser is allowed to do it itself: the
|
|
8888
|
+
// word around the caret the click lands on.
|
|
8889
|
+
const selectWordAtPoint = (x, y) => {
|
|
8890
|
+
const caretRange = createCaretRange(x, y);
|
|
8891
|
+
if (!caretRange) {
|
|
8892
|
+
return;
|
|
8893
|
+
}
|
|
8894
|
+
const selection = window.getSelection();
|
|
8895
|
+
selection.removeAllRanges();
|
|
8896
|
+
selection.addRange(caretRange);
|
|
8897
|
+
// "word" is not a position a Range can be built from — it is a movement the
|
|
8898
|
+
// selection knows how to make, and the caret walking to both of its edges is
|
|
8899
|
+
// what draws the word.
|
|
8900
|
+
if (selection.modify) {
|
|
8901
|
+
selection.modify("move", "backward", "word");
|
|
8902
|
+
selection.modify("extend", "forward", "word");
|
|
8903
|
+
}
|
|
8904
|
+
};
|
|
8905
|
+
const createCaretRange = (x, y) => {
|
|
8906
|
+
if (document.caretPositionFromPoint) {
|
|
8907
|
+
const caretPosition = document.caretPositionFromPoint(x, y);
|
|
8908
|
+
if (!caretPosition) {
|
|
8909
|
+
return null;
|
|
8910
|
+
}
|
|
8911
|
+
const range = document.createRange();
|
|
8912
|
+
range.setStart(caretPosition.offsetNode, caretPosition.offset);
|
|
8913
|
+
range.collapse(true);
|
|
8914
|
+
return range;
|
|
8915
|
+
}
|
|
8916
|
+
if (document.caretRangeFromPoint) {
|
|
8917
|
+
return document.caretRangeFromPoint(x, y);
|
|
8918
|
+
}
|
|
8919
|
+
return null;
|
|
8920
|
+
};
|
|
8921
|
+
const isSelectable = element => {
|
|
8922
|
+
if (!element || element.nodeType !== 1) {
|
|
8923
|
+
return true;
|
|
8924
|
+
}
|
|
8925
|
+
const computedStyle = window.getComputedStyle(element);
|
|
8926
|
+
const userSelect = computedStyle.userSelect || computedStyle.webkitUserSelect;
|
|
8927
|
+
return userSelect !== "none";
|
|
8928
|
+
};
|
|
8929
|
+
const collapseSelection = () => {
|
|
8930
|
+
const selection = window.getSelection();
|
|
8931
|
+
if (selection && !selection.isCollapsed) {
|
|
8932
|
+
selection.removeAllRanges();
|
|
8933
|
+
}
|
|
8934
|
+
};
|
|
8818
8935
|
|
|
8819
8936
|
installImportMetaCssBuild(import.meta);/**
|
|
8820
8937
|
* When a press becomes a drag.
|
|
@@ -10921,33 +11038,6 @@ const findTableCellCol = (cellElement) => {
|
|
|
10921
11038
|
return correspondingCol;
|
|
10922
11039
|
};
|
|
10923
11040
|
|
|
10924
|
-
// Temporarily attach to the element so inherited CSS vars resolve correctly,
|
|
10925
|
-
// then snapshot all drop-hint custom properties onto the scroll container
|
|
10926
|
-
// so they survive once the element moves to the scroll container.
|
|
10927
|
-
const moveCSSVars = (vars, fromEl, toEl) => {
|
|
10928
|
-
const fromComputedStyle = getComputedStyle(fromEl);
|
|
10929
|
-
const savedVars = {};
|
|
10930
|
-
for (const varName of vars) {
|
|
10931
|
-
const value = fromComputedStyle.getPropertyValue(varName).trim();
|
|
10932
|
-
if (value) {
|
|
10933
|
-
savedVars[varName] = toEl.style.getPropertyValue(varName);
|
|
10934
|
-
toEl.style.setProperty(varName, value);
|
|
10935
|
-
}
|
|
10936
|
-
}
|
|
10937
|
-
|
|
10938
|
-
return () => {
|
|
10939
|
-
for (const varName of vars) {
|
|
10940
|
-
if (varName in savedVars) {
|
|
10941
|
-
if (savedVars[varName]) {
|
|
10942
|
-
toEl.style.setProperty(varName, savedVars[varName]);
|
|
10943
|
-
} else {
|
|
10944
|
-
toEl.style.removeProperty(varName);
|
|
10945
|
-
}
|
|
10946
|
-
}
|
|
10947
|
-
}
|
|
10948
|
-
};
|
|
10949
|
-
};
|
|
10950
|
-
|
|
10951
11041
|
const applyStickyFrontiersToAutoScrollArea = (
|
|
10952
11042
|
autoScrollArea,
|
|
10953
11043
|
{ direction, scrollContainer, dragName },
|
|
@@ -11081,6 +11171,12 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
11081
11171
|
* stable row of items to look between.
|
|
11082
11172
|
* - **toss**: it is gotten rid of. The same copy, for the opposite reason: the
|
|
11083
11173
|
* original stays until the answer says it is really gone.
|
|
11174
|
+
* - **land**: it comes down ON something. Also a copy, and the closest to
|
|
11175
|
+
* `reorder` — the difference is what a target IS: a row of a list is a place
|
|
11176
|
+
* BETWEEN two others, whereas a square of a board is a place of its own, which
|
|
11177
|
+
* may already be taken. So nothing is inserted and nothing is a no-op: the
|
|
11178
|
+
* answer is "this one came down on that one", and what that means (take the
|
|
11179
|
+
* place, swap the two, refuse) is the caller's.
|
|
11084
11180
|
*
|
|
11085
11181
|
* The caller lists which outcomes ITS element can answer, and only the machinery
|
|
11086
11182
|
* those need runs: no copy for a move, no drop hint for something that can only be
|
|
@@ -11102,13 +11198,14 @@ const TOSS_DURATION_MS = 320;
|
|
|
11102
11198
|
// Far enough to be off any screen, in the direction the hand was going.
|
|
11103
11199
|
const TOSS_DISTANCE = 900;
|
|
11104
11200
|
const css$1 = /* css */`
|
|
11105
|
-
/*
|
|
11106
|
-
|
|
11107
|
-
|
|
11201
|
+
/* IT COSTS THE LIST NOTHING: the hint lands on the edge of a row, which for
|
|
11202
|
+
the last one is the very bottom of the scroll area — a line taking up room
|
|
11203
|
+
there would push the scrollable area a few pixels further and make a
|
|
11108
11204
|
scrollbar appear (or hide the hint under it) exactly when one is trying to
|
|
11109
|
-
drop at the end.
|
|
11110
|
-
|
|
11111
|
-
the
|
|
11205
|
+
drop at the end. Being fixed is what avoids it: a fixed box has the
|
|
11206
|
+
viewport as containing block, so it is left out of the scrollable overflow
|
|
11207
|
+
of every ancestor and can overhang the list freely. Same for the clone it
|
|
11208
|
+
accompanies. */
|
|
11112
11209
|
.navi_drop_hint {
|
|
11113
11210
|
/* A popover, so it lands in the top layer: no z-index to bid against the
|
|
11114
11211
|
page, and nothing it can be hidden behind. Shown BEFORE the clone, which
|
|
@@ -11150,8 +11247,8 @@ const css$1 = /* css */`
|
|
|
11150
11247
|
/* A chevron at each end, pointing in: the line alone is easy to lose against
|
|
11151
11248
|
a list of borders and separators, two arrows read as "here" at a glance
|
|
11152
11249
|
(same idea as the table's column drop preview). They overhang the line,
|
|
11153
|
-
which costs nothing
|
|
11154
|
-
|
|
11250
|
+
which costs nothing to a box left out of the scrollable area — and the more
|
|
11251
|
+
they stick out, the easier they are to spot. */
|
|
11155
11252
|
.navi_drop_hint_cap {
|
|
11156
11253
|
position: absolute;
|
|
11157
11254
|
top: 50%;
|
|
@@ -11172,14 +11269,45 @@ const css$1 = /* css */`
|
|
|
11172
11269
|
rotate: 90deg;
|
|
11173
11270
|
}
|
|
11174
11271
|
|
|
11272
|
+
/* WHERE IT LANDS, when landing is ON a thing rather than between two: the
|
|
11273
|
+
place itself is lit up, because there is no gap to draw a line in. Fixed
|
|
11274
|
+
and in the top layer for the same reasons as the line above. */
|
|
11275
|
+
.navi_drop_surface {
|
|
11276
|
+
position: fixed;
|
|
11277
|
+
inset: auto;
|
|
11278
|
+
top: var(--drop-target-top);
|
|
11279
|
+
left: var(--drop-target-left);
|
|
11280
|
+
display: none;
|
|
11281
|
+
box-sizing: border-box;
|
|
11282
|
+
width: var(--drop-target-width);
|
|
11283
|
+
height: var(--drop-target-height);
|
|
11284
|
+
margin: 0;
|
|
11285
|
+
padding: 0;
|
|
11286
|
+
color: inherit;
|
|
11287
|
+
background: var(--drop-surface-background-color, rgba(68, 118, 255, 0.16));
|
|
11288
|
+
border: var(--drop-surface-border-width, 2px) solid
|
|
11289
|
+
var(--drop-surface-border-color, #4476ff);
|
|
11290
|
+
border-radius: var(--drop-surface-border-radius, 6px);
|
|
11291
|
+
pointer-events: none;
|
|
11292
|
+
overflow: visible;
|
|
11293
|
+
}
|
|
11294
|
+
.navi_drop_surface[data-drop-over]:popover-open {
|
|
11295
|
+
display: block;
|
|
11296
|
+
}
|
|
11297
|
+
|
|
11175
11298
|
/* WHO CAN START A DRAG, said in the cursor.
|
|
11176
|
-
A handle
|
|
11177
|
-
the
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11299
|
+
A handle exists only to drag, so it shows the hand. A source does not, and
|
|
11300
|
+
the gesture must not claim its cursor: it drags only once the intent shows
|
|
11301
|
+
(a few pixels of travel, or a long press), a plain click on it stays a
|
|
11302
|
+
click, and it is usually something else FIRST — a link, a card one opens.
|
|
11303
|
+
The cursor says what the element is, and a hand insisting on the one thing
|
|
11304
|
+
it can also be would talk over that. So it is left alone — default, and not
|
|
11305
|
+
an I-beam, because dragging across the text does not select it (the gesture
|
|
11306
|
+
takes the pointer; see the selectstart refused in drag_gesture.js) — and
|
|
11307
|
+
whoever puts the drag there asks for the hand when a grab really is the
|
|
11308
|
+
first thing the element offers.
|
|
11309
|
+
An opted-out area keeps both its cursor and its selection, and never starts
|
|
11310
|
+
a drag (see the check in startDragTo).
|
|
11183
11311
|
Controls inside a source keep their own cursor: cursor is inherited, and
|
|
11184
11312
|
anything setting its own (a button's pointer) wins on itself.
|
|
11185
11313
|
Only the resting cursor is set here: what it becomes once a drag is under
|
|
@@ -11190,11 +11318,9 @@ const css$1 = /* css */`
|
|
|
11190
11318
|
}
|
|
11191
11319
|
[data-drag-source] {
|
|
11192
11320
|
cursor: default;
|
|
11193
|
-
user-select: none;
|
|
11194
11321
|
}
|
|
11195
11322
|
[data-drag-ignore] {
|
|
11196
11323
|
cursor: auto;
|
|
11197
|
-
user-select: auto;
|
|
11198
11324
|
}
|
|
11199
11325
|
|
|
11200
11326
|
[navi-drag-clone-source] {
|
|
@@ -11218,13 +11344,10 @@ const css$1 = /* css */`
|
|
|
11218
11344
|
color: inherit;
|
|
11219
11345
|
background: transparent;
|
|
11220
11346
|
border: none;
|
|
11221
|
-
/*
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
shade is a theme's business either way. */
|
|
11225
|
-
box-shadow: var(--drag-clone-shadow, 0 12px 28px rgba(0, 0, 0, 0.22));
|
|
11347
|
+
/* Carries the chain down to the copy, for an item whose own radius is an
|
|
11348
|
+
"inherit" from the list around it. */
|
|
11349
|
+
border-radius: inherit;
|
|
11226
11350
|
opacity: 0.95;
|
|
11227
|
-
transition: box-shadow 0.15s ease;
|
|
11228
11351
|
pointer-events: none;
|
|
11229
11352
|
/* Nothing in a copy being carried by a pointer is text to select: the
|
|
11230
11353
|
selection belongs to the original, which is still in the page. This is the
|
|
@@ -11247,17 +11370,23 @@ const css$1 = /* css */`
|
|
|
11247
11370
|
}
|
|
11248
11371
|
|
|
11249
11372
|
[navi-drag-clone] {
|
|
11373
|
+
/* Cast by the copy itself rather than by the box around it, so it takes the
|
|
11374
|
+
shape of the thing — a rounded row throws a rounded shadow. Its value is a
|
|
11375
|
+
var read on the copy, which IS the dragged element: what being carried
|
|
11376
|
+
looks like belongs to whoever owns the thing — a row lifted off a list
|
|
11377
|
+
wants this shadow, a sheet of paper leaving a board wants none, and its
|
|
11378
|
+
shade is a theme's business either way. */
|
|
11379
|
+
box-shadow: var(--drag-clone-shadow, 0 12px 28px rgba(0, 0, 0, 0.22));
|
|
11250
11380
|
transform: scale(var(--drag-clone-scale, 1.03));
|
|
11251
11381
|
transform-origin: var(--drag-origin);
|
|
11252
|
-
transition:
|
|
11382
|
+
transition:
|
|
11383
|
+
transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
11384
|
+
box-shadow 0.15s ease;
|
|
11253
11385
|
}
|
|
11254
11386
|
|
|
11255
11387
|
@starting-style {
|
|
11256
|
-
[navi-drag-clone-wrapper] {
|
|
11257
|
-
box-shadow: none;
|
|
11258
|
-
}
|
|
11259
|
-
|
|
11260
11388
|
[navi-drag-clone] {
|
|
11389
|
+
box-shadow: none;
|
|
11261
11390
|
transform: scale(1);
|
|
11262
11391
|
}
|
|
11263
11392
|
}
|
|
@@ -11265,7 +11394,6 @@ const css$1 = /* css */`
|
|
|
11265
11394
|
// At module scope, not inside startDragTo: the cursor rules above say who
|
|
11266
11395
|
// can start a drag, and they have to be true BEFORE anyone drags anything.
|
|
11267
11396
|
import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to.js"];
|
|
11268
|
-
const dragCSSVars = ["--drop-hint-size", "--drop-hint-background-color", "--drop-hint-border-radius", "--drop-hint-margin-x", "--drop-hint-margin-y", "--drop-hint-arrow-size", "--drag-clone-scale", "--drag-clone-shadow"];
|
|
11269
11397
|
|
|
11270
11398
|
/**
|
|
11271
11399
|
* Starts a drag-to-reorder interaction on a list item.
|
|
@@ -11276,9 +11404,9 @@ const dragCSSVars = ["--drop-hint-size", "--drop-hint-background-color", "--drop
|
|
|
11276
11404
|
* reorders anything by accident.
|
|
11277
11405
|
* - Clones the grabbed element and moves the clone while the original stays hidden in place
|
|
11278
11406
|
* (keeps the layout intact so other items don't shift during the drag).
|
|
11279
|
-
* -
|
|
11280
|
-
*
|
|
11281
|
-
*
|
|
11407
|
+
* - The clone and the drop-hint live in the dragged element's own parent, so the CSS vars
|
|
11408
|
+
* that dress them (`--drag-clone-shadow`, `--drop-hint-size`, …) reach them by plain
|
|
11409
|
+
* inheritance, and so do the rules the list writes for its items.
|
|
11282
11410
|
* - Shows a drop-hint line indicating where the item will land.
|
|
11283
11411
|
* - Drop-target detection is intersection-based: the clone's bounding rect is compared
|
|
11284
11412
|
* against every item that matches `itemSelector` in the scroll container.
|
|
@@ -11715,10 +11843,11 @@ const createDragToMoveGestureController = ({
|
|
|
11715
11843
|
* Starts a drag, for one or more of the outcomes listed.
|
|
11716
11844
|
*
|
|
11717
11845
|
* @param {PointerEvent} event The `pointerdown` that may become a drag.
|
|
11718
|
-
* @param {("move"|"reorder"|"toss")[]} effects
|
|
11719
|
-
* What letting go of this element can mean. `reorder` and `
|
|
11720
|
-
* `move` carries the element itself. Asking for `move` and `reorder`
|
|
11721
|
-
* asking one release to mean two things
|
|
11846
|
+
* @param {("move"|"reorder"|"toss"|"land")[]} effects
|
|
11847
|
+
* What letting go of this element can mean. `reorder`, `toss` and `land` carry a
|
|
11848
|
+
* copy; `move` carries the element itself. Asking for `move` and `reorder`
|
|
11849
|
+
* together is asking one release to mean two things, and so is asking for
|
|
11850
|
+
* `reorder` and `land`.
|
|
11722
11851
|
* @param {object} [options]
|
|
11723
11852
|
* @param {Element} [options.draggedElement=event.currentTarget]
|
|
11724
11853
|
* @param {(detail: {gestureInfo: object, x: number, y: number}) => Promise|void} [options.onMove]
|
|
@@ -11734,6 +11863,12 @@ const createDragToMoveGestureController = ({
|
|
|
11734
11863
|
* It was thrown away. The copy leaves the screen while this runs and comes back
|
|
11735
11864
|
* if the promise rejects, because the thing still exists and the screen has to
|
|
11736
11865
|
* say so.
|
|
11866
|
+
* @param {function} [options.onLand]
|
|
11867
|
+
* `onLand(fromId, toId, syncCloneWithDropTarget)` — it came down on `toId`, which
|
|
11868
|
+
* is an element and never null: nothing under the copy is a cancelled release.
|
|
11869
|
+
* The copy is held until what comes back settles, exactly like `onReorder`.
|
|
11870
|
+
* `syncCloneWithDropTarget` takes an element when the place is not the shape of
|
|
11871
|
+
* what stands on it: the copy then takes THAT box instead of the target's.
|
|
11737
11872
|
* @param {number} [options.tossDistance=110] How far a throw goes, in px.
|
|
11738
11873
|
* @param {number} [options.tossSpeed=0.45] And how fast, in px/ms. BOTH are asked
|
|
11739
11874
|
* for: one without the other is moving the thing while hesitating, and nothing is
|
|
@@ -11768,11 +11903,13 @@ const startDragTo = (event, effects, {
|
|
|
11768
11903
|
}
|
|
11769
11904
|
const canReorder = effects.includes("reorder");
|
|
11770
11905
|
const canToss = effects.includes("toss");
|
|
11771
|
-
|
|
11906
|
+
const canLand = effects.includes("land");
|
|
11907
|
+
if (canReorder || canToss || canLand) {
|
|
11772
11908
|
return startDragToCarryCopy(event, {
|
|
11773
11909
|
draggedElement,
|
|
11774
11910
|
canReorder,
|
|
11775
11911
|
canToss,
|
|
11912
|
+
canLand,
|
|
11776
11913
|
...options
|
|
11777
11914
|
});
|
|
11778
11915
|
}
|
|
@@ -11857,6 +11994,7 @@ const resolveDropMeaning = ({
|
|
|
11857
11994
|
hasDropTarget,
|
|
11858
11995
|
canReorder,
|
|
11859
11996
|
canToss,
|
|
11997
|
+
canLand,
|
|
11860
11998
|
tossDistance = TOSS_DISTANCE_TO_COMMIT,
|
|
11861
11999
|
tossSpeed = TOSS_SPEED_TO_COMMIT
|
|
11862
12000
|
}) => {
|
|
@@ -11870,8 +12008,13 @@ const resolveDropMeaning = ({
|
|
|
11870
12008
|
return "toss";
|
|
11871
12009
|
}
|
|
11872
12010
|
}
|
|
11873
|
-
if (
|
|
11874
|
-
|
|
12011
|
+
if (hasDropTarget) {
|
|
12012
|
+
if (canLand) {
|
|
12013
|
+
return "land";
|
|
12014
|
+
}
|
|
12015
|
+
if (canReorder) {
|
|
12016
|
+
return "reorder";
|
|
12017
|
+
}
|
|
11875
12018
|
}
|
|
11876
12019
|
return "cancel";
|
|
11877
12020
|
};
|
|
@@ -11880,13 +12023,15 @@ const resolveDropMeaning = ({
|
|
|
11880
12023
|
* A COPY of the element is carried, and the original keeps its place in the
|
|
11881
12024
|
* layout — which is what makes a reorder possible at all: nothing else moves
|
|
11882
12025
|
* while the hand looks for a place, so there is a stable row of items to look
|
|
11883
|
-
* between. A
|
|
11884
|
-
* until the answer says it is really
|
|
12026
|
+
* between. A landing on a place of a board is the same, and a throw uses that copy
|
|
12027
|
+
* for the opposite reason: the original stays until the answer says it is really
|
|
12028
|
+
* gone.
|
|
11885
12029
|
*/
|
|
11886
12030
|
const startDragToCarryCopy = (event, {
|
|
11887
12031
|
draggedElement,
|
|
11888
12032
|
canReorder,
|
|
11889
12033
|
canToss,
|
|
12034
|
+
canLand,
|
|
11890
12035
|
// Something that can be thrown away has to be able to LEAVE. The default of
|
|
11891
12036
|
// the layer below keeps what is dragged inside its scroll area, which is right
|
|
11892
12037
|
// for a reorder (a row belongs to its list) and makes a throw impossible — the
|
|
@@ -11901,10 +12046,16 @@ const startDragToCarryCopy = (event, {
|
|
|
11901
12046
|
itemSelector,
|
|
11902
12047
|
getItemId,
|
|
11903
12048
|
onReorder,
|
|
12049
|
+
onLand,
|
|
11904
12050
|
onToss,
|
|
11905
12051
|
tossDistance,
|
|
11906
12052
|
tossSpeed,
|
|
11907
|
-
|
|
12053
|
+
// A list runs one way and reordering walks it; a board has places all around,
|
|
12054
|
+
// so something landing on one of them goes wherever the hand takes it.
|
|
12055
|
+
direction = canLand ? {
|
|
12056
|
+
x: true,
|
|
12057
|
+
y: true
|
|
12058
|
+
} : {
|
|
11908
12059
|
x: false,
|
|
11909
12060
|
y: true
|
|
11910
12061
|
},
|
|
@@ -11930,9 +12081,6 @@ const startDragToCarryCopy = (event, {
|
|
|
11930
12081
|
return dragAfterIntent(event, () => {
|
|
11931
12082
|
const cloneWrapper = createDragClone(draggedElement, event);
|
|
11932
12083
|
draggedElement.setAttribute("navi-drag-clone-source", "");
|
|
11933
|
-
// Move drag related CSS vars from the element to the document
|
|
11934
|
-
// so they're accessible to .navi_drop_hint and the clone (which are both in document.body)
|
|
11935
|
-
const restoreCSSVars = moveCSSVars(dragCSSVars, draggedElement, document.documentElement);
|
|
11936
12084
|
const gestureController = createDragToMoveGestureController({
|
|
11937
12085
|
direction,
|
|
11938
12086
|
releasePositionEffect: "manual",
|
|
@@ -11948,10 +12096,14 @@ const startDragToCarryCopy = (event, {
|
|
|
11948
12096
|
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
11949
12097
|
|
|
11950
12098
|
// No place to land, no hint: an element that can only be thrown away has
|
|
11951
|
-
// nowhere to be put.
|
|
11952
|
-
|
|
12099
|
+
// nowhere to be put. What the hint LOOKS like follows what a place is here —
|
|
12100
|
+
// a line in the gap between two items, or the place itself lit up.
|
|
12101
|
+
const dropHintEl = canLand ? createDropSurface() : canReorder ? createDropHint() : null;
|
|
11953
12102
|
if (dropHintEl) {
|
|
11954
|
-
|
|
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);
|
|
11955
12107
|
}
|
|
11956
12108
|
// The hint first, the clone second: that order is what stacks them in the
|
|
11957
12109
|
// top layer.
|
|
@@ -11967,10 +12119,12 @@ const startDragToCarryCopy = (event, {
|
|
|
11967
12119
|
return;
|
|
11968
12120
|
}
|
|
11969
12121
|
dropHintEl.removeAttribute("data-drop-edge");
|
|
12122
|
+
dropHintEl.removeAttribute("data-drop-over");
|
|
11970
12123
|
dropHintEl.style.removeProperty("--drop-target-top");
|
|
11971
12124
|
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
11972
12125
|
dropHintEl.style.removeProperty("--drop-target-left");
|
|
11973
12126
|
dropHintEl.style.removeProperty("--drop-target-width");
|
|
12127
|
+
dropHintEl.style.removeProperty("--drop-target-height");
|
|
11974
12128
|
};
|
|
11975
12129
|
const clearDropHint = () => {
|
|
11976
12130
|
currentBeforeElement = undefined;
|
|
@@ -11990,13 +12144,32 @@ const startDragToCarryCopy = (event, {
|
|
|
11990
12144
|
}
|
|
11991
12145
|
}
|
|
11992
12146
|
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
11993
|
-
|
|
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
|
|
11994
12151
|
});
|
|
11995
12152
|
gestureInfo.dropTargetInfo = dropTargetInfo || null;
|
|
11996
12153
|
if (!dropTargetInfo) {
|
|
11997
12154
|
clearDropHint();
|
|
11998
12155
|
return;
|
|
11999
12156
|
}
|
|
12157
|
+
if (canLand) {
|
|
12158
|
+
// The whole element is the target, so which of its edges the copy came
|
|
12159
|
+
// in by says nothing: there is no gap to be on one side of.
|
|
12160
|
+
const dropElement = dropTargetInfo.element;
|
|
12161
|
+
if (dropElement === currentReleaseElement) {
|
|
12162
|
+
return;
|
|
12163
|
+
}
|
|
12164
|
+
currentReleaseElement = dropElement;
|
|
12165
|
+
const dropRect = dropElement.getBoundingClientRect();
|
|
12166
|
+
dropHintEl.setAttribute("data-drop-over", "");
|
|
12167
|
+
dropHintEl.style.setProperty("--drop-target-top", `${dropRect.top}px`);
|
|
12168
|
+
dropHintEl.style.setProperty("--drop-target-left", `${dropRect.left}px`);
|
|
12169
|
+
dropHintEl.style.setProperty("--drop-target-width", `${dropRect.width}px`);
|
|
12170
|
+
dropHintEl.style.setProperty("--drop-target-height", `${dropRect.height}px`);
|
|
12171
|
+
return;
|
|
12172
|
+
}
|
|
12000
12173
|
// Convert {element, edge} to a beforeElement using the items array
|
|
12001
12174
|
// (not nextElementSibling, which breaks if non-item elements exist between items).
|
|
12002
12175
|
// edge "start" → insert before the hovered element
|
|
@@ -12037,21 +12210,44 @@ const startDragToCarryCopy = (event, {
|
|
|
12037
12210
|
dragGesture.addReleaseCallback(async gestureInfo => {
|
|
12038
12211
|
clearDropHintDOM();
|
|
12039
12212
|
dropHintEl?.remove();
|
|
12040
|
-
restoreCSSVars();
|
|
12041
12213
|
|
|
12042
12214
|
// What THIS release means, from what the element said it can answer. A
|
|
12043
12215
|
// throw is asked about first: it is the more insistent of the two, and a
|
|
12044
12216
|
// hand that sent the thing across the screen has not asked for it to swap
|
|
12045
12217
|
// places with whatever it happened to fly over.
|
|
12046
|
-
const hasDropTarget = currentBeforeElement !== undefined;
|
|
12218
|
+
const hasDropTarget = canLand ? currentReleaseElement !== undefined : currentBeforeElement !== undefined;
|
|
12047
12219
|
const dropMeans = resolveDropMeaning({
|
|
12048
12220
|
gestureInfo,
|
|
12049
12221
|
hasDropTarget,
|
|
12050
12222
|
canReorder,
|
|
12051
12223
|
canToss,
|
|
12224
|
+
canLand,
|
|
12052
12225
|
tossDistance,
|
|
12053
12226
|
tossSpeed
|
|
12054
12227
|
});
|
|
12228
|
+
|
|
12229
|
+
// The copy stops where the hand left it, and the answer is given a way to
|
|
12230
|
+
// take it the rest of the way — synchronously, inside a view transition, so
|
|
12231
|
+
// it is captured where it lands rather than where it was let go of.
|
|
12232
|
+
const landCopyOn = async (targetElement, answer) => {
|
|
12233
|
+
const clone = cloneWrapper.firstElementChild;
|
|
12234
|
+
// Bake the current visual position (transform included) into the CSS vars
|
|
12235
|
+
// so the copy stays where the user released it when the transform goes.
|
|
12236
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
12237
|
+
gestureInfo.cancelPosition();
|
|
12238
|
+
// Where the copy comes down is not always the thing it came down ON: a
|
|
12239
|
+
// place of a board can be larger than what stands on it, and the copy
|
|
12240
|
+
// has to keep its own size and land where the item will be. Said with
|
|
12241
|
+
// an element, because the caller has one — the piece already standing
|
|
12242
|
+
// there, the empty slot waiting.
|
|
12243
|
+
const syncCloneWithDropTarget = (landingElement = targetElement) => {
|
|
12244
|
+
setCloneViewportRect(cloneWrapper, landingElement);
|
|
12245
|
+
// Removing this attr drops the CSS scale, so the browser captures the
|
|
12246
|
+
// copy at scale 1 as the "new" state.
|
|
12247
|
+
clone.removeAttribute("navi-drag-clone");
|
|
12248
|
+
};
|
|
12249
|
+
await answer(syncCloneWithDropTarget);
|
|
12250
|
+
};
|
|
12055
12251
|
if (dropMeans === "toss") {
|
|
12056
12252
|
// Bake the position the hand left it at, so the flight starts from
|
|
12057
12253
|
// there rather than from where the clone was declared.
|
|
@@ -12064,25 +12260,10 @@ const startDragToCarryCopy = (event, {
|
|
|
12064
12260
|
// place.
|
|
12065
12261
|
await settleCloneBack(cloneWrapper, draggedElement);
|
|
12066
12262
|
}
|
|
12067
|
-
} else if (dropMeans === "
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
12072
|
-
gestureInfo.cancelPosition();
|
|
12073
|
-
const fromId = getItemId(draggedElement);
|
|
12074
|
-
const toId = currentBeforeElement ? getItemId(currentBeforeElement) : null;
|
|
12075
|
-
// provide onReorder a way to synchronously move the clone to the drop target
|
|
12076
|
-
// (meant to be used inside a startViewTransition callback)
|
|
12077
|
-
const syncCloneWithDropTarget = () => {
|
|
12078
|
-
// Snap the CSS-var position to the drop target rect so the browser
|
|
12079
|
-
// captures the "new" state at the landing position.
|
|
12080
|
-
setCloneViewportRect(cloneWrapper, currentReleaseElement);
|
|
12081
|
-
// Removing this attr drops the CSS scale(1.15), so the browser
|
|
12082
|
-
// captures the clone at scale 1 as the "new" state.
|
|
12083
|
-
clone.removeAttribute("navi-drag-clone");
|
|
12084
|
-
};
|
|
12085
|
-
await onReorder(fromId, toId, syncCloneWithDropTarget);
|
|
12263
|
+
} else if (dropMeans === "land") {
|
|
12264
|
+
await landCopyOn(currentReleaseElement, syncCloneWithDropTarget => onLand(getItemId(draggedElement), getItemId(currentReleaseElement), syncCloneWithDropTarget));
|
|
12265
|
+
} else if (dropMeans === "reorder") {
|
|
12266
|
+
await landCopyOn(currentReleaseElement, syncCloneWithDropTarget => onReorder(getItemId(draggedElement), currentBeforeElement ? getItemId(currentBeforeElement) : null, syncCloneWithDropTarget));
|
|
12086
12267
|
}
|
|
12087
12268
|
draggedElement.removeAttribute("navi-drag-clone-source");
|
|
12088
12269
|
cloneWrapper.remove();
|
|
@@ -12153,6 +12334,14 @@ const createDropHint = () => {
|
|
|
12153
12334
|
div.innerHTML = dropHintTemplate.trim();
|
|
12154
12335
|
return div.firstElementChild;
|
|
12155
12336
|
};
|
|
12337
|
+
const createDropSurface = () => {
|
|
12338
|
+
const div = document.createElement("div");
|
|
12339
|
+
div.className = "navi_drop_surface";
|
|
12340
|
+
// Manual, like the copy it accompanies: it is opened and closed with the drag
|
|
12341
|
+
// and must survive an Escape or a click elsewhere.
|
|
12342
|
+
div.setAttribute("popover", "manual");
|
|
12343
|
+
return div;
|
|
12344
|
+
};
|
|
12156
12345
|
|
|
12157
12346
|
/**
|
|
12158
12347
|
* The copy leaves the screen the way it was thrown, and the caller says what that
|
|
@@ -12211,13 +12400,6 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
12211
12400
|
// scale(1.15) expands from where the user clicked, not the element center.
|
|
12212
12401
|
// These offsets are element-relative so viewport coords are correct here.
|
|
12213
12402
|
wrapper.style.setProperty("--drag-origin", `${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`);
|
|
12214
|
-
// The clone is appended to document.body, so it loses inherited styles
|
|
12215
|
-
// from the original parent. Copy the computed inherited properties that
|
|
12216
|
-
// are most likely to affect visual appearance.
|
|
12217
|
-
const computedStyle = getComputedStyle(element.parentElement);
|
|
12218
|
-
for (const property of INHERITED_PROPERTIES_TO_COPY_SET) {
|
|
12219
|
-
wrapper.style.setProperty(property, computedStyle.getPropertyValue(property));
|
|
12220
|
-
}
|
|
12221
12403
|
const elementClone = element.cloneNode(true);
|
|
12222
12404
|
// A deep copy copies the ids too, and two elements answering to one id is a
|
|
12223
12405
|
// document that lies: getElementById picks whichever comes first, an anchor
|
|
@@ -12235,12 +12417,15 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
12235
12417
|
elementClone.setAttribute("data-grabbed", "");
|
|
12236
12418
|
elementClone.style.viewTransitionName = "navi-drag-clone";
|
|
12237
12419
|
wrapper.appendChild(elementClone);
|
|
12238
|
-
|
|
12420
|
+
// Beside the thing it copies, so it stands where that thing stands: every
|
|
12421
|
+
// inherited value and every custom property the original reads, the copy reads
|
|
12422
|
+
// too, and a rule written for an item in this list finds the copy as well. The
|
|
12423
|
+
// top layer is what lets it stay there — a popover is painted above the page
|
|
12424
|
+
// whatever its depth in the tree, and being fixed keeps it out of the
|
|
12425
|
+
// scrollable overflow of the list it sits in.
|
|
12426
|
+
element.parentElement.appendChild(wrapper);
|
|
12239
12427
|
return wrapper;
|
|
12240
12428
|
};
|
|
12241
|
-
const INHERITED_PROPERTIES_TO_COPY_SET = new Set(["color", "font-family", "font-size", "font-weight", "font-style", "line-height", "letter-spacing",
|
|
12242
|
-
// in case the item has border-radius: inherit. The clone can inherit too
|
|
12243
|
-
"border-radius"]);
|
|
12244
12429
|
|
|
12245
12430
|
const startDragToResizeGesture = (
|
|
12246
12431
|
pointerdownEvent,
|
|
@@ -12392,15 +12577,6 @@ import.meta.css = [/* css */`
|
|
|
12392
12577
|
[data-drag-travel*="y"] * {
|
|
12393
12578
|
overscroll-behavior-y: contain !important;
|
|
12394
12579
|
}
|
|
12395
|
-
:root[${WALKING_ATTRIBUTE}] {
|
|
12396
|
-
/* A drag over text selects it on the way, and the blue trail says the
|
|
12397
|
-
gesture was understood as something else. Not from the press: a press on
|
|
12398
|
-
text IS how one selects it, and only a press that has become a travel has
|
|
12399
|
-
said it was about something else — which is also why this cannot be the
|
|
12400
|
-
whole answer, and why the selection made meanwhile is dropped by hand
|
|
12401
|
-
(see dropSelection). */
|
|
12402
|
-
user-select: none;
|
|
12403
|
-
}
|
|
12404
12580
|
`, "@jsenv/dom/src/interaction/drag/drag_to_travel.js"];
|
|
12405
12581
|
|
|
12406
12582
|
// How far a pointer goes before it is a travel rather than a click: below this
|
|
@@ -12476,29 +12652,6 @@ const axesLeftBy = (axes, fromElement, stopElement, attribute) => {
|
|
|
12476
12652
|
return left;
|
|
12477
12653
|
};
|
|
12478
12654
|
|
|
12479
|
-
/**
|
|
12480
|
-
* What the browser painted blue while it was still allowed to think this press
|
|
12481
|
-
* was about text.
|
|
12482
|
-
*
|
|
12483
|
-
* A mouse dragged across a page selects what it crosses, and it starts doing so
|
|
12484
|
-
* from the first pixel — while this is still spending ten of them deciding
|
|
12485
|
-
* whether the press is a travel at all. By the time it is one, a trail is
|
|
12486
|
-
* already there. `user-select: none` (see the CSS) stops it GROWING, it does not
|
|
12487
|
-
* take back what was made, and a selection already under way goes on being
|
|
12488
|
-
* extended by some browsers whatever the property says.
|
|
12489
|
-
*
|
|
12490
|
-
* So it is dropped, and dropped again as it comes back. The cause is outside —
|
|
12491
|
-
* one gesture, two things answering it, and the browser answers first — and
|
|
12492
|
-
* cannot be removed from here; what can be removed is its trace, on every frame
|
|
12493
|
-
* of a travel that is walking.
|
|
12494
|
-
*/
|
|
12495
|
-
const dropSelection = () => {
|
|
12496
|
-
const selection = window.getSelection();
|
|
12497
|
-
if (selection && !selection.isCollapsed) {
|
|
12498
|
-
selection.removeAllRanges();
|
|
12499
|
-
}
|
|
12500
|
-
};
|
|
12501
|
-
|
|
12502
12655
|
/**
|
|
12503
12656
|
* A scroller between the pointer and the box it is in, with room left the way
|
|
12504
12657
|
* the gesture goes: it gets the gesture, and nothing travels — dragging a row
|
|
@@ -12817,9 +12970,6 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
12817
12970
|
};
|
|
12818
12971
|
document.documentElement.setAttribute(WALKING_ATTRIBUTE, axis);
|
|
12819
12972
|
}
|
|
12820
|
-
// Whatever the press was taken for until now, it was taken for something
|
|
12821
|
-
// else (see dropSelection).
|
|
12822
|
-
dropSelection();
|
|
12823
12973
|
const {
|
|
12824
12974
|
axis
|
|
12825
12975
|
} = travel;
|
|
@@ -12881,10 +13031,6 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
12881
13031
|
return;
|
|
12882
13032
|
}
|
|
12883
13033
|
finish();
|
|
12884
|
-
// Last chance: the pointer moves once more as it goes up, and by then the
|
|
12885
|
-
// attribute above is off — so a trail made on that last move would be the
|
|
12886
|
-
// one that stays (see dropSelection).
|
|
12887
|
-
dropSelection();
|
|
12888
13034
|
const {
|
|
12889
13035
|
axis,
|
|
12890
13036
|
size,
|