@jsenv/navi 0.29.300 → 0.29.302
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 +892 -226
- package/dist/dev/jsenv_navi.js.map +25 -20
- package/dist/jsenv_navi.js +826 -207
- package/dist/jsenv_navi.js.map +25 -20
- package/docs/dialog_shape.md +23 -8
- package/docs/interactions.md +100 -16
- package/docs/pan_zoom.md +2 -2
- package/docs/popup_backdrop.md +28 -7
- package/docs/popup_open.md +43 -7
- package/package.json +1 -1
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,32 @@ 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"` |
|
|
103
|
+
|
|
104
|
+
Two holds on one press — a `longpress` declared on something inside an
|
|
105
|
+
element that declares one too — are answered by the nearer one, the way a
|
|
106
|
+
click is the innermost target's: the inner hold fires, the outer wait is given
|
|
107
|
+
up. Delays being equal; an outer hold made shorter than the inner one fires
|
|
108
|
+
first.
|
|
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.
|
|
100
118
|
|
|
101
119
|
A name nothing knows how to detect produces a dev warning naming the detectors
|
|
102
120
|
that exist. The carrying family and the two surface streams each have a file of
|
|
@@ -190,6 +208,67 @@ comes back once it settles — a failure leaves the row in place so it can be tr
|
|
|
190
208
|
again. What a success does to the element is yours (a list that redemands its
|
|
191
209
|
rows, a row that leaves): navi does not make it disappear.
|
|
192
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
|
+
|
|
193
272
|
## Carrying something, or a surface under the hand
|
|
194
273
|
|
|
195
274
|
Two families of interaction have a file of their own, because each is a subject:
|
|
@@ -408,6 +487,10 @@ tuned in one place and a stylesheet can read the same value.
|
|
|
408
487
|
| `data-longpress-delay` | `450` | ms the press must be held |
|
|
409
488
|
| `data-longpress-slop` | `8` | px the pointer may drift during the wait |
|
|
410
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
|
+
|
|
411
494
|
A threshold is a **fraction and never a distance**: the same gesture must mean
|
|
412
495
|
the same thing on a phone and on a wide screen. Speed answers on its own on top
|
|
413
496
|
of it — a brief flick counts whatever the distance covered.
|
|
@@ -516,17 +599,18 @@ container above it does not take the gesture:
|
|
|
516
599
|
finger, which is the native gesture. To place it at the press point rather than
|
|
517
600
|
on the element:
|
|
518
601
|
`triggerNaviCommand(target, "--navi-open", interactionEvent, { anchor })`.
|
|
519
|
-
- **A swipe has no keyboard equivalent
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
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.
|
|
523
607
|
|
|
524
608
|
## Reference
|
|
525
609
|
|
|
526
610
|
- `src/control/interaction/interaction_registry.js` — the prop, the four values,
|
|
527
611
|
the registry.
|
|
528
|
-
- `src/control/interaction/interaction_press.js` — swipes
|
|
529
|
-
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.
|
|
530
614
|
- `src/control/interaction/interaction_keyboard.js`,
|
|
531
615
|
`interaction_native.js` — the other two detectors.
|
|
532
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
|
@@ -409,8 +409,9 @@ The picker's `action` runs on the trigger, not in the popup: it is dispatched
|
|
|
409
409
|
on the close that keeps, and the popup goes while it runs. So what is still on
|
|
410
410
|
screen answers for the write — `aria-busy` and the loading outline on the
|
|
411
411
|
trigger while the request is out, the error callout on it if the server
|
|
412
|
-
refuses, and the value rolled back to the last accepted one
|
|
413
|
-
|
|
412
|
+
refuses, and the value rolled back to the last accepted one — on by default
|
|
413
|
+
for a picker, and what one wants: nobody is left mid-edit behind a closed
|
|
414
|
+
popup, and the card goes on saying what the server knows. A
|
|
414
415
|
card that IS the trigger (`variant="bare"`, the card as `ui`) therefore waits
|
|
415
416
|
as a card and is refused as a card, wherever in the tree the sheet was written.
|
|
416
417
|
|
|
@@ -422,9 +423,6 @@ as a card and is refused as a card, wherever in the tree the sheet was written.
|
|
|
422
423
|
ui={<MatchCard match={match} />}
|
|
423
424
|
value={match}
|
|
424
425
|
action={(next) => MATCH.PATCH({ id: match.id, ...next })}
|
|
425
|
-
// the draft is what the person typed, and the sheet is the only place it
|
|
426
|
-
// can be found again: keep it when the server says no
|
|
427
|
-
resetOnError={false}
|
|
428
426
|
>
|
|
429
427
|
<ControlGroup>
|
|
430
428
|
<MatchCard match={match} editable />
|
|
@@ -434,6 +432,16 @@ as a card and is refused as a card, wherever in the tree the sheet was written.
|
|
|
434
432
|
</Picker>
|
|
435
433
|
```
|
|
436
434
|
|
|
435
|
+
The card shows the answer the moment the sheet leaves when its `ui` is drawn
|
|
436
|
+
from what the picker holds rather than from what the caller knows — the same
|
|
437
|
+
state navi's own default `ui` reads, rolled back by `resetOnError`, so nothing
|
|
438
|
+
about the pending value is the caller's to keep or to put back. Two ways to
|
|
439
|
+
read it: `ui={MatchCard}` hands the component `value`, `loading` and
|
|
440
|
+
`interactive` as props; `ui={<MatchCard match={match} />}` keeps the caller's
|
|
441
|
+
own props and reads the same three with `usePickerState()` inside. The
|
|
442
|
+
`loading` is the wait: navi's loading outline is a two-pixel run around the
|
|
443
|
+
box, which a card-sized trigger draws its own waiting state for.
|
|
444
|
+
|
|
437
445
|
What the picker measures as "changed since open" is what its mirrored group
|
|
438
446
|
holds. A piece of the answer living outside the controls — a seating
|
|
439
447
|
rearranged by drag, kept in component state — has to be held by a control in
|
|
@@ -442,13 +450,41 @@ as nothing changed and nothing runs.
|
|
|
442
450
|
|
|
443
451
|
Two props finish the construct. `openOn="longpress"` (or
|
|
444
452
|
`["longpress", "contextmenu"]`) makes the hold what opens it, so a tap on the
|
|
445
|
-
card stays a tap — what a card in a list needs
|
|
446
|
-
|
|
453
|
+
card stays a tap — what a card in a list needs: the drawing gets its pointer
|
|
454
|
+
back, a link in it navigates, a button in it presses, a picker in it opens on
|
|
455
|
+
its own click, with nothing to declare. Two pickers on one card is the score
|
|
456
|
+
sheet inside the edit card (`12_picker_card_demo.html`): the inner one names
|
|
457
|
+
the card as its `anchor` so it lifts the whole card, and says `standalone` so
|
|
458
|
+
its value stays its own. One card, one wait: each picker wears the other's
|
|
459
|
+
run through `loading` (from its `onActionStart`/`onActionEnd`), so while
|
|
460
|
+
either write is out the hold and the click are both refused, with the busy
|
|
461
|
+
reason where the finger is — the click only once the score picker says
|
|
462
|
+
`openWhileReadOnly={false}`: a busy picker otherwise still opens, to be read,
|
|
463
|
+
and a sheet of fields to fill in is not that. A hold declared inside the card answers before the
|
|
464
|
+
card's own: the nearer hold takes the press. `animation="growing"` with
|
|
465
|
+
`dialogSizeFromAnchor` (as wide as the card, floor and ceiling),
|
|
447
466
|
`popupBackgroundColor="transparent"` and `popupBoxShadow="none"` lifts the
|
|
448
467
|
card out of its place and puts it back (`data-grow` on the card inside the
|
|
449
468
|
sheet; see Dialog's own `animation`). `12_picker_card_demo.html` shows all of
|
|
450
469
|
it, against a backend that answers when told to.
|
|
451
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
|
+
|
|
452
488
|
This is not `optimistic`. `optimistic` on a control is "draw no wait at all";
|
|
453
489
|
here the wait is drawn, on the trigger, and the popup was never what held it.
|
|
454
490
|
An optimistic picker is for a write not worth showing — the card then reads
|