@jsenv/navi 0.29.31 → 0.29.33

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.
@@ -62,7 +62,8 @@ condition: `{ swipe_right: canArchive && archive }`.
62
62
  | `mousedown` `mouseup` `click` `dblclick` `contextmenu` | the browser's own events |
63
63
  | `swipe_left` `swipe_right` `swipe_up` `swipe_down` | a press that travels |
64
64
  | `longpress` | a press held still |
65
- | `move` `reorder` `toss` | the element carried, and what letting go means |
65
+ | `move` `reorder` `land` `toss` | the element carried, and what letting go means |
66
+ | `grab` | the instant a drag takes hold of it |
66
67
  | `"keyboard:<shortcut>"` | keys, e.g. `"keyboard:ctrl+backspace"` |
67
68
 
68
69
  A name nothing knows how to detect produces a dev warning naming the detectors
@@ -141,17 +142,18 @@ comes back once it settles — a failure leaves the row in place so it can be tr
141
142
  again. What a success does to the element is yours (a list that redemands its
142
143
  rows, a row that leaves): navi does not make it disappear.
143
144
 
144
- ## Carrying something: `move`, `reorder`, `toss`
145
+ ## Carrying something: `move`, `reorder`, `land`, `toss`
145
146
 
146
- All three are the same gesture — the element is picked up and carried — and what
147
+ All four are the same gesture — the element is picked up and carried — and what
147
148
  differs is the release. One detector reads them all, because it is one press.
148
149
 
149
- `reorder` and `toss` **combine**: dropped on another item the element changes
150
- places, thrown far and fast it is gotten rid of. `move` does **not** combine with
151
- `reorder` an element either goes where it is put or takes a place in a list,
152
- and one release cannot mean both (a dev warning says so).
150
+ `toss` **combines** with either of the two others: dropped on another item the
151
+ element changes places, thrown far and fast it is gotten rid of. `move`,
152
+ `reorder` and `land` do **not** combine with each other an element either goes
153
+ where it is put, takes a place in a list, or comes down on a place, and one
154
+ release cannot mean two of those (a dev warning says so).
153
155
 
154
- `move` carries the element ITSELF and leaves it where it was put; the other two
156
+ `move` carries the element ITSELF and leaves it where it was put; the others
155
157
  carry a copy and put the original back. That is the same difference said in layout
156
158
  terms: something moved has a new place of its own, something reordered had its
157
159
  place taken by the list.
@@ -228,6 +230,109 @@ name what moves.
228
230
  | `data-drag-delay` `data-drag-slop` `data-drag-threshold` | when the press becomes a grab |
229
231
  | `data-toss-distance` `data-toss-speed` | how far and how fast counts as a throw |
230
232
 
233
+ ### Landing on a place: `land`
234
+
235
+ `reorder` and `land` both come down on an item, and what separates them is **what
236
+ a place is**. A row of a list is a place BETWEEN two others — free by
237
+ construction, so the answer is an insertion and putting a row back where it
238
+ already was is a no-op. A place of a board is a place of its own, which may
239
+ already be taken — so nothing is inserted, nothing is a no-op, and the answer is
240
+ simply "this one came down on that one". What that means is yours: take the
241
+ place, swap the two, refuse.
242
+
243
+ ```jsx
244
+ <Box
245
+ id={playerId}
246
+ interactions={{
247
+ land: (event) => {
248
+ const { fromId, toId, syncCloneWithDropTarget } = event.detail;
249
+ return document.startViewTransition(() => {
250
+ syncCloneWithDropTarget();
251
+ setLineup((previous) => swapPlaces(previous, fromId, toId));
252
+ }).finished;
253
+ },
254
+ }}
255
+ />
256
+ ```
257
+
258
+ `toId` is an element and **never null**: a copy over nothing is a release that
259
+ meant nothing, and the interaction does not happen at all.
260
+
261
+ **Which elements are places: those marked `data-droppable`, and only those.**
262
+ Declaring `land` says an element can be CARRIED, which on a board is a different
263
+ thing from being somewhere one can be put — a zone receives without ever being
264
+ carried, a piece is carried without ever receiving, and both at once is a third
265
+ case (dropped on a piece, the two swap, so it says `data-droppable` as well). A
266
+ list has no such distinction, every row being both, which is why `reorder` needs
267
+ no marker in the markup.
268
+
269
+ Places are looked for among the carried element's **siblings**, so a piece must
270
+ not be nested inside its place: nested, it would see that one place and have
271
+ nowhere else to go. Draw the pieces beside the places, positioned over them.
272
+
273
+ **When the place is bigger than what stands on it** — a zone holding a smaller
274
+ card, a square holding a piece — the copy must not take the place's box, or it
275
+ resizes on landing and resizes back when the real element appears.
276
+ `syncCloneWithDropTarget` takes an element for that: the copy comes down on THAT
277
+ box instead of the target's. Pass whatever occupies the destination — the piece
278
+ already standing there, the empty slot waiting.
279
+
280
+ ```jsx
281
+ land: (event) => {
282
+ const { fromId, toId, syncCloneWithDropTarget } = event.detail;
283
+ const landingElement = pieceAt(toId) || slotOf(toId);
284
+ return document.startViewTransition(() => {
285
+ syncCloneWithDropTarget(landingElement);
286
+
287
+ }).finished;
288
+ };
289
+ ```
290
+
291
+ The hint follows what a place is: a line drawn in the gap for `reorder`, the
292
+ place itself lit up for `land`. Both are drawn inside the carried element's
293
+ parent, so the variables dressing them are read from the list or the board and
294
+ reach them by plain inheritance.
295
+
296
+ | Variable | Dresses |
297
+ | ------------------------------------------------------------------------------------------ | ----------------------- |
298
+ | `--drop-hint-size` `--drop-hint-background-color` `--drop-hint-border-radius` | the line of a reorder |
299
+ | `--drop-hint-margin-x` `--drop-hint-margin-y` `--drop-hint-arrow-size` | where it sits, its caps |
300
+ | `--drop-surface-border-width` `--drop-surface-border-color` `--drop-surface-border-radius` | the lit place of a land |
301
+ | `--drop-surface-background-color` | and its fill |
302
+
303
+ ### Saying the grab is acquired: `grab`
304
+
305
+ The three above answer the **release**. Between the press and the release there is
306
+ one instant that counts for the hand: the one where the object stops being pressed
307
+ and starts being held. `grab` is that instant — the same one whichever way the drag
308
+ was entered, a finger held still or a mouse travelled a few pixels.
309
+
310
+ ```jsx
311
+ <Box
312
+ interactions={{
313
+ toss: (event) => remove(event.detail.id),
314
+ grab: () => navigator.vibrate?.(10),
315
+ }}
316
+ />
317
+ ```
318
+
319
+ It matters most where it is least visible. On a screen the held object is under the
320
+ thumb that hides it, so the only feedback available is the one that is felt; without
321
+ it the hand waits, doubts the press was heard, and lets go too early — the whole
322
+ gesture fails, not its decoration. With a mouse the grab is acquired after a few
323
+ pixels and the object has visibly moved, so the answer is already there.
324
+
325
+ Nothing here is about vibration: a sound, a class, a measure are the same moment.
326
+
327
+ `grab` **reports, it does not ask**: what it returns is not waited on, and
328
+ preventing its event does not call the gesture off. And it is not an interaction on
329
+ its own — declared without `move`, `reorder` or `toss` there is no gesture for it to
330
+ be the beginning of, and a dev warning says so. Its detail carries `pointerType` and
331
+ the `gestureInfo`.
332
+
333
+ A `longpress` needs none of this: it already happens at the moment the hold is
334
+ acquired, not at the release.
335
+
231
336
  ### Dressing the clone
232
337
 
233
338
  What the pointer carries is a copy, and a copy of a transparent element is
@@ -250,7 +355,27 @@ it:
250
355
  ```
251
356
 
252
357
  Reusing the item's own class is the point: the copy is that item, so it is styled
253
- as that item plus whatever being carried changes. The copy is a real element in the
358
+ as that item plus whatever being carried changes.
359
+
360
+ **Which is also the trap, for anything positioned on a board.** The copy is the
361
+ same element re-parented into a carrier box, so a geometry written in the style
362
+ attribute follows it there — `width: calc(50% - 2 * var(--gap))` then means half
363
+ of the copy instead of half of the board, and the piece is carried at the wrong
364
+ size. Put what a piece LOOKS like in a class and leave only which place it is
365
+ inline (two custom properties will do), then let it fill its carrier:
366
+
367
+ ```css
368
+ [navi-drag-clone-wrapper] .piece {
369
+ position: static;
370
+ width: 100%;
371
+ height: 100%;
372
+ translate: none;
373
+ }
374
+ ```
375
+
376
+ Said of what is inside the wrapper rather than of `[navi-drag-clone]` itself,
377
+ because the copy loses that mark as it lands — that is how it drops its lift for
378
+ the transition — and it has to keep its size all the way down. The copy is a real element in the
254
379
  page, in the top layer, and everything about its look is reachable from CSS —
255
380
  including these, read off the dragged element so a whole list or a single item can
256
381
  answer:
@@ -258,7 +383,7 @@ answer:
258
383
  | Variable | What it changes |
259
384
  | --------------------- | ------------------------------------------------------------- |
260
385
  | `--drag-clone-shadow` | what being lifted casts; `none` for something that flies flat |
261
- | `--drag-clone-scale` | how much bigger it gets once picked up |
386
+ | `--drag-clone-scale` | how much bigger it gets once picked up; `1` to keep its size |
262
387
 
263
388
  **What stays behind is the source, not a hole.** The original is never taken out of
264
389
  the page — it keeps its place in the layout and wears `navi-drag-clone-source`,
@@ -284,9 +409,32 @@ back to it.
284
409
 
285
410
  `data-drag-axis` says which axes the drag walks, and its default is not the same
286
411
  for every outcome: `reorder` alone walks the list (`y`, or `x` for a list that runs
287
- sideways), while a `move` goes wherever it is put and a `toss` wherever it was
288
- thrown (`xy`). `data-drag-delay`, `data-drag-slop`, `data-drag-threshold` tune when
289
- the press becomes a grab.
412
+ sideways), while a `move` goes wherever it is put, a `land` wherever the board has
413
+ places and a `toss` wherever it was thrown (`xy`). `data-drag-delay`,
414
+ `data-drag-slop`, `data-drag-threshold` tune when the press becomes a grab.
415
+
416
+ ### A control inside something draggable
417
+
418
+ `data-drag-ignore` says the press there is none of the gesture's business: the
419
+ element under it never starts a drag, and keeps both its cursor and its text
420
+ selection. That is what a button living inside a carried piece needs — a cross
421
+ that removes it, a menu — otherwise the piece is picked up from the button like
422
+ from anywhere else. A click of its own usually has to stop there too, or the
423
+ piece reads it as its own click.
424
+
425
+ ### What says a thing can be picked up
426
+
427
+ Almost nothing, on purpose. A **handle** (`data-drag-handle`) exists only to drag,
428
+ so it shows the hand; a **source** does not — it drags only once the intent shows,
429
+ a plain click on it stays a click, and it is usually something else FIRST (a link,
430
+ a card one opens). The cursor says what an element IS, and the gesture is not the
431
+ one who knows, so it leaves it alone (`default`, not an I-beam: the text inside
432
+ cannot be selected either).
433
+
434
+ So on a board where a piece is also clickable, the cursor is already spoken for and
435
+ the affordance has to be said in the piece itself — a grip mark in a corner, a
436
+ shadow appearing under the pointer, a handle. It is worth deciding, not defaulting:
437
+ a board one may drag on is worth nothing if nobody tries.
290
438
 
291
439
  ## Tuning
292
440
 
@@ -400,7 +548,10 @@ container above it does not take the gesture:
400
548
  the registry.
401
549
  - `src/control/interaction/interaction_press.js` — swipes and holds, and what a
402
550
  swipe writes on the element.
551
+ - `src/control/interaction/interaction_drag.js` — `move`, `reorder`, `land`,
552
+ `toss` and the `grab` moment.
403
553
  - `src/control/interaction/interaction_keyboard.js`,
404
554
  `interaction_native.js` — the other two detectors.
405
555
  - `src/control/demos/38_interactions_demo.html` — every case above, plus a
406
- mailbox and a custom `swipe_out` gesture registered from the page.
556
+ mailbox, a board whose places are zones and the same board whose places are the
557
+ pieces, and a custom gesture registered from the page.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.31",
3
+ "version": "0.29.33",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {