@jsenv/navi 0.29.301 → 0.29.303
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/dev/jsenv_navi.js +710 -194
- package/dist/dev/jsenv_navi.js.map +21 -16
- package/dist/jsenv_navi.js +687 -191
- package/dist/jsenv_navi.js.map +21 -16
- package/docs/dialog_shape.md +23 -8
- package/docs/interactions.md +94 -16
- package/docs/pan_zoom.md +2 -2
- package/docs/popup_backdrop.md +28 -7
- package/docs/popup_open.md +17 -0
- package/package.json +2 -2
package/docs/dialog_shape.md
CHANGED
|
@@ -77,6 +77,14 @@ adjusted without giving up the rest:
|
|
|
77
77
|
| `expandX` | `true` | container-wide, same reason |
|
|
78
78
|
| `scrollCapture` | `true` | a drag past the sheet's edge must not reach the page |
|
|
79
79
|
|
|
80
|
+
A `layer="top"` dialog flush with an edge of the screen — a docked sheet on the
|
|
81
|
+
bottom, anything with `marginWithContainer={0}` — keeps the band the device
|
|
82
|
+
reserves there (`env(safe-area-inset-*)`: the notch, the home indicator,
|
|
83
|
+
Safari's floating bar). The surface still reaches the edge; what it holds stops
|
|
84
|
+
at the band. Only the device's own inset: the dialog is in front of the app's
|
|
85
|
+
fixed bars, so `--navi-safe-area-inset-*`, which counts them too (see
|
|
86
|
+
[`safe_area.md`](./safe_area.md)), is not what it reads.
|
|
87
|
+
|
|
80
88
|
Plus a swipe-down-to-close, held by the sheet's `header` (a `Box` with the
|
|
81
89
|
`header` prop) and by anything carrying `data-swipe-grip` — never by the whole
|
|
82
90
|
sheet, so a board something is dragged across keeps its own gestures. See
|
|
@@ -181,10 +189,17 @@ Two things make it safe to reach for:
|
|
|
181
189
|
|
|
182
190
|
## A `layer="local"` dialog answers to its container
|
|
183
191
|
|
|
184
|
-
`layer="top"` (the default) is a real `<dialog>` in the browser's top layer
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
`layer="top"` (the default) is a real `<dialog>` in the browser's top layer,
|
|
193
|
+
placed against the screen and clipped by nothing — modal, with everything
|
|
194
|
+
behind made inert, unless `backdrop={false}` says otherwise (see
|
|
195
|
+
[`popup_backdrop.md`](./popup_backdrop.md)). `layer="local"` stays in normal
|
|
196
|
+
document flow, confined to and clipped by its own positioned ancestor.
|
|
197
|
+
|
|
198
|
+
For a **docked** dialog that is the choice of which edge the sheet rests on:
|
|
199
|
+
`layer` names the container, and docking makes the sheet flush and full width
|
|
200
|
+
against it. A sheet meant for the bottom of the screen is `layer="top"`, even
|
|
201
|
+
when the page behind it must stay live — that is `backdrop={false}`, not
|
|
202
|
+
`layer="local"`.
|
|
188
203
|
|
|
189
204
|
For the shape, that swaps what every bound is measured against: the container
|
|
190
205
|
becomes that ancestor's box, read through
|
|
@@ -194,10 +209,10 @@ is open — its backdrop covers the scrollport, not the scrolled content, so
|
|
|
194
209
|
scrolling would slide the dialog away and reveal what the backdrop does not
|
|
195
210
|
cover. `scrollCapture` is what extends that lock to the whole page.
|
|
196
211
|
|
|
197
|
-
One accepted limitation, not an oversight: a
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`showModal()` element joins.
|
|
212
|
+
One accepted limitation, not an oversight: a dialog that is not modal —
|
|
213
|
+
`layer="local"`, or `backdrop={false}` — **cannot** be dismissed by the
|
|
214
|
+
hardware/gesture back button. No web API hooks into that outside the browser's
|
|
215
|
+
own modal-dismissal stack, which only a genuine `showModal()` element joins.
|
|
201
216
|
|
|
202
217
|
## Reaching all of this through a `Picker` or a `SplitButton`
|
|
203
218
|
|
package/docs/interactions.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
- [Which interaction asked](#which-interaction-asked)
|
|
8
8
|
- [Reaching the control](#reaching-the-control)
|
|
9
9
|
- [What a swipe draws, and what it leaves to you](#what-a-swipe-draws-and-what-it-leaves-to-you)
|
|
10
|
+
- [Twice, whichever hand it is](#twice-whichever-hand-it-is)
|
|
11
|
+
- [The click on the way there](#the-click-on-the-way-there)
|
|
10
12
|
- [Carrying something, or a surface under the hand](#carrying-something-or-a-surface-under-the-hand)
|
|
11
13
|
- [An affordance inside somebody else's box: `selfInteractions`](#an-affordance-inside-somebody-elses-box-selfinteractions)
|
|
12
14
|
- [Why it is a list, and why it is required](#why-it-is-a-list-and-why-it-is-required)
|
|
@@ -87,16 +89,17 @@ which the carrying interactions answer with).
|
|
|
87
89
|
|
|
88
90
|
### The interactions navi detects
|
|
89
91
|
|
|
90
|
-
| Key
|
|
91
|
-
|
|
|
92
|
-
| `mousedown` `mouseup` `click` `
|
|
93
|
-
| `swipe_left` `swipe_right` `swipe_up` `swipe_down`
|
|
94
|
-
| `longpress`
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
92
|
+
| Key | Read from |
|
|
93
|
+
| -------------------------------------------------- | ----------------------------------------------------------- |
|
|
94
|
+
| `mousedown` `mouseup` `click` `contextmenu` | the browser's own events |
|
|
95
|
+
| `swipe_left` `swipe_right` `swipe_up` `swipe_down` | a press that travels |
|
|
96
|
+
| `longpress` | a press held still |
|
|
97
|
+
| `double_click` `single_click` | two presses in a row, or one that stayed alone |
|
|
98
|
+
| `move` `reorder` `land` `toss` `leave` | the element carried, and what letting go means |
|
|
99
|
+
| `moving` | the same carry, told on every frame |
|
|
100
|
+
| `grab` `release` `refuse` | the instants a drag takes hold, lets go, or does not happen |
|
|
101
|
+
| `pan` `zoom` | a surface under the hand, or under a wheel |
|
|
102
|
+
| `"keyboard:<shortcut>"` | keys, e.g. `"keyboard:ctrl+backspace"` |
|
|
100
103
|
|
|
101
104
|
Two holds on one press — a `longpress` declared on something inside an
|
|
102
105
|
element that declares one too — are answered by the nearer one, the way a
|
|
@@ -104,6 +107,15 @@ click is the innermost target's: the inner hold fires, the outer wait is given
|
|
|
104
107
|
up. Delays being equal; an outer hold made shorter than the inner one fires
|
|
105
108
|
first.
|
|
106
109
|
|
|
110
|
+
There is no `dblclick` in that table, on purpose: the browser fires it for a
|
|
111
|
+
mouse and never for a finger, so an element declaring it would answer half the
|
|
112
|
+
hands that reach it. `double_click` is the same gesture counted from the
|
|
113
|
+
pointer — see [Twice, whichever hand it is](#twice-whichever-hand-it-is). navi says so in dev, once per element, the first time it happens: the
|
|
114
|
+
outer hold is a declaration that can never fire, and a picker opened by
|
|
115
|
+
`openOn="longpress"` around a card that already holds one is exactly where it
|
|
116
|
+
happens. One hold has one meaning — two things on one card want two gestures
|
|
117
|
+
(a hold and a click), not two holds.
|
|
118
|
+
|
|
107
119
|
A name nothing knows how to detect produces a dev warning naming the detectors
|
|
108
120
|
that exist. The carrying family and the two surface streams each have a file of
|
|
109
121
|
their own — [drag_interactions.md](./drag_interactions.md) and
|
|
@@ -196,6 +208,67 @@ comes back once it settles — a failure leaves the row in place so it can be tr
|
|
|
196
208
|
again. What a success does to the element is yours (a list that redemands its
|
|
197
209
|
rows, a row that leaves): navi does not make it disappear.
|
|
198
210
|
|
|
211
|
+
## Twice, whichever hand it is
|
|
212
|
+
|
|
213
|
+
At the finger there is no `dblclick`. There is not even a second `click`: two
|
|
214
|
+
taps in the same place are a gesture the browser keeps for itself (its own
|
|
215
|
+
zoom), so it withholds them — measured on a phone, two taps 120 ms apart give
|
|
216
|
+
two `pointerup`, ONE `click` and no `dblclick` at all. Anything that wants to
|
|
217
|
+
mean "twice" therefore has to count presses, which is what `double_click` is:
|
|
218
|
+
|
|
219
|
+
```jsx
|
|
220
|
+
<Box interactions={{ pan, zoom, double_click: (event) => open(event) }} />
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
One name for the mouse and for the finger — a tap is a click, and the same
|
|
224
|
+
gesture opens the plan at a desk and on a phone.
|
|
225
|
+
|
|
226
|
+
The rhythm is one window, opened by the FIRST press: the second has to land
|
|
227
|
+
inside it, and near enough to it.
|
|
228
|
+
|
|
229
|
+
| Attribute | Default | Meaning |
|
|
230
|
+
| ------------------------- | ------- | ------------------------------------------- |
|
|
231
|
+
| `data-double-click-delay` | `400` | ms the window stays open |
|
|
232
|
+
| `data-double-click-slop` | `30` | px the second press may land from the first |
|
|
233
|
+
|
|
234
|
+
The two numbers are read against the rest of the family rather than picked. 400
|
|
235
|
+
sits under the hold's 450: a pause longer than the wait navi calls "held" is
|
|
236
|
+
longer than one gesture — which also means a press slow enough to be a hold
|
|
237
|
+
cannot start a double click, and a hold answered on the second press takes that
|
|
238
|
+
press back. 30 px is a fingertip and not a pixel, because between the two the
|
|
239
|
+
finger leaves the glass and lands again where it means to; the 8 px of
|
|
240
|
+
`data-longpress-slop` answer a different question (has this finger stood
|
|
241
|
+
still?).
|
|
242
|
+
|
|
243
|
+
### The click on the way there
|
|
244
|
+
|
|
245
|
+
By default both happen, the way they do in a browser: the first press is a
|
|
246
|
+
`click`, and the second is answered by the `double_click` alone — the click it
|
|
247
|
+
would have left behind is swallowed, the way every gesture swallows the one it
|
|
248
|
+
leaves.
|
|
249
|
+
|
|
250
|
+
`single_click` is for the case where even the first one is too much: two
|
|
251
|
+
answers that exclude each other — a plan that opens on the double must not do
|
|
252
|
+
whatever a lone tap does on the way there. It is the same window read the other
|
|
253
|
+
way, the tap that STAYED alone, said once the window has closed on it:
|
|
254
|
+
|
|
255
|
+
```jsx
|
|
256
|
+
<Box
|
|
257
|
+
interactions={{
|
|
258
|
+
single_click: (event) => select(event),
|
|
259
|
+
double_click: (event) => open(event),
|
|
260
|
+
}}
|
|
261
|
+
/>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
It costs that wait, which is why it is a name a caller picks rather than
|
|
265
|
+
something a declared `double_click` imposes on `click`. And it hands the
|
|
266
|
+
element's click over: the click each tap leaves behind is swallowed, so a
|
|
267
|
+
control that wants its action on a lone click asks for it there —
|
|
268
|
+
`single_click: "request_action"`, not `action` reached by a click that no longer
|
|
269
|
+
arrives. A keyboard activation is not held: nothing can double it, so it is said
|
|
270
|
+
at once.
|
|
271
|
+
|
|
199
272
|
## Carrying something, or a surface under the hand
|
|
200
273
|
|
|
201
274
|
Two families of interaction have a file of their own, because each is a subject:
|
|
@@ -414,6 +487,10 @@ tuned in one place and a stylesheet can read the same value.
|
|
|
414
487
|
| `data-longpress-delay` | `450` | ms the press must be held |
|
|
415
488
|
| `data-longpress-slop` | `8` | px the pointer may drift during the wait |
|
|
416
489
|
|
|
490
|
+
Plus the two the double click is counted with, `data-double-click-delay` and
|
|
491
|
+
`data-double-click-slop` (see [Twice, whichever hand it
|
|
492
|
+
is](#twice-whichever-hand-it-is)).
|
|
493
|
+
|
|
417
494
|
A threshold is a **fraction and never a distance**: the same gesture must mean
|
|
418
495
|
the same thing on a phone and on a wide screen. Speed answers on its own on top
|
|
419
496
|
of it — a brief flick counts whatever the distance covered.
|
|
@@ -522,17 +599,18 @@ container above it does not take the gesture:
|
|
|
522
599
|
finger, which is the native gesture. To place it at the press point rather than
|
|
523
600
|
on the element:
|
|
524
601
|
`triggerNaviCommand(target, "--navi-open", interactionEvent, { anchor })`.
|
|
525
|
-
- **A swipe has no keyboard equivalent
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
control's own
|
|
602
|
+
- **A swipe has no keyboard equivalent, and neither has a double click.** There
|
|
603
|
+
is nothing to press that means "swipe right", and no key that means "twice",
|
|
604
|
+
so either is only reachable if something else on the element offers the same
|
|
605
|
+
thing — a `"keyboard:<shortcut>"`, a `contextmenu`, or the control's own
|
|
606
|
+
action.
|
|
529
607
|
|
|
530
608
|
## Reference
|
|
531
609
|
|
|
532
610
|
- `src/control/interaction/interaction_registry.js` — the prop, the four values,
|
|
533
611
|
the registry.
|
|
534
|
-
- `src/control/interaction/interaction_press.js` — swipes
|
|
535
|
-
swipe writes on the element.
|
|
612
|
+
- `src/control/interaction/interaction_press.js` — swipes, holds and the two
|
|
613
|
+
taps, and what a swipe writes on the element.
|
|
536
614
|
- `src/control/interaction/interaction_keyboard.js`,
|
|
537
615
|
`interaction_native.js` — the other two detectors.
|
|
538
616
|
- `src/control/demos/38_interactions_demo.html` — every case above, plus a
|
package/docs/pan_zoom.md
CHANGED
|
@@ -43,8 +43,8 @@ when it lands; the pan stepping back for what is carried across the surface (a
|
|
|
43
43
|
for); the pinch not beginning as a pan under its first finger; the wheel and the
|
|
44
44
|
pinch writing one `zoom`; the capture, the pointer the browser drops, the click
|
|
45
45
|
the release leaves behind. A pointer pans only once it has travelled a few px
|
|
46
|
-
(`data-drag-threshold`), so a tap stays a tap
|
|
47
|
-
`pan` still gets its hold.
|
|
46
|
+
(`data-drag-threshold`), so a tap stays a tap: a `longpress` declared beside
|
|
47
|
+
`pan` still gets its hold, and a `double_click` still gets its two presses.
|
|
48
48
|
|
|
49
49
|
Declared alone, `zoom` takes two fingers and the wheel and leaves one pointer to
|
|
50
50
|
whatever else reads it; `pan` alone leaves the wheel to the page.
|
package/docs/popup_backdrop.md
CHANGED
|
@@ -61,13 +61,34 @@ questions. `"invisible"` is for a popup that must absorb — a menu whose
|
|
|
61
61
|
dismissing click must not also press what is under it — without dimming the
|
|
62
62
|
page for it. `backdrop={false}` is for a popup that must not absorb at all.
|
|
63
63
|
|
|
64
|
-
**
|
|
65
|
-
`Dialog`
|
|
66
|
-
`showModal()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
**It is what decides whether a popup is modal.** `Popover` is never modal,
|
|
65
|
+
either layer. A `Dialog` is modal exactly when it has a wall: with one, a
|
|
66
|
+
top-layer dialog is `showModal()`'d and the browser makes everything behind
|
|
67
|
+
genuinely inert; without one it goes to the same top layer through the Popover
|
|
68
|
+
API (`popover="manual"`), placed against the screen just the same, over a page
|
|
69
|
+
that stays live. `Popup` forwards `backdrop` in both modes, so which of popover
|
|
70
|
+
or dialog the small-screen resolution picks says nothing about whether one
|
|
71
|
+
press or two are needed.
|
|
72
|
+
|
|
73
|
+
That is what a bottom sheet on a phone is made of. `dockedOnSmallTouchScreen`
|
|
74
|
+
docks the dialog against its container, and `layer` is what that container is —
|
|
75
|
+
`"top"` for the screen, `"local"` for the box the popup was declared in. A
|
|
76
|
+
sheet flush with the bottom of the screen over a map still being read object by
|
|
77
|
+
object is `layer="top"` for the shape and `backdrop={false}` for the map:
|
|
78
|
+
|
|
79
|
+
```jsx
|
|
80
|
+
<Popup dockedOnSmallTouchScreen layer="top" backdrop={false}>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Going `layer="local"` to keep the map live instead confines the sheet to the
|
|
84
|
+
box it was declared in — full width and flush against _that_, which on a
|
|
85
|
+
partial container reads as a sheet that failed rather than as a sheet.
|
|
86
|
+
|
|
87
|
+
What a wall-less dialog gives up is the one thing only a modal gets natively:
|
|
88
|
+
the hardware/gesture back button no longer dismisses it (same accepted
|
|
89
|
+
limitation as `layer="local"`, see [`dialog_shape.md`](./dialog_shape.md)).
|
|
90
|
+
Focus is not trapped either, deliberately — a page meant to be reachable is
|
|
91
|
+
meant to be reachable with the keyboard too.
|
|
71
92
|
|
|
72
93
|
`pointerInteractionOutsideEffect="capture"` and `backdrop={false}` contradict
|
|
73
94
|
each other — absorbing is what a wall does — and navi warns rather than
|
package/docs/popup_open.md
CHANGED
|
@@ -468,6 +468,23 @@ card out of its place and puts it back (`data-grow` on the card inside the
|
|
|
468
468
|
sheet; see Dialog's own `animation`). `12_picker_card_demo.html` shows all of
|
|
469
469
|
it, against a backend that answers when told to.
|
|
470
470
|
|
|
471
|
+
A gesture inside the sheet that is not a field — cancel the game, delete it —
|
|
472
|
+
is still an answer the sheet gives. A named button says which:
|
|
473
|
+
|
|
474
|
+
```jsx
|
|
475
|
+
<Button name="op" value="cancel" command="--navi-send">
|
|
476
|
+
Cancel the game
|
|
477
|
+
</Button>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Its name and value travel with the fields (the group takes the sender's
|
|
481
|
+
value, see `wantRequesterButtonState`), the sheet leaves on the send, and the
|
|
482
|
+
picker's `action` receives `{ …fields, op: "cancel" }` — so this write is
|
|
483
|
+
worn by the trigger exactly like the others, callout included, and a drawing
|
|
484
|
+
reading `usePickerState()` can already show the "cancelled" stamp while the
|
|
485
|
+
request is out. No button inside the popup needs an action of its own: a run
|
|
486
|
+
started in there would belong to a control the close takes away.
|
|
487
|
+
|
|
471
488
|
This is not `optimistic`. `optimistic` on a control is "draw no wait at all";
|
|
472
489
|
here the wait is drawn, on the trigger, and the popup was never what held it.
|
|
473
490
|
An optimistic picker is for a write not worth showing — the card then reads
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.303",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Library of components including navigation to create frontend applications",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"preact": "^11.0.0-rc.2"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@jsenv/dom": "0.17.
|
|
37
|
+
"@jsenv/dom": "0.17.62",
|
|
38
38
|
"@jsenv/humanize": "1.8.2",
|
|
39
39
|
"@jsenv/validity": "0.4.3"
|
|
40
40
|
},
|