@jsenv/navi 0.29.418 → 0.29.420
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/README.md +1 -1
- package/dist/dev/jsenv_navi.js +584 -196
- package/dist/dev/jsenv_navi.js.map +35 -35
- package/dist/jsenv_navi.js +563 -187
- package/dist/jsenv_navi.js.map +35 -35
- package/docs/AI_INSTRUCTIONS.md +177 -261
- package/docs/actions.md +164 -251
- package/docs/autofocus.md +66 -83
- package/docs/control_object.md +117 -214
- package/docs/control_value.md +121 -185
- package/docs/create_and_edit.md +169 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +39 -43
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +115 -174
- package/docs/drag_to_travel.md +10 -9
- package/docs/dynamic_import.md +119 -136
- package/docs/error_handling.md +75 -127
- package/docs/field_validation.md +49 -89
- package/docs/form_changed.md +66 -81
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +6 -4
- package/docs/list_action.md +36 -51
- package/docs/list_refresh.md +73 -115
- package/docs/mobile_touch.md +56 -62
- package/docs/navigation.md +377 -723
- package/docs/network_policy.md +61 -75
- package/docs/pan_zoom.md +62 -103
- package/docs/popup_backdrop.md +128 -168
- package/docs/popup_lift.md +179 -268
- package/docs/popup_open.md +380 -651
- package/docs/resource.md +228 -408
- package/docs/route_transitions.md +333 -482
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -335
- package/docs/state_binding.md +2 -1
- package/docs/testid.md +18 -23
- package/docs/typography.md +111 -141
- package/docs/view_transitions.md +106 -104
- package/docs/z_index.md +61 -109
- package/package.json +2 -2
- package/docs/badge_list.md +0 -83
- package/docs/control_group.md +0 -168
- package/docs/mobile_layout_pitfalls.md +0 -145
package/docs/network_policy.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
One declaration says whether a request may leave, and the action layer reads it
|
|
4
4
|
everywhere it matters. Two things follow from it:
|
|
5
5
|
|
|
6
|
-
- **writes are held.** A control
|
|
7
|
-
action — or sitting in a form
|
|
8
|
-
|
|
6
|
+
- **writes are held.** A control holding a `POST`, `PUT`, `PATCH` or `DELETE`
|
|
7
|
+
action — or sitting in a form holding one — is read-only and says why, before
|
|
8
|
+
the press.
|
|
9
9
|
- **reads are answered from the store.** A resource `GET` completes with the row
|
|
10
10
|
it already holds and asks nothing.
|
|
11
11
|
|
|
@@ -17,36 +17,24 @@ are the entire point — wants only the first, and says `reads: "network"`.
|
|
|
17
17
|
## What navi keeps without being asked
|
|
18
18
|
|
|
19
19
|
What an app wants when the network is gone is one sentence: **answer from what
|
|
20
|
-
you already hold, ask nothing, and refuse writes politely.**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- **Running a completed action is a no-op.** Coming back to `/games/abc` from
|
|
32
|
-
the list sends nothing while the instance that read it is alive — `run()` on a
|
|
33
|
-
`COMPLETED` action already has its data. Only `rerun()` goes back to the
|
|
34
|
-
network (see [actions.md](./actions.md)). An instance nothing references any
|
|
35
|
-
more is asked again, with the store row drawn meanwhile; under the policy
|
|
36
|
-
that ask is answered from the store (below).
|
|
37
|
-
- **A failed rerun keeps the previous value.** Only `errorSignal` and the
|
|
38
|
-
running state move; what was on screen stays on screen.
|
|
39
|
-
|
|
40
|
-
So an app that caches responses in a `Map` beside navi is keeping a second copy
|
|
41
|
-
of what the store holds — and going stale on its own.
|
|
20
|
+
you already hold, ask nothing, and refuse writes politely.** navi does it once,
|
|
21
|
+
and an app only has to say _when_. Leaving a page erases nothing: the data of a
|
|
22
|
+
resource action is the store row, live, updated by every later response that
|
|
23
|
+
mentions it; a route action left behind is aborted, not reset; coming back to
|
|
24
|
+
`/games/abc` sends nothing while the instance that read it is alive, since
|
|
25
|
+
`run()` on a completed action is a no-op
|
|
26
|
+
([actions.md](./actions.md#running-run-rerun-prerun-reset)), and an instance
|
|
27
|
+
nothing references any more asks again with the store row drawn meanwhile; a
|
|
28
|
+
failed rerun keeps the previous value. So an app that caches responses in a
|
|
29
|
+
`Map` beside navi is keeping a second copy of what the store holds — and going
|
|
30
|
+
stale on its own.
|
|
42
31
|
|
|
43
32
|
## The one thing the app decides: the reason
|
|
44
33
|
|
|
45
|
-
Which moments count as "offline" is the app's
|
|
34
|
+
Which moments count as "offline" is the app's: a device without a network
|
|
46
35
|
interface, a mode chosen in the settings, an outage the user agreed to stop
|
|
47
|
-
fighting
|
|
48
|
-
|
|
49
|
-
reads it everywhere it matters:
|
|
36
|
+
fighting are not said the same way to the user. navi asks for a single value,
|
|
37
|
+
the **reason**, and reads it everywhere it matters:
|
|
50
38
|
|
|
51
39
|
```js
|
|
52
40
|
import { setNetworkPolicy } from "@jsenv/navi";
|
|
@@ -61,7 +49,7 @@ setNetworkPolicy(offlineReasonSignal, {
|
|
|
61
49
|
```
|
|
62
50
|
|
|
63
51
|
The source may be a signal (followed live), a function, or a plain value. Set
|
|
64
|
-
it once, at startup
|
|
52
|
+
it once, at startup.
|
|
65
53
|
|
|
66
54
|
## What the policy changes
|
|
67
55
|
|
|
@@ -71,28 +59,27 @@ was asked:
|
|
|
71
59
|
|
|
72
60
|
- **A `GET` answers from the store.** If the row it designates is there, the
|
|
73
61
|
action completes with it and nothing is asked. The row is the one its params
|
|
74
|
-
name — by the resource's `idKey`, or by any of its `uniqueKeys` — or, when
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
name — by the resource's `idKey`, or by any of its `uniqueKeys` — or, when the
|
|
63
|
+
params name none (a `GET` without params, like `/me`), the row the action last
|
|
64
|
+
completed with. Going from game A to game B, both read before, is a change of
|
|
65
|
+
params that would normally rerun `GAME.GET`; under the policy it completes
|
|
66
|
+
from the store, and a screen that does not take `error: true` keeps drawing
|
|
67
|
+
what it holds instead of falling into its error boundary. A route opening a
|
|
68
|
+
user by id **or** by slug gives navi both keys to look by:
|
|
69
|
+
`resource("user", { uniqueKeys: ["slug"], … })`.
|
|
82
70
|
- **A completed read stays completed.** `GET_MANY` (and every other read) has
|
|
83
71
|
nowhere to answer from: the store holds items, not queries, and only the
|
|
84
72
|
action's own value knows which ids answered `/users?scope=shareable`. So a
|
|
85
73
|
rerun asked of a completed read under the policy is held — the action keeps
|
|
86
74
|
its state, its value and its data, exactly as a `run()` on a completed action
|
|
87
|
-
would.
|
|
88
|
-
runs, its action has already been reset.
|
|
75
|
+
would.
|
|
89
76
|
- **A `GET_RANGE` revalidation fails quietly.** A list that comes back to the
|
|
90
77
|
screen draws the composition it left and asks again for the window it draws;
|
|
91
78
|
under the policy that ask fails, and a failed revalidation keeps the rows it
|
|
92
79
|
had. Only a window never loaded shows the failure.
|
|
93
80
|
- **Everything else settles with a `NetworkPolicyError`.** A read with nothing
|
|
94
|
-
in the store, a relationship `GET` (it has no row of its own to answer with),
|
|
95
|
-
write that got through anyway: the action ends `FAILED` with an error whose
|
|
81
|
+
in the store, a relationship `GET` (it has no row of its own to answer with),
|
|
82
|
+
a write that got through anyway: the action ends `FAILED` with an error whose
|
|
96
83
|
`reason` is the policy's value. `isNetworkPolicyError(error)` tells it apart
|
|
97
84
|
from a request that left and never came back — that one is the app's `fetch`
|
|
98
85
|
rejecting, and the app names it. The class says the request never left, and
|
|
@@ -101,30 +88,30 @@ was asked:
|
|
|
101
88
|
|
|
102
89
|
It is an error only in the way it travels. Nothing treats it as a bug: navi
|
|
103
90
|
never reports it as unhandled, and when one reaches `window` anyway — a run
|
|
104
|
-
called by hand whose failure nobody catches, a render throw no boundary took
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
a control with `actionErrorEffect="throw"` — makes `preact/debug` log it with
|
|
111
|
-
`console.error`, as it logs every error a boundary catches, 404s and failed
|
|
112
|
-
fetches included. `preact/debug` reads no option or flag that would say the
|
|
113
|
-
error is handled, so that line stays. The jsenv overlay stays quiet, and a
|
|
91
|
+
called by hand whose failure nobody catches, a render throw no boundary took —
|
|
92
|
+
navi cancels the event, so neither the browser console nor the jsenv overlay
|
|
93
|
+
says anything. One line is left, in dev only: a boundary displaying it
|
|
94
|
+
(`<ErrorBoundary>`, a control with `actionErrorEffect="throw"`) makes
|
|
95
|
+
`preact/debug` log it with `console.error`, as it logs every error a boundary
|
|
96
|
+
catches, and `preact/debug` reads nothing that would say it is handled. A
|
|
114
97
|
build does not load `preact/debug`.
|
|
115
98
|
|
|
116
|
-
- **A write is refused before the press.** A control
|
|
117
|
-
|
|
118
|
-
is read-only while the policy holds, and
|
|
119
|
-
|
|
99
|
+
- **A write is refused before the press.** A control holding a write verb's
|
|
100
|
+
instance — `GAME.DELETE`, `GAME.DELETE.bindParams({ id })` — or any control
|
|
101
|
+
inside a `<Form>` holding one, is read-only while the policy holds, and
|
|
102
|
+
answers the press with `readOnlyMessage`.
|
|
120
103
|
|
|
121
104
|
Which actions the policy sees: those declaring a verb (`meta.verb`) — every
|
|
122
|
-
action a `resource()` makes. A plain `createAction` may not touch the network
|
|
123
|
-
|
|
124
|
-
it into
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
action a `resource()` makes. A plain `createAction` may not touch the network at
|
|
106
|
+
all, so it is left alone, and so is a function handed to a control's `action`:
|
|
107
|
+
it is wrapped into an action with no verb, even when it calls one, so the
|
|
108
|
+
control is not held and the write fails after the press with a
|
|
109
|
+
`NetworkPolicyError` ([resource.md](./resource.md#a-function-calling-the-verb-or-the-instance)).
|
|
110
|
+
`createAction(callback, { meta: { verb } })` opts an action into what the action
|
|
111
|
+
layer holds — a control bound to a write verb is read-only, a completed `"GET"`
|
|
112
|
+
is not rerun — but its callback is still called when it runs: answering from
|
|
113
|
+
the store and settling with a `NetworkPolicyError` are done by the callbacks a
|
|
114
|
+
`resource()` wraps.
|
|
128
115
|
|
|
129
116
|
## Holding writes while reads go out
|
|
130
117
|
|
|
@@ -149,10 +136,9 @@ own account.
|
|
|
149
136
|
What this buys over a guard in the app's own request layer is **when** the
|
|
150
137
|
refusal happens. A guard one layer below sees a request that was already built:
|
|
151
138
|
the press was accepted, the sheet opened, the form submitted, and only then does
|
|
152
|
-
it fail. navi knows which controls
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
buttons.
|
|
139
|
+
it fail. navi knows which controls hold a write before any of that, so the
|
|
140
|
+
button says why instead of being pressed, and the dialog it would have opened
|
|
141
|
+
never opens. A button that always fails teaches people not to press buttons.
|
|
156
142
|
|
|
157
143
|
## Two reasons at once
|
|
158
144
|
|
|
@@ -174,8 +160,8 @@ setNetworkPolicy(networkPolicyReasonSignal, {
|
|
|
174
160
|
|
|
175
161
|
`reads` is read from the reason for exactly this: one declaration, and the
|
|
176
162
|
reason decides which kind of policy is holding. The `??` above is the
|
|
177
|
-
precedence, and it is the app's to write
|
|
178
|
-
|
|
163
|
+
precedence, and it is the app's to write: when both hold the same write, navi
|
|
164
|
+
has nothing to decide which one says why.
|
|
179
165
|
|
|
180
166
|
## What stays the app's
|
|
181
167
|
|
|
@@ -195,14 +181,14 @@ one says why is something navi has nothing to decide it with.
|
|
|
195
181
|
- **What a screen says on a `NetworkPolicyError`** — "no network" is a fact
|
|
196
182
|
about the device, "offline mode" a decision, "we are only looking" neither;
|
|
197
183
|
`error.reason` is there so the screen says the right one.
|
|
198
|
-
- **Which answer is worth keeping across a reload
|
|
184
|
+
- **Which answer is worth keeping across a reload** — which resource, into
|
|
185
|
+
which signal, for which session: a resource declaring
|
|
199
186
|
[`persist`](./resource.md#persist-the-last-answer-drawn-again-after-a-reload)
|
|
200
|
-
has its
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
since?) is the app's business.
|
|
187
|
+
has its kept row answer the `GET` under the policy, so a reload offline
|
|
188
|
+
reopens on it.
|
|
189
|
+
- **A queue of writes to replay** once the network is back — not navi's: what a
|
|
190
|
+
replayed write means (a score entered twice? a seat taken since?) is the app's
|
|
191
|
+
business.
|
|
206
192
|
|
|
207
193
|
## See also
|
|
208
194
|
|
package/docs/pan_zoom.md
CHANGED
|
@@ -36,20 +36,19 @@ them panning and the distance between them zooming, and a finger lifting
|
|
|
36
36
|
re-anchors on what is left. What a pixel of pan means in your coordinates, and
|
|
37
37
|
whether the zoom is continuous or stepped, is yours: the numbers are the numbers.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
inside its own slop.
|
|
39
|
+
Everything else is settled before the press, and there is nothing to wire: the
|
|
40
|
+
pan steps back for what is carried across the surface (a `move`, a handle, a
|
|
41
|
+
field, a popover — the same list a travelling box steps back for), the pinch does
|
|
42
|
+
not begin as a pan under its first finger, the wheel and the pinch write one
|
|
43
|
+
`zoom`, the click the release leaves behind is swallowed, and the next tap's is
|
|
44
|
+
kept — Chrome on Android drops it after a touch drag whose `touchmove`s nobody
|
|
45
|
+
refused (see
|
|
46
|
+
[mobile_touch.md](./mobile_touch.md#a-tap-dropped-after-a-touch-drag)). A pointer
|
|
47
|
+
pans only once it has travelled a few px (`data-drag-threshold`), so a tap stays
|
|
48
|
+
a tap: a `longpress` declared beside `pan` still gets its hold, and a
|
|
49
|
+
`double_click` its two presses. A press that does pan is the surface's: a
|
|
50
|
+
`longpress` waiting on it gives up the moment the pan begins, even inside its own
|
|
51
|
+
slop.
|
|
53
52
|
|
|
54
53
|
Declared alone, `zoom` takes two fingers and the wheel and leaves one pointer to
|
|
55
54
|
whatever else reads it: nothing captures it, waits on it or swallows its click
|
|
@@ -62,9 +61,8 @@ the page.
|
|
|
62
61
|
|
|
63
62
|
A surface takes every touch that lands on it, which is right for a map filling
|
|
64
63
|
the screen — there is no page left behind it to scroll. A plan shown as a
|
|
65
|
-
thumbnail on a page is the other case: a finger there means to scroll
|
|
66
|
-
|
|
67
|
-
it.
|
|
64
|
+
thumbnail on a page is the other case: a finger there usually means to scroll,
|
|
65
|
+
and a surface answering every one makes the page unreadable past it.
|
|
68
66
|
|
|
69
67
|
```jsx
|
|
70
68
|
<Box data-pan-after-hold interactions={{ pan, zoom }} />
|
|
@@ -74,20 +72,16 @@ The finger then says it means THIS surface the way it says it means to carry a
|
|
|
74
72
|
drag source — by standing still. Until the hold the page keeps its scroll, and
|
|
75
73
|
the pan starts where the finger already is. The pinch is not given away with it:
|
|
76
74
|
two fingers on a surface that answers `zoom` are its own. A mouse travelling is
|
|
77
|
-
untouched — a button held down over a surface could never have meant a scroll
|
|
78
|
-
|
|
79
|
-
or any ancestor, the same place `data-drag-on-contact` is said.
|
|
75
|
+
untouched — a button held down over a surface could never have meant a scroll.
|
|
76
|
+
It is read off the element or any ancestor, like `data-drag-on-contact`.
|
|
80
77
|
|
|
81
|
-
Opt-in,
|
|
82
|
-
[`data-drag-on-contact`](./drag_interactions.md#a-finger-that-does-not-have-to-wait-data-drag-on-contact)
|
|
83
|
-
navi cannot see whether anything behind the surface scrolls. You know;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
On a surface that only zooms there is no pan to wait for: one finger is the
|
|
89
|
-
page's to scroll, two are the surface's, and `"kept"` (below) has nothing to
|
|
90
|
-
keep.
|
|
78
|
+
Opt-in, for the same reason its opposite
|
|
79
|
+
[`data-drag-on-contact`](./drag_interactions.md#a-finger-that-does-not-have-to-wait-data-drag-on-contact)
|
|
80
|
+
is: navi cannot see whether anything behind the surface scrolls. You know; say
|
|
81
|
+
so. It spends the hold: a `longpress` declared beside a `pan` that waits asks one
|
|
82
|
+
finger to answer two waits, and the surface's — the shorter one — is the one
|
|
83
|
+
answered. On a surface that only zooms there is no pan to wait for: one finger is
|
|
84
|
+
the page's, two are the surface's, and `"kept"` (below) has nothing to keep.
|
|
91
85
|
|
|
92
86
|
The wait is then asked before every pan, which is right while each finger
|
|
93
87
|
landing there is genuinely ambiguous. Once the user has settled into the plan it
|
|
@@ -98,14 +92,11 @@ is not, and asking again is asking three times for one sentence:
|
|
|
98
92
|
```
|
|
99
93
|
|
|
100
94
|
From the moment the surface is given the hand — the hold, a pinch, a mouse
|
|
101
|
-
travelling — it pans on contact, the way the same plan does opened full screen
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
The paid wait is a state to draw — the map answers on contact now, and the user
|
|
107
|
-
has to be told — so the surface says it: `[data-hand-kept]`, for as long as it
|
|
108
|
-
holds the hand, a finger on it or not.
|
|
95
|
+
travelling — it pans on contact, the way the same plan does opened full screen,
|
|
96
|
+
until a press lands away from it; navi watches for that press itself, so nothing
|
|
97
|
+
on your side listens to the window. The paid wait is a state to draw, and only a
|
|
98
|
+
`"kept"` surface carries it: `[data-hand-kept]`, for as long as it holds the
|
|
99
|
+
hand, a finger on it or not.
|
|
109
100
|
|
|
110
101
|
```css
|
|
111
102
|
.plan[data-hand-kept] {
|
|
@@ -113,23 +104,20 @@ holds the hand, a finger on it or not.
|
|
|
113
104
|
}
|
|
114
105
|
```
|
|
115
106
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
(`[data-hand-kept]:not([data-grabbed])` is the surface waiting, held, for the
|
|
120
|
-
next finger).
|
|
107
|
+
`[data-grabbed]` is the narrower word — a hand on it at this instant — so
|
|
108
|
+
`[data-hand-kept]:not([data-grabbed])` is the surface waiting, held, for the
|
|
109
|
+
next finger.
|
|
121
110
|
|
|
122
111
|
### A wheel that does not steal the page's scroll
|
|
123
112
|
|
|
124
|
-
A wheel over a surface in a page means to scroll that page
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
a
|
|
131
|
-
|
|
132
|
-
key away:
|
|
113
|
+
A wheel over a surface in a page means to scroll that page just as often as a
|
|
114
|
+
finger does, and is settled the other way for one reason: what a TOUCH may do is
|
|
115
|
+
decided before it lands, from `touch-action`, so only the caller can say it; a
|
|
116
|
+
WHEEL is read, and by the time the event is there, what scrolls around the
|
|
117
|
+
surface can simply be looked up. So nothing is declared: a bare wheel zooms only
|
|
118
|
+
where nothing around the surface would have scrolled — a map filling the screen,
|
|
119
|
+
a board in a modal (the walk stops at a modal) — and where something would, the
|
|
120
|
+
page keeps its scroll and the zoom is one key away:
|
|
133
121
|
|
|
134
122
|
| wheel over the surface | in a page that scrolls | where nothing scrolls |
|
|
135
123
|
| ---------------------- | ---------------------- | --------------------- |
|
|
@@ -137,22 +125,12 @@ key away:
|
|
|
137
125
|
| `ctrl` / `meta` held | zooms | zooms |
|
|
138
126
|
| trackpad pinch | zooms | zooms |
|
|
139
127
|
|
|
140
|
-
Nothing is declared for this, unlike `data-pan-after-hold` right above — and the
|
|
141
|
-
difference is worth knowing, because it is the same question asked twice. What a
|
|
142
|
-
TOUCH may do is settled before it lands, from `touch-action`, so nothing can be
|
|
143
|
-
read at that point and only the caller knows. A WHEEL is read: by the time the
|
|
144
|
-
event is there, what scrolls around the surface can simply be looked up, so navi
|
|
145
|
-
looks it up instead of asking. The walk stops at a modal, whose page behind is
|
|
146
|
-
not what a wheel over it is for.
|
|
147
|
-
|
|
148
128
|
A refused wheel is not a wheel that did nothing: navi says what it is waiting for
|
|
149
129
|
(`⌘ + scroll to zoom`, `Ctrl` off a Mac) in a callout over the surface, which
|
|
150
|
-
goes away with the gesture
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
zooming everywhere: it is the desk's version of two fingers on a phone, and it
|
|
155
|
-
was never the page's.
|
|
130
|
+
goes away with the gesture — navi's own text, `interaction.zoom.needs_modifier`,
|
|
131
|
+
overridden through `naviI18n` like any other (see [i18n.md](./i18n.md)). The
|
|
132
|
+
trackpad pinch arrives as a wheel with `ctrl` held, which is why it zooms
|
|
133
|
+
everywhere: it is the desk's version of two fingers on a phone.
|
|
156
134
|
|
|
157
135
|
```jsx
|
|
158
136
|
<Box data-zoom-on-contact interactions={{ pan, zoom }} />
|
|
@@ -160,32 +138,16 @@ was never the page's.
|
|
|
160
138
|
|
|
161
139
|
`data-zoom-on-contact` takes the bare wheel back, for a surface that owns it
|
|
162
140
|
whatever stands around it — a map whose page happens to scroll a little, an
|
|
163
|
-
editor where the wheel is a tool. Read off the element or any ancestor
|
|
164
|
-
place `data-pan-after-hold` is.
|
|
141
|
+
editor where the wheel is a tool. Read off the element or any ancestor.
|
|
165
142
|
|
|
166
143
|
### When the surface has the hand: `grab`, `release`, `[data-grabbed]`
|
|
167
144
|
|
|
168
145
|
A surface that is asked for — by a hold, by the first few pixels of travel, or
|
|
169
146
|
by a second finger — has an instant where it becomes the hand's, and nothing on
|
|
170
|
-
screen says it
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
```jsx
|
|
175
|
-
<Box
|
|
176
|
-
data-pan-after-hold
|
|
177
|
-
interactions={{
|
|
178
|
-
pan,
|
|
179
|
-
zoom,
|
|
180
|
-
grab: (event) => {
|
|
181
|
-
if (event.detail.pointerType === "touch") {
|
|
182
|
-
navigator.vibrate?.(10);
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
release: () => {},
|
|
186
|
-
}}
|
|
187
|
-
/>
|
|
188
|
-
```
|
|
147
|
+
screen says it: the hand moves too early and scrolls the page, or waits long past
|
|
148
|
+
the moment out of doubt. So the surface says it with the two words and the
|
|
149
|
+
attribute a carried element uses (see
|
|
150
|
+
[drag_interactions.md](./drag_interactions.md#saying-the-grab-is-acquired-grab)).
|
|
189
151
|
|
|
190
152
|
```css
|
|
191
153
|
.plan[data-grabbed] {
|
|
@@ -195,20 +157,17 @@ words a carried element says (`grab`, `release`) and the same attribute:
|
|
|
195
157
|
|
|
196
158
|
`[data-grabbed]` is on the element for as long as a hand is on the surface (on
|
|
197
159
|
one that only zooms, from the second finger landing to the last one lifting), so
|
|
198
|
-
a contour, a veil or a raised shadow needs no listener
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
nothing at all, and a finger that moves is told at the very moment the surface is
|
|
210
|
-
already moving under it. With a mouse it lands mid-travel. `grab` is told where it
|
|
211
|
-
happens, before the first `pan`.
|
|
160
|
+
a contour, a veil or a raised shadow needs no listener. `grab` and `release`,
|
|
161
|
+
declared beside `pan`/`zoom`, are for the rest — a vibration on the touch that
|
|
162
|
+
took the surface, a state kept elsewhere. Their detail is `{ pointerType }`; they
|
|
163
|
+
report and do not ask, and declared without `pan` or `zoom` they are a drag's
|
|
164
|
+
words again, which the dev warning says.
|
|
165
|
+
|
|
166
|
+
**Do not read the pointer capture instead.** The browser announces it just
|
|
167
|
+
before the NEXT pointer event, so a finger held still is announced nothing and a
|
|
168
|
+
finger that moves is told when the surface is already moving under it (see
|
|
169
|
+
[mobile_touch.md](./mobile_touch.md#a-finger-is-captured-to-what-it-touched-inside-shadow-trees-too)).
|
|
170
|
+
`grab` is told where it happens, before the first `pan`.
|
|
212
171
|
|
|
213
172
|
## Reference
|
|
214
173
|
|
|
@@ -217,5 +176,5 @@ happens, before the first `pan`.
|
|
|
217
176
|
page, on `installPanZoom` from `@jsenv/dom`.
|
|
218
177
|
- `@jsenv/dom` — `src/interaction/drag/pan_zoom.js`: the pointers, the pinch,
|
|
219
178
|
the wheel and what it is given to.
|
|
220
|
-
- `src/control/demos/38_interactions_demo.html` — a surface panned and zoomed
|
|
221
|
-
|
|
179
|
+
- `src/control/demos/38_interactions_demo.html` — a surface panned and zoomed,
|
|
180
|
+
after a hold, and kept.
|