@jsenv/navi 0.29.175 → 0.29.176
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_navi.js +14 -5
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/drag_to_travel.md +159 -19
- package/package.json +2 -2
package/docs/drag_to_travel.md
CHANGED
|
@@ -16,21 +16,35 @@ and are referenced from here rather than restated.
|
|
|
16
16
|
## What the rules are
|
|
17
17
|
|
|
18
18
|
- A press is not a gesture until it has wandered ~10px, and the axis it leans on
|
|
19
|
-
then is the axis it walks, for good
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
answers
|
|
19
|
+
then is the axis it walks, for good — how that axis is read, and why the
|
|
20
|
+
reading is biased, has [a section of its own
|
|
21
|
+
below](#the-axis-is-read-once-and-read-with-a-bias). **Except on something
|
|
22
|
+
already moving**: there the hand said what it wanted by reaching for it, so
|
|
23
|
+
the gesture answers from its first pixel and owes it every one of them —
|
|
24
|
+
asking it to cross a threshold is asking twice, and over those pixels the
|
|
25
|
+
thing it is holding answers to nobody. A diagonal would ask for two travels at
|
|
24
26
|
once and only one screen can arrive.
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
the distance
|
|
27
|
+
- **Of the way covered when the gesture forms, only the threshold is withheld**
|
|
28
|
+
— never the whole first report. Movement reports are coalesced to frames, so
|
|
29
|
+
the faster the gesture the bigger its first report is: charged whole, a flick
|
|
30
|
+
delivered in one report sets off with nothing left of itself to have pulled —
|
|
31
|
+
it moves nothing on screen and is refused at the release for it, and what the
|
|
32
|
+
hand feels is a swipe that did strictly nothing.
|
|
33
|
+
- **A hand still moving when it lets go says where this goes, and it says it
|
|
34
|
+
both ways.** Towards what it was bringing in, the travel carries on — however
|
|
35
|
+
slowly and whatever the distance: an intention still being acted on at the
|
|
36
|
+
lift. Away from it, fast, everything goes back whatever the distance already
|
|
37
|
+
covered — otherwise a screen caught in flight and thrown back still arrives,
|
|
38
|
+
because the picture alone decided. Only a release at rest, or a slow retreat,
|
|
39
|
+
is judged by the picture — about a third of a box — because there the
|
|
40
|
+
position is the only witness left.
|
|
41
|
+
- **The bars those verdicts compare against sit well below the speed the
|
|
42
|
+
fingertip actually has, and they must.** Velocity is averaged over a trailing
|
|
43
|
+
window (pointer events arrive irregularly, and the last one before a release
|
|
44
|
+
often repeats its coordinates — measured on that pair alone, every throw ends
|
|
45
|
+
at zero), and the release itself adds one more sample at the same place a
|
|
46
|
+
moment later. Both pull the measure down from the hand's peak: a threshold
|
|
47
|
+
sized against the hand refuses the hand.
|
|
34
48
|
- Pulling towards nothing follows the finger at a fraction of its distance and
|
|
35
49
|
comes back: a wall one can lean on, never walk through.
|
|
36
50
|
- **A direction that will be refused is one of those walls, and it must be a
|
|
@@ -52,6 +66,43 @@ and are referenced from here rather than restated.
|
|
|
52
66
|
hand that came in fast would push against a screen that does not answer for as
|
|
53
67
|
many pixels as it went too far.
|
|
54
68
|
|
|
69
|
+
## The axis is read once, and read with a bias
|
|
70
|
+
|
|
71
|
+
The axis is decided on the first movement report after the threshold and never
|
|
72
|
+
revisited. That report is ~10-25px of movement, and for a thumb those pixels
|
|
73
|
+
misstate the gesture: a thumb swiping sideways moves along an ARC, and the
|
|
74
|
+
start of the arc leans off-axis far more than the swipe does. Read even
|
|
75
|
+
(whichever axis covered more), the lean hands the whole press to an axis
|
|
76
|
+
nobody meant — and a press given up is given up WHOLE: the hand's remaining
|
|
77
|
+
hundred pixels are read by no one, and what it feels is a swipe that did
|
|
78
|
+
nothing at all.
|
|
79
|
+
|
|
80
|
+
So on a box that travels one axis, the reading is biased towards that axis:
|
|
81
|
+
the cross axis takes the press only when it clearly dominates the first
|
|
82
|
+
report (2×, `AXIS_CROSS_DOMINANCE`). A gesture that really belongs to the
|
|
83
|
+
other axis — a scroll — is near-pure on its axis from the first pixel (4-6×),
|
|
84
|
+
so it still leaves whole, at once, in the same tick. A box that travels both
|
|
85
|
+
axes keeps the even reading: every answer is a travel there, and there is
|
|
86
|
+
nothing to protect.
|
|
87
|
+
|
|
88
|
+
Why the decision cannot simply wait for more evidence — this boundary is
|
|
89
|
+
physics, not caution:
|
|
90
|
+
|
|
91
|
+
- the browser is racing for the same press, on the axis `touch-action` leaves
|
|
92
|
+
it, and it holds off only while the touchmoves are being refused;
|
|
93
|
+
- refusing a touchmove is **irreversible for that touch**: whether the stream
|
|
94
|
+
can scroll is decided at its first refusable move, and un-refusing later
|
|
95
|
+
resurrects nothing;
|
|
96
|
+
- so every frame spent gathering evidence is a frame of the page's scroll
|
|
97
|
+
spent, for good. A decision deferred until the arc has proven itself would
|
|
98
|
+
make every genuinely vertical swipe over the box a dead gesture — the exact
|
|
99
|
+
bug, mirrored onto the page.
|
|
100
|
+
|
|
101
|
+
The bias moves the frame-one boundary to where the two hands actually
|
|
102
|
+
separate; nothing can remove the boundary. A first report steeper than the
|
|
103
|
+
bias (a start more than twice off-axis) is genuinely ambiguous with a scroll
|
|
104
|
+
and goes to the page — which at least answers it visibly.
|
|
105
|
+
|
|
55
106
|
## Two inputs, one travel
|
|
56
107
|
|
|
57
108
|
A thumb dragging the page and a wheel pushing it sideways both ask to travel,
|
|
@@ -83,6 +134,40 @@ Two things say "the hand asked again", and a stream only ever has one of them:
|
|
|
83
134
|
decay — is therefore one step, and a second flick over the tail of the first
|
|
84
135
|
is heard as its own.
|
|
85
136
|
|
|
137
|
+
Both are read against a stream that never announces itself: the browser sees
|
|
138
|
+
one unbroken burst (the tail of the first push never went silent), so "the
|
|
139
|
+
hand asked again" is reconstructed, never received. Two consequences are easy
|
|
140
|
+
to get wrong from there:
|
|
141
|
+
|
|
142
|
+
- **a second push is answered like a first event — a screen, now** — never with
|
|
143
|
+
credit towards one. The price a screen after the first costs (an accumulated
|
|
144
|
+
delta, deliberately steep) exists to keep momentum from walking slides on its
|
|
145
|
+
own; charged to a hand that pushed again over the tail, it reads as "my
|
|
146
|
+
swipes are ignored until I wait for silence". The stream itself says which is
|
|
147
|
+
which: once it has faded into a recognized tail, growing **twice in a row**
|
|
148
|
+
is a hand (`WHEEL_REGROW_RUN`) — decay jitter bumps up in isolated events and
|
|
149
|
+
never twice consecutively.
|
|
150
|
+
- **the other axis, over a faded stream, is not ours to swallow.** Within a
|
|
151
|
+
push, cross-axis events are the push's own wobble and are eaten with the
|
|
152
|
+
rest — a hand is never perfectly straight. But once our axis is momentum
|
|
153
|
+
only, a hand pushing the other way is a NEW gesture, and on an axis the box
|
|
154
|
+
does not travel it is not ours at all: it is handed back to whatever scrolls
|
|
155
|
+
under the pointer, untouched, and it does not renew the claim either. Renewed
|
|
156
|
+
by what it swallows, the gesture would be kept alive by the very scroll it
|
|
157
|
+
ignores — every attempt extending the silence it was waiting for. Handed
|
|
158
|
+
back, though, only when it IS a scroll: at the end of a fade the deltas are a
|
|
159
|
+
pixel or two on each axis, and read alone such an event says "the other
|
|
160
|
+
axis" as easily as it says nothing — passed along, those crumbs scroll the
|
|
161
|
+
slide's own content a few pixels under the travel, a header creeping off the
|
|
162
|
+
edge of the box. Three things separate a scroll from the crumbs, and all
|
|
163
|
+
three are asked for: the other axis clearly dominates the event (the same
|
|
164
|
+
bar a drag uses to give up its press); the event is the size of a hand, or
|
|
165
|
+
the run is growing event over event — a scroll ramping up from rest, which
|
|
166
|
+
decay never looks like; and it is a RUN — wobble is isolated, a scroll
|
|
167
|
+
insists (`WHEEL_CROSS_RUN`, `WHEEL_CROSS_CONFIRM_DELTA`). The run costs a
|
|
168
|
+
real scroll its first event or two — a few pixels, one notch of a stepped
|
|
169
|
+
wheel — which is the cheap side of the trade.
|
|
170
|
+
|
|
86
171
|
Taking it is also the only way to stop the browser from answering it: on a
|
|
87
172
|
laptop a horizontal two-finger swipe IS the back-navigation gesture, and a
|
|
88
173
|
region that neither takes it nor lets it go is the worst of the three — the page
|
|
@@ -442,11 +527,29 @@ hold:
|
|
|
442
527
|
moves are read from the window, filtered by pointer id, so nothing is missed
|
|
443
528
|
for not owning the pointer.
|
|
444
529
|
|
|
445
|
-
And the refusal has to be
|
|
446
|
-
refuses later: whether a touchmove can be refused at all is decided
|
|
447
|
-
touch begins, from the non-passive listeners present at that moment.
|
|
448
|
-
|
|
449
|
-
|
|
530
|
+
And the refusal has to be _listenable_ **before the finger lands**, even though
|
|
531
|
+
it only refuses later: whether a touchmove can be refused at all is decided
|
|
532
|
+
when the touch begins, from the non-passive listeners present at that moment.
|
|
533
|
+
Registered at the pointerdown it is already too late — the events arrive
|
|
534
|
+
`cancelable: false`, the listener runs, calls `preventDefault`, and nothing
|
|
535
|
+
happens. What that costs is not the start of the gesture but its middle: the
|
|
536
|
+
travel takes the press fine (the axis the browser was left never moved), and
|
|
537
|
+
then the thumb's arc bends towards the axis `touch-action` leaves to the page —
|
|
538
|
+
the browser starts the page's own scroll and CANCELS the pointer stream, and
|
|
539
|
+
the screen the hand was carrying snaps back with the finger still down.
|
|
540
|
+
|
|
541
|
+
So every box that travels says it from its own render, next to the attribute
|
|
542
|
+
naming its axes: `onTouchMove={keepTouchRefusable}` — a no-op listener whose
|
|
543
|
+
being registered is the whole of it (the same function everything picked up
|
|
544
|
+
and carried registers, see `keepTouchRefusable` in drag_after_intent.js). A
|
|
545
|
+
JSX prop is enough because the passive-by-default intervention only covers
|
|
546
|
+
`window`, `document` and `document.body`: on any other element a plain
|
|
547
|
+
listener is non-passive already — which matters, because a framework's event
|
|
548
|
+
prop cannot say `passive: false`. The one place the explicit option is
|
|
549
|
+
load-bearing is a listener that goes down on the document root (see
|
|
550
|
+
drag_to.js). And a surface whose swipe starts only from a grip carrying
|
|
551
|
+
`touch-action: none` needs none of this: the browser was never offered
|
|
552
|
+
anything there to take back.
|
|
450
553
|
|
|
451
554
|
### A navi component that reads the pointer marks ITSELF
|
|
452
555
|
|
|
@@ -552,3 +655,40 @@ dropped, and those things are left where they are, live: the bar jumps to the
|
|
|
552
655
|
tab one is heading for instead of sliding to a tab one is not. A slide would be
|
|
553
656
|
nicer, and it is not available — the browser measured both of its ends before
|
|
554
657
|
the hand changed its mind, and neither can be asked for again.
|
|
658
|
+
|
|
659
|
+
## Verifying a gesture
|
|
660
|
+
|
|
661
|
+
A gesture is verified the way movement is (see the animations skill,
|
|
662
|
+
"Verifying") — driven synthetically, read as numbers — but what has to be
|
|
663
|
+
synthesized is the HAND'S imperfection, because that is where gesture bugs
|
|
664
|
+
live. A gesture simulated as clean, evenly-spaced points along one axis passes
|
|
665
|
+
forever and proves nothing:
|
|
666
|
+
|
|
667
|
+
- **coalesce**: deliver a whole flick in one or two moves — that is what a
|
|
668
|
+
fast thumb looks like to the main thread;
|
|
669
|
+
- **arc**: lean the first points off-axis the way a thumb does, and only then
|
|
670
|
+
commit to the axis;
|
|
671
|
+
- **interleave**: start the second gesture while the first one's travel — or
|
|
672
|
+
its momentum tail — is still playing, and sweep the delay between the two;
|
|
673
|
+
- **for a wheel**: a swipe is a ramp, a peak and a decaying tail at frame
|
|
674
|
+
rate, and the interesting cases live in the tail — a second swipe over it, a
|
|
675
|
+
cross-axis scroll into it, decay jitter that must not step.
|
|
676
|
+
|
|
677
|
+
The reading side has traps of its own:
|
|
678
|
+
|
|
679
|
+
- coordinates measured off an element that is mid-travel are stale by dispatch
|
|
680
|
+
time — aim at where the CONTAINER is, never at where the content was;
|
|
681
|
+
- a scripted gesture's wall-clock is diluted by driver round-trips, so
|
|
682
|
+
measured velocities come out below the numbers written in the script — read
|
|
683
|
+
the measured ones, not the intended ones;
|
|
684
|
+
- what the real browser does with an unclaimed touch — take it for a pan and
|
|
685
|
+
cancel the stream, kill a momentum tail the moment a finger lands — does not
|
|
686
|
+
exist in emulation at all. Claims about it are only ever settled on a
|
|
687
|
+
device (and the Firefox wheel limitation above is the same kind of fact).
|
|
688
|
+
|
|
689
|
+
What to read while it runs: the `navi_drag_*` events (grab, start, release)
|
|
690
|
+
say which stage a press reached and with what velocity; the box's state
|
|
691
|
+
attribute (`data-slide-current`, the URL) says what came of it; the track's
|
|
692
|
+
measured position, sampled across frames, says what the screen did in
|
|
693
|
+
between — including the mini-movements and stalls no end-state check ever
|
|
694
|
+
sees.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.176",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Library of components including navigation to create frontend applications",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@jsenv/dom": "0.17.
|
|
32
|
+
"@jsenv/dom": "0.17.42",
|
|
33
33
|
"@jsenv/humanize": "1.7.8",
|
|
34
34
|
"@jsenv/validity": "0.4.3"
|
|
35
35
|
},
|