@jsenv/navi 0.29.417 → 0.29.419
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 +347 -83
- package/dist/dev/jsenv_navi.js.map +12 -12
- package/dist/jsenv_navi.js +345 -81
- package/dist/jsenv_navi.js.map +12 -12
- package/docs/AI_INSTRUCTIONS.md +177 -258
- 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 +159 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +41 -44
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +291 -454
- package/docs/drag_to_travel.md +152 -748
- 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 +61 -78
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +170 -326
- 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 -473
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -317
- package/docs/state_binding.md +40 -84
- 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/interactions.md
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
- [What a swipe draws, and what it leaves to you](#what-a-swipe-draws-and-what-it-leaves-to-you)
|
|
10
10
|
- [Twice, whichever hand it is](#twice-whichever-hand-it-is)
|
|
11
11
|
- [The click on the way there](#the-click-on-the-way-there)
|
|
12
|
-
- [Carrying something, or a surface under the hand](#carrying-something-or-a-surface-under-the-hand)
|
|
13
12
|
- [An affordance inside somebody else's box: `selfInteractions`](#an-affordance-inside-somebody-elses-box-selfinteractions)
|
|
14
13
|
- [Why it is a list, and why it is required](#why-it-is-a-list-and-why-it-is-required)
|
|
15
14
|
- [Where the zone blocks: does it write to the control it sits in?](#where-the-zone-blocks-does-it-write-to-the-control-it-sits-in)
|
|
@@ -63,13 +62,20 @@ one — `Box`, `List.Item`, `Button`, `Link`, the field components. Its keys are
|
|
|
63
62
|
/>
|
|
64
63
|
```
|
|
65
64
|
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
`interactions` adds, it does not replace: a control keeps its own wiring for
|
|
66
|
+
`action` — a click on a button, a change on a field — and `interactions` is
|
|
67
|
+
everything else. One thing moves. A control asking for its action on the press
|
|
68
|
+
(`actionEvent="mousedown"`, `actionOnMouseDown`) waits for the `click` instead
|
|
69
|
+
as soon as a gesture disputing that press is declared — a swipe, a `longpress`,
|
|
70
|
+
the two counted taps, the drag family, `pan`/`zoom` — because the press is not
|
|
71
|
+
known to be a press until the gesture gives it up. `single_click` goes further
|
|
72
|
+
and takes the element's click itself (see
|
|
73
|
+
[The click on the way there](#the-click-on-the-way-there)).
|
|
74
|
+
|
|
75
|
+
A plain `Box` has no such wiring: it does nothing with `action` (dev warns), and
|
|
76
|
+
a click on it is declared like any other interaction,
|
|
77
|
+
`interactions={{ click: onSelect }}` — beside `move` or `grab` too, the click a
|
|
78
|
+
drag leaves behind being already suppressed.
|
|
73
79
|
|
|
74
80
|
### The four values
|
|
75
81
|
|
|
@@ -101,29 +107,20 @@ which the carrying interactions answer with).
|
|
|
101
107
|
| `pan` `zoom` | a surface under the hand, or under a wheel |
|
|
102
108
|
| `"keyboard:<shortcut>"` | keys, e.g. `"keyboard:ctrl+backspace"` |
|
|
103
109
|
|
|
104
|
-
Two holds on one press — a `longpress` declared
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
There is no `dblclick` in that table, on purpose: the browser fires it for a
|
|
119
|
-
mouse and never for a finger, so an element declaring it would answer half the
|
|
120
|
-
hands that reach it. `double_click` is the same gesture counted from the
|
|
121
|
-
pointer — see [Twice, whichever hand it is](#twice-whichever-hand-it-is).
|
|
122
|
-
|
|
123
|
-
A name nothing knows how to detect produces a dev warning naming the detectors
|
|
124
|
-
that exist. The carrying family and the two surface streams each have a file of
|
|
125
|
-
their own — [drag_interactions.md](./drag_interactions.md) and
|
|
126
|
-
[pan_zoom.md](./pan_zoom.md).
|
|
110
|
+
Two holds on one press — a `longpress` declared inside an element that declares
|
|
111
|
+
one too — are answered by the nearer one, the way a click is the innermost
|
|
112
|
+
target's (with equal delays; an outer hold made shorter fires first), and dev
|
|
113
|
+
says so once per element: the outer hold is a declaration that never fires. One
|
|
114
|
+
hold has one meaning — two things on one card want a hold and a click, not two
|
|
115
|
+
holds. A press on something carried inside the element (`move`, `moving`,
|
|
116
|
+
`reorder`…) is that thing's the same way, whether it drags by holding or by
|
|
117
|
+
distance — unless it says `"refuse"` for this press, and then it carries nothing.
|
|
118
|
+
|
|
119
|
+
There is no `dblclick`, on purpose (see
|
|
120
|
+
[Twice, whichever hand it is](#twice-whichever-hand-it-is)). A name nothing knows
|
|
121
|
+
how to detect gets a dev warning naming the detectors that exist. The carrying
|
|
122
|
+
family and the surface have a file each:
|
|
123
|
+
[drag_interactions.md](./drag_interactions.md) and [pan_zoom.md](./pan_zoom.md).
|
|
127
124
|
|
|
128
125
|
## Which interaction asked
|
|
129
126
|
|
|
@@ -148,10 +145,11 @@ receives.
|
|
|
148
145
|
ancestor can also listen for one, and `preventDefault()` on it means "not this
|
|
149
146
|
time".
|
|
150
147
|
|
|
151
|
-
The lower-level event
|
|
152
|
-
`
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
The lower-level event is reachable too: `interactionEvent.detail.event` is the
|
|
149
|
+
`pointerdown` a hold was made of, and the `pointerup` that ended a swipe or a
|
|
150
|
+
tap — which is how a menu is opened at the point the press happened. A drag's
|
|
151
|
+
is its `pointerdown`, release included, with one exception (see
|
|
152
|
+
[drag_interactions.md](./drag_interactions.md)).
|
|
155
153
|
|
|
156
154
|
## Reaching the control
|
|
157
155
|
|
|
@@ -163,24 +161,18 @@ still answers a callback; only the two requests (`"request_action"`,
|
|
|
163
161
|
`"request_ui_action"`) have nothing to ask, and say so in dev.
|
|
164
162
|
|
|
165
163
|
A `Box` that lays out **several** controls — a row of badges, a toolbar — belongs
|
|
166
|
-
to none of them: its interactions are its own, answered with no gate
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
neither has anything to show.
|
|
179
|
-
|
|
180
|
-
Which controls let a read through is theirs to say, not the caller's: a picker
|
|
181
|
-
with no popup of its own opens the browser's, which cannot be held read-only, so
|
|
182
|
-
that one refuses. `openWhileReadOnly={false}` is how a caller says the popup is
|
|
183
|
-
a form with nothing to read.
|
|
164
|
+
to none of them: its interactions are its own, answered with no gate. A click on
|
|
165
|
+
one of the controls reaches the box too, the way any click bubbles; the callback
|
|
166
|
+
reads `event.target` when it has to tell them apart.
|
|
167
|
+
|
|
168
|
+
**The gate takes every declared interaction for a write.** Letting a read through
|
|
169
|
+
a read-only or busy control is navi's to grant inside its own controls, and
|
|
170
|
+
`interactions` cannot ask for it: a callback on a read-only control is refused, a
|
|
171
|
+
`longpress` included. So a plain press opens a read-only or busy `<Picker>` — its
|
|
172
|
+
popup is where its answer is drawn, held read-only in turn — while a read-only
|
|
173
|
+
one opened by `openOn="longpress"` refuses, whatever `openWhileReadOnly` says.
|
|
174
|
+
Disabled refuses everything. Which pickers open, and `openWhileReadOnly={false}`,
|
|
175
|
+
are in the Picker's JSDoc.
|
|
184
176
|
|
|
185
177
|
## What a swipe draws, and what it leaves to you
|
|
186
178
|
|
|
@@ -196,9 +188,9 @@ that — and says where the gesture is up to:
|
|
|
196
188
|
|
|
197
189
|
WHAT is revealed behind is yours: navi does not know what putting a row away
|
|
198
190
|
looks like. A trail is usually a child of the swiped element sized off
|
|
199
|
-
`--swipe-pulled
|
|
200
|
-
|
|
201
|
-
|
|
191
|
+
`--swipe-pulled`: both values inherit, so a child reads them and a sibling
|
|
192
|
+
cannot — and the child travels with the row, since what navi translates is the
|
|
193
|
+
element that declares the gesture.
|
|
202
194
|
|
|
203
195
|
```css
|
|
204
196
|
.trail {
|
|
@@ -215,41 +207,30 @@ navi translates is the element that declares the gesture.
|
|
|
215
207
|
```
|
|
216
208
|
|
|
217
209
|
While the answer takes time, the element **stays where the gesture left it**, and
|
|
218
|
-
comes back once it settles — a failure leaves the row in place
|
|
219
|
-
again. What a success does to
|
|
220
|
-
|
|
210
|
+
comes back once it settles — a failure leaves the row in place to be tried
|
|
211
|
+
again. What a success does to it is yours (a list that redemands its rows, a row
|
|
212
|
+
that leaves): navi does not make it disappear.
|
|
221
213
|
|
|
222
214
|
## Twice, whichever hand it is
|
|
223
215
|
|
|
224
|
-
At the finger there is no `dblclick
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
216
|
+
At the finger there is no `dblclick`, and not even a second `click`: two taps in
|
|
217
|
+
the same place are the browser's own zoom gesture, so it withholds them (see
|
|
218
|
+
[mobile_touch.md](./mobile_touch.md#the-click-is-the-browsers-call-as-much-as-ours)).
|
|
219
|
+
An element declaring `dblclick` would answer half the hands that reach it.
|
|
220
|
+
Anything that means "twice" therefore counts presses, which is what
|
|
221
|
+
`double_click` is — one name for the mouse and the finger:
|
|
229
222
|
|
|
230
223
|
```jsx
|
|
231
224
|
<Box interactions={{ pan, zoom, double_click: (event) => open(event) }} />
|
|
232
225
|
```
|
|
233
226
|
|
|
234
|
-
One name for the mouse and for the finger — a tap is a click, and the same
|
|
235
|
-
gesture opens the plan at a desk and on a phone.
|
|
236
|
-
|
|
237
227
|
The rhythm is one window, opened by the FIRST press: the second has to land
|
|
238
|
-
inside it
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
| `data-double-click-delay` | `400` | ms the window stays open |
|
|
243
|
-
| `data-double-click-slop` | `30` | px the second press may land from the first |
|
|
244
|
-
|
|
245
|
-
The two numbers are read against the rest of the family rather than picked. 400
|
|
246
|
-
sits under the hold's 450: a pause longer than the wait navi calls "held" is
|
|
247
|
-
longer than one gesture — which also means a press slow enough to be a hold
|
|
228
|
+
inside it (`data-double-click-delay`) and near enough to it
|
|
229
|
+
(`data-double-click-slop`, a fingertip rather than a pixel, because between the
|
|
230
|
+
two the finger leaves the glass and lands again — see [Tuning](#tuning)). The
|
|
231
|
+
window is shorter than the hold's wait, so a press slow enough to be a hold
|
|
248
232
|
cannot start a double click, and a hold answered on the second press takes that
|
|
249
|
-
press back.
|
|
250
|
-
finger leaves the glass and lands again where it means to; the 8 px of
|
|
251
|
-
`data-longpress-slop` answer a different question (has this finger stood
|
|
252
|
-
still?).
|
|
233
|
+
press back.
|
|
253
234
|
|
|
254
235
|
### The click on the way there
|
|
255
236
|
|
|
@@ -258,10 +239,10 @@ By default both happen, the way they do in a browser: the first press is a
|
|
|
258
239
|
would have left behind is swallowed, the way every gesture swallows the one it
|
|
259
240
|
leaves.
|
|
260
241
|
|
|
261
|
-
`single_click` is for
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
242
|
+
`single_click` is for two answers that exclude each other — a plan that opens on
|
|
243
|
+
the double must not do whatever a lone tap does on the way there. It is the same
|
|
244
|
+
window read the other way, the tap that STAYED alone, said once the window has
|
|
245
|
+
closed on it:
|
|
265
246
|
|
|
266
247
|
```jsx
|
|
267
248
|
<Box
|
|
@@ -280,18 +261,6 @@ control that wants its action on a lone click asks for it there —
|
|
|
280
261
|
arrives. A keyboard activation is not held: nothing can double it, so it is said
|
|
281
262
|
at once.
|
|
282
263
|
|
|
283
|
-
## Carrying something, or a surface under the hand
|
|
284
|
-
|
|
285
|
-
Two families of interaction have a file of their own, because each is a subject:
|
|
286
|
-
|
|
287
|
-
- [drag_interactions.md](./drag_interactions.md) — an element picked up and
|
|
288
|
-
carried: `move`, `reorder`, `land`, `toss`, `leave`, the `moving` stream,
|
|
289
|
-
the `grab`/`release`/`refuse` moments, how the copy is dressed, and the
|
|
290
|
-
machinery handed over for a gesture whose product is a value;
|
|
291
|
-
- [pan_zoom.md](./pan_zoom.md) — a surface dragged to look elsewhere on and
|
|
292
|
-
pinched or wheeled to look closer at: `pan`, `zoom`, and what a touch and a
|
|
293
|
-
wheel over it may do to the page around it.
|
|
294
|
-
|
|
295
264
|
## An affordance inside somebody else's box: `selfInteractions`
|
|
296
265
|
|
|
297
266
|
A chip's cross inside a carried piece, an eye on a row that travels, a diskette
|
|
@@ -313,20 +282,13 @@ instead of firing from the DOM.
|
|
|
313
282
|
A press is not a drag. A drag announces itself — a few pixels of travel with a
|
|
314
283
|
mouse, a long hold with a finger — and a click is the absence of both, so the
|
|
315
284
|
two can be told apart without anyone guessing at pointerdown. An affordance
|
|
316
|
-
that claimed every gesture at once would be a HOLE in whatever it sits in
|
|
317
|
-
|
|
318
|
-
the
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
| ------------------------------- | ---------------------------- | ------------------------------------------ |
|
|
324
|
-
| `selfInteractions="click"` | the press | the grab — the card is still carried by it |
|
|
325
|
-
| `selfInteractions="click drag"` | both | — |
|
|
326
|
-
| `selfInteractions="*"` | every gesture, now and later | — |
|
|
327
|
-
|
|
328
|
-
`"*"` is there for the case where it is true, not as a shorthand: it is the one
|
|
329
|
-
value that will silently swallow a gesture navi has not shipped yet.
|
|
285
|
+
that claimed every gesture at once would be a HOLE in whatever it sits in, at
|
|
286
|
+
precisely the edge one grabs to carry it. So the claim names its interactions,
|
|
287
|
+
and what it does not name stays the zone's: `"click"` takes the press and leaves
|
|
288
|
+
the grab — the card is still carried by it — `"click drag"` takes both, and
|
|
289
|
+
`"*"` takes every gesture, now and later. `"*"` is there for the case where it
|
|
290
|
+
is true, not as a shorthand: it is the one value that will silently swallow a
|
|
291
|
+
gesture navi has not shipped yet.
|
|
330
292
|
|
|
331
293
|
`data-drag-ignore` says a different thing, to the gesture alone and for all of
|
|
332
294
|
them at once: the press there is none of the gesture's business, and the element
|
|
@@ -335,26 +297,22 @@ keeps both its cursor and its text selection.
|
|
|
335
297
|
### Where the zone blocks: does it write to the control it sits in?
|
|
336
298
|
|
|
337
299
|
That question, and nothing else, picks `whenSelfInteractionsBlocked` — what
|
|
338
|
-
becomes of the affordance where the zone around it is disabled or read-only.
|
|
339
|
-
|
|
340
|
-
|
|
300
|
+
becomes of the affordance where the zone around it is disabled or read-only. Only
|
|
301
|
+
the claimed interactions are its subject: the ones left to the zone were never
|
|
302
|
+
this element's to block.
|
|
341
303
|
|
|
342
|
-
| what it does |
|
|
343
|
-
| ------------------------------------------------------------ |
|
|
344
|
-
| writes to it (a cross that removes, a stepper) |
|
|
345
|
-
| writes to it, and its presence says there is something there | `
|
|
346
|
-
| never touches it (a diskette saving into MY address book) | `
|
|
304
|
+
| what it does | `whenSelfInteractionsBlocked` | on a blocked zone |
|
|
305
|
+
| ------------------------------------------------------------ | ----------------------------- | ----------------------------------- |
|
|
306
|
+
| writes to it (a cross that removes, a stepper) | `"hide"`, the default | it goes |
|
|
307
|
+
| writes to it, and its presence says there is something there | `"refuse"` | it stays and refuses with a callout |
|
|
308
|
+
| never touches it (a diskette saving into MY address book) | `"ignore"` | still lit, still pressed |
|
|
347
309
|
|
|
348
310
|
A greyed cross that still removes is worse than no cross — hence the default.
|
|
349
|
-
`"ignore"` is the other extreme and the caller owns it:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
navi's own `<Dialog.Close />` is the second canonical case: a read-only picker
|
|
355
|
-
still opens, and leaving what it opened writes nothing to it — so the cross is
|
|
356
|
-
`"ignore"` and a hand-written one must say the same, or it refuses the press
|
|
357
|
-
aimed at the way out (see
|
|
311
|
+
`"ignore"` is the other extreme and the caller owns it: answering "read-only" to
|
|
312
|
+
a gesture that was never going to write says nothing true, so use it only when
|
|
313
|
+
that is really the case. navi's `<Dialog.Close />` is one — leaving what a
|
|
314
|
+
read-only picker opened writes nothing to it — and a hand-written cross must say
|
|
315
|
+
the same, or it refuses the way out (see
|
|
358
316
|
[popup_open.md](./popup_open.md#the-close-cross)).
|
|
359
317
|
|
|
360
318
|
Busy is not on the list because busy does not block: it is the read-only a
|
|
@@ -362,118 +320,64 @@ running action sets on its way that does.
|
|
|
362
320
|
|
|
363
321
|
### The third question: whose value is it?
|
|
364
322
|
|
|
365
|
-
`selfInteractions` and `whenSelfInteractionsBlocked` are about the **gesture
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
group around it, so what it holds never joins that value (see
|
|
370
|
-
[form_changed.md](./form_changed.md#a-control-that-answers-for-itself)).
|
|
371
|
-
|
|
323
|
+
`selfInteractions` and `whenSelfInteractionsBlocked` are about the **gesture**.
|
|
324
|
+
Whose **value** an element carries is a separate question, and `standalone`
|
|
325
|
+
answers it: the control does not register with the form, picker or group around
|
|
326
|
+
it (see [form_changed.md](./form_changed.md#a-control-that-answers-for-itself)).
|
|
372
327
|
They come apart, which is why they are separate props:
|
|
373
328
|
|
|
374
|
-
| the element
|
|
375
|
-
|
|
|
376
|
-
| a chip's cross
|
|
377
|
-
| a door opening a sheet that writes into the form
|
|
378
|
-
|
|
|
379
|
-
|
|
380
|
-
Reading `standalone` as "ignore everything around me" is the trap: `disabled`,
|
|
381
|
-
`readOnly` and `loading` travel on their own contexts and go on reaching it,
|
|
382
|
-
because "what do I hold" and "may anything be changed here" are not the same
|
|
383
|
-
question.
|
|
329
|
+
| the element | says |
|
|
330
|
+
| ------------------------------------------------ | ------------------------------------------------------ |
|
|
331
|
+
| a chip's cross | `selfInteractions` — it never carried a value |
|
|
332
|
+
| a door opening a sheet that writes into the form | `standalone` — but a read-only form must still shut it |
|
|
333
|
+
| the diskette above | all three: own press, block not about it, own value |
|
|
384
334
|
|
|
385
335
|
### The fourth question: whose wait is it?
|
|
386
336
|
|
|
387
337
|
A running action says two things at once. To the control: I am mid-action —
|
|
388
338
|
busy, a second press refused, the error callout if it fails. To everything
|
|
389
|
-
around it: nothing here moves on — the form does not submit,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
The second half is a promise about an answer: a send holds something neither
|
|
394
|
-
committed nor given up, so the screen showing it stays. It is exactly wrong for
|
|
395
|
-
a run that was started to be LEFT running — activating a service worker update,
|
|
396
|
-
which lands only once the browser switches over and can be held by the page's
|
|
397
|
-
own in-flight work for minutes. Waiting is not the point; the app goes on being
|
|
398
|
-
used and the feedback is somewhere else entirely. `actionStandalone` says the
|
|
399
|
-
wait is the control's own:
|
|
339
|
+
around it: nothing here moves on — the form does not submit, the popup does not
|
|
340
|
+
close. `actionStandalone` keeps the first and drops the second: the wait is the
|
|
341
|
+
control's own, which is why it is `action` plus a word rather than the three
|
|
342
|
+
re-implemented by hand, and no ancestor is told.
|
|
400
343
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
Everything the control does for itself stays: it renders busy, it refuses a
|
|
408
|
-
second press, and it raises the error callout — which is why it keeps `action`
|
|
409
|
-
rather than re-implementing the three by hand. What changes is that no ancestor
|
|
410
|
-
is told: the form around it submits, and the panel it sits in closes on Escape,
|
|
411
|
-
on the backdrop and on its cross.
|
|
412
|
-
|
|
413
|
-
The question that picks it is what closing over the run would lose. An answer
|
|
414
|
-
being sent: everything — the popup is the only place its failure can be read,
|
|
415
|
-
and `actionStandalone` there is how a save fails behind a closed popup. Something
|
|
416
|
-
the app watches from somewhere else: nothing — it was never being watched here.
|
|
417
|
-
|
|
418
|
-
And when the answer being sent may never arrive at all — a request over a
|
|
419
|
-
network that stopped answering — the hold outlives what it was protecting:
|
|
420
|
-
nothing can close the popup, ever. `actionAbortable` keeps the hold and gives it
|
|
421
|
-
a release, so closing calls the run off rather than being refused. What that
|
|
422
|
-
costs, and where it may be said, is
|
|
423
|
-
[popup_open.md](./popup_open.md#the-popup-owns-its-open-state).
|
|
344
|
+
What picks it is what closing over the run would lose. Something the app watches
|
|
345
|
+
from somewhere else — a service worker update being activated — loses nothing.
|
|
346
|
+
An answer being sent loses everything: the popup is the only place its failure
|
|
347
|
+
can be read. That hold, and `actionAbortable` for a run whose answer may never
|
|
348
|
+
come, are [popup_open.md](./popup_open.md#the-popup-owns-its-open-state)'s.
|
|
424
349
|
|
|
425
350
|
### On something you draw yourself
|
|
426
351
|
|
|
427
352
|
`selfInteractions` is a `Box` prop too, so an affordance does not have to become
|
|
428
353
|
a control to claim its interactions — a pastille positioned in a card's corner
|
|
429
|
-
by its own class stays
|
|
354
|
+
by its own class stays what it was drawn as:
|
|
430
355
|
|
|
431
356
|
```jsx
|
|
432
357
|
<Box as="button" selfInteractions="click" className="court_side" onClick={explain}>
|
|
433
358
|
```
|
|
434
359
|
|
|
435
|
-
On a box the prop does
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
is about a gate, a callout and a control's own read-only, none of which a box
|
|
444
|
-
has. A box claims interactions and nothing more; put the affordance on a control
|
|
445
|
-
when what it does about a held zone matters.
|
|
360
|
+
On a box the prop does one thing: it writes `data-self-interactions`, the claim
|
|
361
|
+
itself — read by the controls above and by the gesture readers
|
|
362
|
+
(`data-drag-handle`, `data-drag-ignore` and friends are the same vocabulary).
|
|
363
|
+
Writing it by hand on an element navi does not render is the last resort: a typo
|
|
364
|
+
there is silent, whereas the prop is checked. `whenSelfInteractionsBlocked`
|
|
365
|
+
belongs to controls — a gate, a callout, a read-only of its own, none of which a
|
|
366
|
+
box has — so put the affordance on a control when what it does about a held zone
|
|
367
|
+
matters.
|
|
446
368
|
|
|
447
369
|
### When the affordance should sit OUTSIDE instead
|
|
448
370
|
|
|
449
|
-
`selfInteractions` says a façade CAN yield a zone; it does not say it should.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
So: an affordance that acts on what it sits in (a chip's cross, a stepper, an
|
|
460
|
-
eye on a row) belongs inside, and takes its press back with `selfInteractions`.
|
|
461
|
-
One that swaps what is being shown belongs outside it, where it can stay put.
|
|
462
|
-
|
|
463
|
-
Whichever side of the frame it ends up on, the control must be told, because a
|
|
464
|
-
control draws affordances of its own and will otherwise draw a second one:
|
|
465
|
-
|
|
466
|
-
- **inside** — a field has slots for it, `Input.UI.LeftSlot`,
|
|
467
|
-
`Input.UI.RightSlot`, `Input.UI.IconSlot` (an icon sized on the line rather
|
|
468
|
-
than on a character) and `Input.UI.UnitSlot`. They label the field, so a
|
|
469
|
-
press lands on it rather than blurring it — except when the field is not
|
|
470
|
-
focused yet, where the slot may take the focus itself, which is what a clear
|
|
471
|
-
cross or a reveal-password eye needs.
|
|
472
|
-
- **outside** — the icon the type would have drawn has to go, or it sits two
|
|
473
|
-
centimetres from yours: `icon={null}`. It is the same prop that replaces it
|
|
474
|
-
(`icon={<MySvg />}`) and that leaves it alone (left out). A `search` field
|
|
475
|
-
still swaps that slot for its clear cross once it holds a value, whatever
|
|
476
|
-
`icon` says — the cross is about the value, not about the decoration.
|
|
371
|
+
`selfInteractions` says a façade CAN yield a zone; it does not say it should. An
|
|
372
|
+
affordance that acts on what it sits in (a chip's cross, a stepper, an eye on a
|
|
373
|
+
row) belongs inside, and takes its press back with `selfInteractions`. One that
|
|
374
|
+
swaps what is being shown belongs outside both controls, at a fixed place, so the
|
|
375
|
+
pixel that opened the search is the one that closes it: `<ControlSwap>` is that
|
|
376
|
+
row.
|
|
377
|
+
|
|
378
|
+
Either way the control must be told, or it draws a second affordance of its
|
|
379
|
+
own: inside a field it goes in an `Input.UI` slot, and outside, `icon={null}`
|
|
380
|
+
takes away the icon the type would have drawn.
|
|
477
381
|
|
|
478
382
|
```jsx
|
|
479
383
|
<Input type="search" icon={null} /> // the row draws the magnifier
|
|
@@ -492,15 +396,13 @@ there yourself if it must not.
|
|
|
492
396
|
Read off the element or any ancestor carrying the attribute, so a whole list is
|
|
493
397
|
tuned in one place and a stylesheet can read the same value.
|
|
494
398
|
|
|
495
|
-
| Attribute
|
|
496
|
-
|
|
|
497
|
-
| `data-swipe-threshold`
|
|
498
|
-
| `data-longpress-delay`
|
|
499
|
-
| `data-longpress-slop`
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
`data-double-click-slop` (see [Twice, whichever hand it
|
|
503
|
-
is](#twice-whichever-hand-it-is)).
|
|
399
|
+
| Attribute | Default | Meaning |
|
|
400
|
+
| ------------------------- | ------- | ---------------------------------------------- |
|
|
401
|
+
| `data-swipe-threshold` | `0.33` | fraction of the element to pull to commit |
|
|
402
|
+
| `data-longpress-delay` | `450` | ms the press must be held |
|
|
403
|
+
| `data-longpress-slop` | `8` | px the pointer may drift during the wait |
|
|
404
|
+
| `data-double-click-delay` | `400` | ms the window stays open, from the first press |
|
|
405
|
+
| `data-double-click-slop` | `30` | px the second press may land from the first |
|
|
504
406
|
|
|
505
407
|
A threshold is a **fraction and never a distance**: the same gesture must mean
|
|
506
408
|
the same thing on a phone and on a wide screen. Speed answers on its own on top
|
|
@@ -509,114 +411,57 @@ of it — a brief flick counts whatever the distance covered.
|
|
|
509
411
|
## Registering an interaction navi does not have
|
|
510
412
|
|
|
511
413
|
The registry holds no detector of its own: navi's swipes, holds and shortcuts go
|
|
512
|
-
through the same door an application uses
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
let count = 0;
|
|
522
|
-
let timeout = null;
|
|
523
|
-
const onClick = (clickEvent) => {
|
|
524
|
-
count++;
|
|
525
|
-
clearTimeout(timeout);
|
|
526
|
-
timeout = setTimeout(() => {
|
|
527
|
-
count = 0;
|
|
528
|
-
}, 1000);
|
|
529
|
-
if (count < 3) {
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
count = 0;
|
|
533
|
-
trigger(clickEvent);
|
|
534
|
-
};
|
|
535
|
-
element.addEventListener("click", onClick);
|
|
536
|
-
return () => {
|
|
537
|
-
clearTimeout(timeout);
|
|
538
|
-
element.removeEventListener("click", onClick);
|
|
539
|
-
};
|
|
540
|
-
},
|
|
541
|
-
});
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
`setup(element, trigger, { types, readConfig })` runs **once per element** and
|
|
545
|
-
returns how to undo whatever it did. Listeners, attributes, anything: it is a
|
|
546
|
-
plain setup and teardown, so a detector counts what it needs in its own closure
|
|
547
|
-
and nothing has to hold state on its behalf.
|
|
548
|
-
|
|
549
|
-
`claims` is asked one name at a time, and a detector claims a **set** of them
|
|
550
|
-
rather than one, because interactions sharing an input have to be arbitrated
|
|
551
|
-
together — a swipe, a hold and a click dispute the same press, and read apart
|
|
552
|
-
they walk over each other. `types` (third argument) is which of them were
|
|
553
|
-
actually declared here.
|
|
554
|
-
|
|
555
|
-
`trigger(type, originalEvent, detail)` says the interaction happened. Called with
|
|
556
|
-
a single event — `trigger(event)` — the type is the detector's own, which only
|
|
557
|
-
works when exactly one of its names is declared. When `originalEvent.type` is
|
|
558
|
-
already the interaction's name (a native one), that event IS the interaction and
|
|
414
|
+
through the same door an application uses, `defineInteractionDetector`. Its
|
|
415
|
+
JSDoc is the contract — `setup` once per element and its teardown, what
|
|
416
|
+
`trigger` returns (`null` when nothing ran, else a promise that rejects when
|
|
417
|
+
the effect failed), `implies`, `refusable`, `disputesPress` — and
|
|
418
|
+
`38_interactions_demo.html` registers a `triple_click` from the page. A detector
|
|
419
|
+
claims a **set** of names rather than one, because interactions sharing an input
|
|
420
|
+
have to be arbitrated together: a swipe, a hold and a click dispute the same
|
|
421
|
+
press, and read apart they walk over each other. An event handed to `trigger`
|
|
422
|
+
that already bears the interaction's name (a native one) IS the interaction, and
|
|
559
423
|
no second one is dispatched.
|
|
560
424
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
`readConfig(attribute, defaultValue)` reads a number off the element or any
|
|
568
|
-
ancestor carrying that attribute, so a whole list is tuned in one place.
|
|
569
|
-
|
|
570
|
-
A detector that reads the pointer must mark itself in the DOM so a travelling
|
|
571
|
-
container above it does not take the gesture:
|
|
572
|
-
`element.setAttribute("data-no-drag-travel", "")`, undone in the teardown (see
|
|
573
|
-
`docs/drag_to_travel.md`). navi's own swipes do the equivalent with
|
|
574
|
-
`data-travel-by-drag`.
|
|
425
|
+
A detector that reads the pointer says two more things: `disputesPress`, so a
|
|
426
|
+
control asking for its action on the press waits for the click (see
|
|
427
|
+
[The prop](#the-prop)); and `data-no-drag-travel` on the element, undone in the
|
|
428
|
+
teardown, so a travelling container above it does not take the gesture (see
|
|
429
|
+
[drag_to_travel.md](./drag_to_travel.md#a-navi-component-that-reads-the-pointer-marks-itself)).
|
|
575
430
|
|
|
576
431
|
## Things worth knowing before guessing
|
|
577
432
|
|
|
578
|
-
- **A drag says its axes to whoever else answers the press.**
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
wire — see `docs/drag_to_travel.md`. A `Dialog` docked to an edge goes
|
|
584
|
-
further and reads the press only on its header (plus anything carrying
|
|
585
|
-
`data-swipe-grip`), so its body is free whatever is in it.
|
|
433
|
+
- **A drag says its axes to whoever else answers the press.** A list reordered
|
|
434
|
+
vertically inside a row of slides swiped sideways leaves the sideways gesture
|
|
435
|
+
alone, and a piece carried both ways inside a bottom sheet takes the press
|
|
436
|
+
whole (see [drag_to_travel.md](./drag_to_travel.md#who-owns-a-gesture), grips
|
|
437
|
+
included).
|
|
586
438
|
- **A hold does not take the context menu.** Declaring `longpress` says what a
|
|
587
439
|
held finger does; a right click comes from the other button and keeps opening
|
|
588
|
-
the browser's menu
|
|
589
|
-
|
|
590
|
-
|
|
440
|
+
the browser's menu — declare `contextmenu` beside it to make it do the same.
|
|
441
|
+
(A held _finger_ is the system's own context-menu gesture, and that one is
|
|
442
|
+
refused for the length of the press, not just of the wait.)
|
|
591
443
|
- **Where the press already means something, text is not selected.** An element
|
|
592
|
-
declaring `longpress`, a swipe or a counted tap
|
|
593
|
-
`
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
444
|
+
declaring `longpress`, a swipe or a counted tap, and a drag source in a
|
|
445
|
+
`data-drag-on-contact` place, keep their text unselectable, for every pointer:
|
|
446
|
+
the browser answers that same press with a selection of its own (the word
|
|
447
|
+
under the thumb, blue, with handles) that nothing takes back, and a mouse
|
|
448
|
+
cannot finish a selection begun where the press is a gesture. What never
|
|
449
|
+
answered that press keeps its text: a field, a popover or a dialog opened from
|
|
450
|
+
inside, anything marked `data-drag-ignore`. Any other drag source has the
|
|
451
|
+
selection refused only for the length of its gesture — in time for a mouse,
|
|
452
|
+
too late for a finger held still — so it says `user-select: none` itself (see
|
|
453
|
+
[drag_interactions.md](./drag_interactions.md#the-text-inside-user-select-none)).
|
|
602
454
|
- **A swipe cannot also be dragged out of the page.** An element declaring a
|
|
603
455
|
swipe gets `draggable={false}` and its `dragstart` refused — a native drag _is_
|
|
604
456
|
press-and-move, and a link or an image is draggable without anyone asking. One
|
|
605
457
|
gesture cannot mean both.
|
|
606
|
-
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
on the element:
|
|
614
|
-
`triggerNaviCommand(target, "--navi-open", interactionEvent, { anchor })`.
|
|
615
|
-
- **A swipe has no keyboard equivalent, and neither has a double click.** There
|
|
616
|
-
is nothing to press that means "swipe right", and no key that means "twice",
|
|
617
|
-
so either is only reachable if something else on the element offers the same
|
|
618
|
-
thing — a `"keyboard:<shortcut>"`, a `contextmenu`, or the control's own
|
|
619
|
-
action.
|
|
458
|
+
- **A popup can open while the finger is still down** — a menu under a waiting
|
|
459
|
+
finger is the native gesture (see
|
|
460
|
+
[popup_open.md](./popup_open.md#opening-while-the-finger-is-still-down), and
|
|
461
|
+
[the anchor](./popup_open.md#the-anchor) to place it at the press point).
|
|
462
|
+
- **A swipe has no keyboard equivalent, and neither has a double click.** Either
|
|
463
|
+
is only reachable if something else on the element offers the same thing — a
|
|
464
|
+
`"keyboard:<shortcut>"`, a `contextmenu`, or the control's own action.
|
|
620
465
|
|
|
621
466
|
## Reference
|
|
622
467
|
|
|
@@ -625,7 +470,6 @@ container above it does not take the gesture:
|
|
|
625
470
|
- `src/control/interaction/interaction_press.js` — swipes, holds and the two
|
|
626
471
|
taps, and what a swipe writes on the element.
|
|
627
472
|
- `src/control/interaction/interaction_keyboard.js`,
|
|
628
|
-
`interaction_native.js` — shortcuts, and the browser's own events.
|
|
629
|
-
carrying and surface detectors are referenced from their own docs.
|
|
473
|
+
`interaction_native.js` — shortcuts, and the browser's own events.
|
|
630
474
|
- `src/control/demos/38_interactions_demo.html` — every case above, plus a
|
|
631
475
|
mailbox, a board, a surface, and a custom gesture registered from the page.
|