@jsenv/navi 0.29.49 → 0.29.51
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 +281 -146
- package/dist/jsenv_navi.js.map +17 -9
- package/dist/jsenv_navi_side_effects.js +36 -16
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/docs/drag_to_travel.md +97 -8
- package/package.json +2 -2
package/docs/drag_to_travel.md
CHANGED
|
@@ -94,6 +94,8 @@ are read before the box moves:
|
|
|
94
94
|
scrolls sideways inside a page still scrolls sideways.
|
|
95
95
|
3. **Another travelling box in between.** The innermost one takes the axes it
|
|
96
96
|
walks, and leaves the ones it does not to whoever is above it.
|
|
97
|
+
4. **A surface in the top layer in between.** Nothing above it gets the gesture
|
|
98
|
+
at all — see [A surface in the top layer](#a-surface-in-the-top-layer).
|
|
97
99
|
|
|
98
100
|
### Boxes inside boxes
|
|
99
101
|
|
|
@@ -115,6 +117,21 @@ last slide does not hand the gesture over mid-drag: it leans on its wall, the wa
|
|
|
115
117
|
it does when it is alone. Travelling the box around it means starting the gesture
|
|
116
118
|
outside it.
|
|
117
119
|
|
|
120
|
+
### A surface in the top layer
|
|
121
|
+
|
|
122
|
+
A popover, a modal `<dialog>`, an element gone fullscreen: it is written inside
|
|
123
|
+
whatever opened it — a slide, a page that travels — and the browser paints it
|
|
124
|
+
over the whole screen. The DOM says "inside", the eye says "on top of", and the
|
|
125
|
+
gesture belongs to what the eye sees: a drag across a full-screen dialog opened
|
|
126
|
+
from a slide is not a drag on the slides, and nothing about it should reach
|
|
127
|
+
them.
|
|
128
|
+
|
|
129
|
+
So every walk up from the pointer stops there. The boxes above the surface get
|
|
130
|
+
no axis, and a scroller above it gets nothing either — it is painted behind the
|
|
131
|
+
surface, and behind is not under the finger. `showModal()` does not do this on
|
|
132
|
+
its own: the rest of the document is made inert, but the press still bubbles out
|
|
133
|
+
of the (not inert) dialog to a listener that sits above it.
|
|
134
|
+
|
|
118
135
|
### The browser also wants to answer the gesture
|
|
119
136
|
|
|
120
137
|
A gesture that is already answered — something is being dragged — must not be
|
|
@@ -123,14 +140,17 @@ page moved a little, and it looked wrong":
|
|
|
123
140
|
|
|
124
141
|
- **the leftovers of a scroll**, handed up the chain until something moves: a
|
|
125
142
|
list inside the box reaches its end and the page scrolls behind the travel.
|
|
126
|
-
`overscroll-behavior-<axis>: contain !important
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
axis only — the other one is the content's own scrolling,
|
|
133
|
-
not stop scrolling anyway, it stops spilling
|
|
143
|
+
`overscroll-behavior-<axis>: contain !important`, **written once and for all,
|
|
144
|
+
never while the finger is down**: a browser decides what a gesture may do when
|
|
145
|
+
the gesture BEGINS (at the touchstart, at the first wheel event), so a
|
|
146
|
+
property written after that decision arrives too late for the gesture it was
|
|
147
|
+
meant for. That is what "usually it does not move, sometimes it does" is made
|
|
148
|
+
of — and it is why none of this can be done in JS at the moment it is needed.
|
|
149
|
+
On the travelling axis only — the other one is the content's own scrolling,
|
|
150
|
+
and containing does not stop scrolling anyway, it stops spilling. WHERE that
|
|
151
|
+
property is written is a question of its own, and the engines do not answer it
|
|
152
|
+
the same way: see [What is contained, and what still
|
|
153
|
+
leaks](#what-is-contained-and-what-still-leaks);
|
|
134
154
|
- **the elastic bounce** at the end of a page, and the swipe that goes back in
|
|
135
155
|
history with it: `overscroll-behavior: none` on the document while a finger is
|
|
136
156
|
down. Same lateness applies, so this is a last resort behind the rule above
|
|
@@ -144,6 +164,75 @@ Both are written by the gesture itself (`data-drag-travel-gesture` and
|
|
|
144
164
|
time goes on bouncing. `preventDefault()` on each move says the same thing to
|
|
145
165
|
the browser for what those two properties do not cover.
|
|
146
166
|
|
|
167
|
+
### What is contained, and what still leaks
|
|
168
|
+
|
|
169
|
+
Containing is only ever read on a **scroll container** — an element that clips,
|
|
170
|
+
in the browser's sense, whether or not it has anything to scroll. Three places
|
|
171
|
+
could carry it: the travelling box, everything inside it, or the scrollers
|
|
172
|
+
themselves. Which of the three works is an engine question, and the answer
|
|
173
|
+
splits in two:
|
|
174
|
+
|
|
175
|
+
- **Blink** asks every scroll container between the pointer and the page whether
|
|
176
|
+
the gesture may go past it, _even one with nothing to scroll_. Containing the
|
|
177
|
+
travelling box is therefore the whole answer — and saying it to everything
|
|
178
|
+
inside is actively harmful: anything that happens to clip (a line with an
|
|
179
|
+
ellipsis, a rounded card, the invisible checkbox that covers a selectable row)
|
|
180
|
+
becomes a **dead zone under the wheel**, a container that stops the gesture
|
|
181
|
+
and has nothing to move with it. This is the bug that made a list refuse the
|
|
182
|
+
wheel inside a popup and only answer its scrollbar.
|
|
183
|
+
- **Gecko and WebKit** ask only the containers that actually scroll. The
|
|
184
|
+
travelling box is walked past — it travels, it does not scroll — so the
|
|
185
|
+
scroller itself has to be told, and saying it to everything is harmless there.
|
|
186
|
+
|
|
187
|
+
So `drag_to_travel.js` writes it on the box for everyone, and on everything
|
|
188
|
+
inside only outside Blink (`@supports not (-webkit-app-region: none)`, one of
|
|
189
|
+
the few properties that names an engine rather than a user agent string).
|
|
190
|
+
|
|
191
|
+
It also names the scrollers a browser makes on its own — `textarea`,
|
|
192
|
+
`select[multiple]`, `select[size]` — wherever they are inside the box. Nobody
|
|
193
|
+
declared those, so nothing can find them by looking; being native is exactly
|
|
194
|
+
what makes them nameable. `input` is deliberately not among them: it has
|
|
195
|
+
nothing to scroll on the axis anything travels on, and containing it is how a
|
|
196
|
+
row-wide invisible checkbox becomes a hole under the wheel. The cost of naming
|
|
197
|
+
them is that an empty `textarea` is contained too — a browser cannot be asked
|
|
198
|
+
"only if it scrolls" — so on Blink a wheel over one moves nothing rather than
|
|
199
|
+
the list around it.
|
|
200
|
+
|
|
201
|
+
On Blink this leaves the boxes that **do not clip**, since those are never
|
|
202
|
+
asked. Of navi's own: `SlideContainer` clips (`overflow: hidden`) and a
|
|
203
|
+
`SidePanel` is a `Dialog`, which scrolls (`overflow: auto`) — both are asked. A
|
|
204
|
+
`RouteTravel` box does not clip and must not be made to: a scroll container
|
|
205
|
+
there would become the nearest one for every `position: sticky` inside the pages
|
|
206
|
+
it holds. A row marked swipeable by `interactions` does not clip either.
|
|
207
|
+
|
|
208
|
+
Which is why navi contains what it KNOWS scrolls, in `box.jsx`:
|
|
209
|
+
|
|
210
|
+
```css
|
|
211
|
+
[data-drag-travel*="y"] [data-scrollable] {
|
|
212
|
+
overscroll-behavior-y: contain !important;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`[data-scrollable]` is worn by a `Box` that ASKED for `overflow: auto|scroll`,
|
|
217
|
+
never by one that merely clips — so this cannot make a dead zone the way `*`
|
|
218
|
+
does, and it puts the containment exactly where every engine reads it. A list, a
|
|
219
|
+
dialog body, a scrolling panel inside a travelling page are covered on Blink
|
|
220
|
+
again.
|
|
221
|
+
|
|
222
|
+
What still leaks, and only on Blink, and only under a box that does not clip: a
|
|
223
|
+
scroller **nobody declared and no tag names** — a bare
|
|
224
|
+
`<div style="overflow: auto">`, a widget from elsewhere. Its leftovers reach the
|
|
225
|
+
page, which is the old symptom in a much smaller corner. Two ways out, per case:
|
|
226
|
+
give the scroller a `Box` with an `overflow` (it is then declared), or contain it
|
|
227
|
+
by hand. The general fix belongs to Blink.
|
|
228
|
+
|
|
229
|
+
A note for whoever tests this: **Firefox cannot be measured with a synthetic
|
|
230
|
+
wheel**. Playwright dispatches one outside APZ, where Gecko enforces
|
|
231
|
+
`overscroll-behavior`, so containment never shows up there — inline, from a
|
|
232
|
+
stylesheet, headless or headed alike. The non-Blink branch is therefore left
|
|
233
|
+
exactly as it always was rather than tuned against a measurement that does not
|
|
234
|
+
exist.
|
|
235
|
+
|
|
147
236
|
### A hand reaching for something still moving is reaching for THAT thing
|
|
148
237
|
|
|
149
238
|
A gesture arriving while a travel is playing takes **that travel** over — it does
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.51",
|
|
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.16",
|
|
33
33
|
"@jsenv/humanize": "1.7.8",
|
|
34
34
|
"@jsenv/validity": "0.4.2"
|
|
35
35
|
},
|