@jsenv/navi 0.29.416 → 0.29.418

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.
@@ -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
- `action` is a control's prop and is untouched by this: a control keeps its own
67
- wiring — a click on a button, a change on a field — and `interactions` is the
68
- other half, everything that is not that natural one. A plain `Box` has no such
69
- wiring: it does nothing with `action` (dev warns), and a click on it is declared
70
- like any other interaction, `interactions={{ click: onSelect }}`. The click the
71
- browser fires after a drag is already suppressed, so `click` sits next to `move`
72
- or `grab` without fighting them.
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 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. That is with equal delays; an outer hold made shorter than the inner one
108
- fires first. navi says so in dev, once per element, the first time it happens:
109
- the outer hold is a declaration that can never fire, and a picker opened by
110
- `openOn="longpress"` around a card that already holds one is exactly where it
111
- happens. One hold has one meaning — two things on one card want two gestures
112
- (a hold and a click), not two holds. A press that lands on something carried
113
- inside the element (`move`, `moving`, `reorder`…) is that thing's the same
114
- way: the outer hold does not wait on it, whether the source drags by holding
115
- or by distance — except a source that says `"refuse"` for this press, which
116
- carries nothing.
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 the interaction was read from is reachable too:
152
- `interactionEvent.detail.event` is the `pointerdown` a hold or a drag was made
153
- of (a drag's release included), and the `pointerup` that ended a swipe or a tap
154
- — which is how a menu is opened at the point the press happened.
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, exactly as
167
- a `Box` with no control near it. A click on its empty part is the box's, and a
168
- click on one of the controls reaches the box too, the way any click bubbles;
169
- the callback reads `event.target` when it has to tell them apart.
170
-
171
- The one thing the gate weighs besides the control's state is what the
172
- interaction would do to it. Everything writes unless it says otherwise; an
173
- interaction that only shows what is already there declares `intent: "read"`, and
174
- a control held read-only lets that one through. That is how a read-only
175
- `<Picker>` still opens: the popup is where its answer is really drawn, so it
176
- opens and everything inside it is held read-only in turn. Disabled and busy go
177
- on refusing either way — one is out of service, the other is mid-operation, and
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`. Both values inherit, so a child reads them; a sibling cannot,
200
- which is why the trail goes inside — and it travels with the row, since what
201
- navi translates is the element that declares the gesture.
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 so it can be tried
219
- again. What a success does to the element is yours (a list that redemands its
220
- rows, a row that leaves): navi does not make it disappear.
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`. There is not even a second `click`: two
225
- taps in the same place are a gesture the browser keeps for itself (its own
226
- zoom), so it withholds them — measured on a phone, two taps 120 ms apart give
227
- two `pointerup`, ONE `click` and no `dblclick` at all. Anything that wants to
228
- mean "twice" therefore has to count presses, which is what `double_click` is:
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, and near enough to it.
239
-
240
- | Attribute | Default | Meaning |
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 and near enough to it (`data-double-click-delay` and
229
+ `data-double-click-slop`, see [Tuning](#tuning)) — a fingertip rather than a
230
+ pixel, because between the two the finger leaves the glass and lands again. 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. 30 px is a fingertip and not a pixel, because between the two the
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 the case where even the first one is too much: two
262
- answers that exclude each other — a plan that opens on the double must not do
263
- whatever a lone tap does on the way there. It is the same window read the other
264
- way, the tap that STAYED alone, said once the window has closed on it:
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: a
317
- badge drawn against the edge of a card is a seventh of that card, and precisely
318
- the edge one grabs to carry it.
319
-
320
- So the claim names its interactions, and what it does not name stays the zone's:
321
-
322
- | written | takes | leaves |
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
- The claimed interactions are its subject, and only them: the ones left to the
340
- zone were never this element's to block.
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 | written | on a blocked zone |
343
- | ------------------------------------------------------------ | -------------------------------------- | ----------------------------------------- |
344
- | writes to it (a cross that removes, a stepper) | nothing — `"hide"` is the default | it goes |
345
- | writes to it, and its presence says there is something there | `whenSelfInteractionsBlocked="refuse"` | it stays and refuses with a callout |
346
- | never touches it (a diskette saving into MY address book) | `whenSelfInteractionsBlocked="ignore"` | nothing changes: still lit, still pressed |
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: the zone's read-only is
350
- about a value the affordance does not write, so answering "read-only" to a
351
- gesture that was never going to write anything says nothing true. Use it only
352
- when that is really the case.
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
- who a pointer event belongs to, and what a block on the box around it does to
367
- that gesture. Whose **value** an element carries is a separate question, and
368
- `standalone` answers it: the control does not register with the form, picker or
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 | says |
375
- | ------------------------------------------------------- | --------------------------------------------------------------------------- |
376
- | a chip's cross | `selfInteractions` — it is a button, it never carried a value |
377
- | a door opening a sheet that writes into the form | `standalone` — no value of its own, but a read-only form must still shut it |
378
- | a diskette filing a name into the reader's address book | all three — own press, block is not about it, own value |
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, and the popup does
390
- not close (see
391
- [popup_open.md](./popup_open.md#the-popup-owns-its-open-state)).
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
- ```jsx
402
- <Button action={() => activateUpdate()} actionStandalone>
403
- Activate
404
- </Button>
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 exactly what it was drawn as:
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 exactly one thing: it writes `data-self-interactions`.
436
- That attribute is the claim — it is what the controls above read, and what the
437
- gesture readers read (`data-drag-handle`, `data-drag-ignore` and friends are the
438
- same vocabulary), each picking its own word out of it. Writing it by hand on an
439
- element navi does not render works and is the last resort: a typo there is
440
- silent, whereas the prop is spelled once and checked.
441
-
442
- `whenSelfInteractionsBlocked` is the other half, and it belongs to controls: it
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
- What decides is whether the affordance stays where the finger left it.
451
-
452
- An icon that lives inside its control while that control is showing, and
453
- becomes a pill of its own once it is not, is a switch that moves when you flip
454
- it: the finger that opened the search has to travel somewhere else to close it.
455
- Draw it outside both controls, at a fixed place, and the same pixel does both —
456
- which is the whole gesture on a phone. `<ControlSwap>` is that row: two
457
- controls taking turns in the middle, a fixed cap at each end.
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 | Default | Meaning |
496
- | ---------------------- | ------- | ----------------------------------------- |
497
- | `data-swipe-threshold` | `0.33` | fraction of the element to pull to commit |
498
- | `data-longpress-delay` | `450` | ms the press must be held |
499
- | `data-longpress-slop` | `8` | px the pointer may drift during the wait |
500
-
501
- Plus the two the double click is counted with, `data-double-click-delay` and
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,55 @@ 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
- ```js
515
- import { defineInteractionDetector } from "@jsenv/navi";
516
-
517
- defineInteractionDetector({
518
- name: "triple_click",
519
- claims: (type) => type === "triple_click",
520
- setup: (element, trigger) => {
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
559
- no second one is dispatched.
560
-
561
- It returns **`null` when nothing ran** (the gate refused, no control to ask, the
562
- interaction event was prevented) and otherwise a **promise**: resolved once the
563
- effect worked, rejected when it did not. Those two answers are not the same and a
564
- detector usually treats them differently — a row pulled out comes back either
565
- way, something thrown off the screen only comes back if the throw failed.
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`.
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.
422
+
423
+ A detector that reads the pointer says two more things: `disputesPress`, so a
424
+ control asking for its action on the press waits for the click (see
425
+ [The prop](#the-prop)); and `data-no-drag-travel` on the element, undone in the
426
+ teardown, so a travelling container above it does not take the gesture (see
427
+ [drag_to_travel.md](./drag_to_travel.md#a-navi-component-that-reads-the-pointer-marks-itself)).
575
428
 
576
429
  ## Things worth knowing before guessing
577
430
 
578
- - **A drag says its axes to whoever else answers the press.** `data-drag-axis`
579
- is written into the DOM as `data-drag-source`, and a box above that travels
580
- under the same finger reads it before answering: a list reordered vertically
581
- inside a row of slides swiped sideways leaves the sideways gesture alone, and a
582
- piece carried both ways inside a bottom sheet takes the press whole. Nothing to
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.
431
+ - **A drag says its axes to whoever else answers the press.** A list reordered
432
+ vertically inside a row of slides swiped sideways leaves the sideways gesture
433
+ alone, and a piece carried both ways inside a bottom sheet takes the press
434
+ whole (see [drag_to_travel.md](./drag_to_travel.md#who-owns-a-gesture), grips
435
+ included).
586
436
  - **A hold does not take the context menu.** Declaring `longpress` says what a
587
437
  held finger does; a right click comes from the other button and keeps opening
588
- the browser's menu. Declare `contextmenu` beside it to make the right click do
589
- the same thing. (A held _finger_ is the system's own context-menu gesture, and
590
- that one is refused for the length of the press, not just of the wait.)
438
+ the browser's menu — declare `contextmenu` beside it to make it do the same.
439
+ (A held _finger_ is the system's own context-menu gesture, and that one is
440
+ refused for the length of the press, not just of the wait.)
591
441
  - **Where the press already means something, text is not selected.** An element
592
- declaring `longpress`, a swipe or a counted tap (`double_click`,
593
- `single_click`), and a drag source standing in a `data-drag-on-contact`
594
- place, keep their text unselectable: the browser answers
595
- that same press with a selection of its own — the word under the thumb, blue,
596
- with handles — and nothing takes it back once the press is over. For every
597
- pointer, mouse included, which cannot finish a selection begun where the press
598
- is a gesture. What never answered that press keeps its text: a field, a popover
599
- or a dialog opened from inside, and anything marked `data-drag-ignore`. A drag
600
- source taken by long press is not concerned — the grab happens first, and the
601
- gesture refuses the selection for its own length.
442
+ declaring `longpress`, a swipe or a counted tap, and a drag source in a
443
+ `data-drag-on-contact` place, keep their text unselectable, for every pointer:
444
+ the browser answers that same press with a selection of its own (the word
445
+ under the thumb, blue, with handles) that nothing takes back, and a mouse
446
+ cannot finish a selection begun where the press is a gesture. What never
447
+ answered that press keeps its text: a field, a popover or a dialog opened from
448
+ inside, anything marked `data-drag-ignore`. Any other drag source has the
449
+ selection refused only for the length of its gesture — in time for a mouse,
450
+ too late for a finger held still — so it says `user-select: none` itself (see
451
+ [drag_interactions.md](./drag_interactions.md#the-text-inside-user-select-none)).
602
452
  - **A swipe cannot also be dragged out of the page.** An element declaring a
603
453
  swipe gets `draggable={false}` and its `dragstart` refused — a native drag _is_
604
454
  press-and-move, and a link or an image is draggable without anyone asking. One
605
455
  gesture cannot mean both.
606
- - **`interactions` adds, it does not replace.** A control's own wiring stays:
607
- `actionEvent` / `actionOnMouseDown` are still how you change what triggers
608
- `action` by default.
609
- - **A popup can open while the finger is still down.** navi's `Popover` is
610
- `popover="manual"` and owns its dismissal, so the `pointerup` ending a hold is
611
- not read as an interaction outside it — a menu can appear under a waiting
612
- finger, which is the native gesture. To place it at the press point rather than
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.
456
+ - **A popup can open while the finger is still down** — a menu under a waiting
457
+ finger is the native gesture (see
458
+ [popup_open.md](./popup_open.md#opening-while-the-finger-is-still-down), and
459
+ [the anchor](./popup_open.md#the-anchor) to place it at the press point).
460
+ - **A swipe has no keyboard equivalent, and neither has a double click.** Either
461
+ is only reachable if something else on the element offers the same thing — a
462
+ `"keyboard:<shortcut>"`, a `contextmenu`, or the control's own action.
620
463
 
621
464
  ## Reference
622
465
 
@@ -625,7 +468,6 @@ container above it does not take the gesture:
625
468
  - `src/control/interaction/interaction_press.js` — swipes, holds and the two
626
469
  taps, and what a swipe writes on the element.
627
470
  - `src/control/interaction/interaction_keyboard.js`,
628
- `interaction_native.js` — shortcuts, and the browser's own events. The
629
- carrying and surface detectors are referenced from their own docs.
471
+ `interaction_native.js` — shortcuts, and the browser's own events.
630
472
  - `src/control/demos/38_interactions_demo.html` — every case above, plus a
631
473
  mailbox, a board, a surface, and a custom gesture registered from the page.