@jsenv/navi 0.29.387 → 0.29.388
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 +4 -4
- package/dist/dev/jsenv_navi.js +400 -310
- package/dist/dev/jsenv_navi.js.map +72 -72
- package/dist/dev/jsenv_navi_side_effects.js.map +1 -1
- package/dist/jsenv_navi.js +396 -306
- package/dist/jsenv_navi.js.map +72 -72
- package/dist/jsenv_navi_side_effects.js.map +1 -1
- package/docs/AI_INSTRUCTIONS.md +39 -23
- package/docs/actions.md +6 -4
- package/docs/autofocus.md +2 -1
- package/docs/badge_list.md +5 -3
- package/docs/control_group.md +4 -3
- package/docs/control_object.md +1 -1
- package/docs/control_value.md +8 -9
- package/docs/css_architecture.md +16 -14
- package/docs/dialog_shape.md +12 -8
- package/docs/drag_interactions.md +44 -36
- package/docs/drag_to_travel.md +29 -23
- package/docs/dynamic_import.md +7 -5
- package/docs/error_handling.md +26 -27
- package/docs/field_validation.md +35 -6
- package/docs/interactions.md +28 -23
- package/docs/list_action.md +8 -5
- package/docs/list_refresh.md +7 -1
- package/docs/mobile_layout_pitfalls.md +6 -5
- package/docs/navigation.md +7 -5
- package/docs/network_policy.md +10 -3
- package/docs/pan_zoom.md +2 -2
- package/docs/popup_backdrop.md +11 -5
- package/docs/popup_lift.md +5 -3
- package/docs/popup_open.md +39 -28
- package/docs/resource.md +24 -17
- package/docs/route_transitions.md +13 -10
- package/docs/safe_area.md +38 -22
- package/docs/scroll.md +11 -9
- package/docs/testid.md +10 -1
- package/docs/typography.md +2 -3
- package/docs/view_transitions.md +9 -9
- package/docs/z_index.md +6 -5
- package/package.json +2 -2
|
@@ -6,6 +6,8 @@ An element picked up and carried, and what letting go means. It is one gesture
|
|
|
6
6
|
the gate they go through and how a control learns which of them asked are in
|
|
7
7
|
[interactions.md](./interactions.md); who owns a press when the carried thing
|
|
8
8
|
stands in a box that travels is in [drag_to_travel.md](./drag_to_travel.md).
|
|
9
|
+
Whatever the moment, `event.detail.event` is the `pointerdown` the carry began
|
|
10
|
+
with — for the release as well, unlike a swipe's.
|
|
9
11
|
|
|
10
12
|
- [Carrying something: `move`, `reorder`, `land`, `toss`, `leave`](#carrying-something-move-reorder-land-toss-leave)
|
|
11
13
|
- [While it is being moved: `moving`](#while-it-is-being-moved-moving)
|
|
@@ -29,11 +31,12 @@ differs is the release. One detector reads them all, because it is one press.
|
|
|
29
31
|
`toss` and `leave` each **combine** with `reorder` and with `land`: dropped on
|
|
30
32
|
another item the element changes places, thrown far and fast it is gotten rid of,
|
|
31
33
|
let go of away from every place it leaves. `leave` combines with `move` as well.
|
|
32
|
-
`
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
`move` does **not** combine with any of the three that carry a copy (`reorder`,
|
|
35
|
+
`land`, `toss`), and neither do `reorder` and `land` with each other — one
|
|
36
|
+
release cannot mean two of those. Declared together, the copy-carrying one wins
|
|
37
|
+
and `move` is never answered — the element itself never travels, and a release
|
|
38
|
+
that is not the other outcome means nothing at all; of `reorder` and `land`,
|
|
39
|
+
`land` wins. A dev warning says so in each case.
|
|
37
40
|
|
|
38
41
|
`move` carries the element ITSELF and leaves it where it was put; the others
|
|
39
42
|
carry a copy and put the original back. That is the same difference said in layout
|
|
@@ -50,9 +53,10 @@ place taken by the list.
|
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
`data-drag-free` on the element or a container lets it leave; by default a `move`
|
|
53
|
-
stays inside what one can SEE of its container —
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
stays inside what one can SEE of its scroll container — the nearest ancestor whose
|
|
57
|
+
`overflow` is `auto` or `scroll`, the page's viewport when there is none — so a
|
|
58
|
+
board that only cuts its content (`hidden`, `clip`) does not hold what moves on
|
|
59
|
+
it. A `move` whose answer rejects travels back, because a
|
|
56
60
|
place the application would not accept must not stay on screen as if it had.
|
|
57
61
|
|
|
58
62
|
**Where the position is kept is the answer's.** An element drawn from state —
|
|
@@ -202,8 +206,9 @@ back, not a row deleted — a list declares `toss`, a surface declares `leave`.
|
|
|
202
206
|
```
|
|
203
207
|
|
|
204
208
|
`leave` combines with every other outcome. Beside `land` or `reorder`, "outside" is
|
|
205
|
-
away from every place.
|
|
206
|
-
|
|
209
|
+
away from every place. Where nothing is a place — beside `move`, which carries the
|
|
210
|
+
element itself, beside `toss`, or alone — it is outside the surface the element
|
|
211
|
+
stands in: the nearest
|
|
207
212
|
`data-droppable` ancestor, or, without one, what can be seen of the scroll
|
|
208
213
|
container (a dev warning says which). Either way it is judged on the element's
|
|
209
214
|
box no longer overlapping it, not on the pointer, which is still well inside the
|
|
@@ -220,8 +225,8 @@ frame when a small marker has just left it.
|
|
|
220
225
|
</Plan>
|
|
221
226
|
```
|
|
222
227
|
|
|
223
|
-
Its detail is `toss`'s — `{ id, x, y }`, the distance travelled
|
|
224
|
-
is animated with. Picked up and put straight back down stays a cancel: it has to
|
|
228
|
+
Its detail is `toss`'s without the speed — `{ id, x, y }`, the distance travelled
|
|
229
|
+
being what an exit is animated with. Picked up and put straight back down stays a cancel: it has to
|
|
225
230
|
have gone somewhere to be away from anything.
|
|
226
231
|
|
|
227
232
|
What the thing does while the answer is asked follows what was carried. A copy
|
|
@@ -283,8 +288,8 @@ place, swap the two, refuse.
|
|
|
283
288
|
/>
|
|
284
289
|
```
|
|
285
290
|
|
|
286
|
-
`toId`
|
|
287
|
-
meant nothing, and the interaction does not happen at all.
|
|
291
|
+
`toId` always names an element and is **never null**: a copy over nothing is a
|
|
292
|
+
release that meant nothing, and the interaction does not happen at all.
|
|
288
293
|
|
|
289
294
|
**Which elements are places: those marked `data-droppable`, and only those.**
|
|
290
295
|
Declaring `land` says an element can be CARRIED, which on a board is a different
|
|
@@ -487,9 +492,11 @@ vibration above, a state kept elsewhere, a counter.
|
|
|
487
492
|
|
|
488
493
|
`grab` **reports, it does not ask**: what it returns is not waited on, and
|
|
489
494
|
preventing its event does not call the gesture off. And it is not an interaction on
|
|
490
|
-
its own — declared without one of the five above there is no
|
|
491
|
-
the beginning of, and a dev warning says so
|
|
492
|
-
|
|
495
|
+
its own — declared without one of the five above (or `moving`) there is no
|
|
496
|
+
gesture for it to be the beginning of, and a dev warning says so, unless the
|
|
497
|
+
element declares `pan` or `zoom`, whose moments they then are
|
|
498
|
+
([pan_zoom.md](./pan_zoom.md)). Its detail carries `pointerType` and the
|
|
499
|
+
`gestureInfo`.
|
|
493
500
|
|
|
494
501
|
A `longpress` needs none of this: it already happens at the moment the hold is
|
|
495
502
|
acquired, not at the release.
|
|
@@ -574,7 +581,7 @@ prevented.
|
|
|
574
581
|
One outcome refusing refuses the whole gesture — the five answer one carry, and
|
|
575
582
|
something that must not be carried has none of them. Like `grab` and `release`,
|
|
576
583
|
`refuse` **reports, it does not ask**, and it is not an interaction on its own.
|
|
577
|
-
Its detail is `{ pointerType }`
|
|
584
|
+
Its detail is `{ pointerType }` beside the press (`event`), and no `gestureInfo`
|
|
578
585
|
because there was no gesture, and what feedback wants to know is which hand asked
|
|
579
586
|
— a vibration for a finger, nothing extra for a mouse whose cursor has already
|
|
580
587
|
said it.
|
|
@@ -587,11 +594,11 @@ usually gets its own from the list around it, which the copy has left. So the
|
|
|
587
594
|
clone's look is the page's to declare, through the attributes the gesture puts on
|
|
588
595
|
it:
|
|
589
596
|
|
|
590
|
-
| Attribute | On
|
|
591
|
-
| ------------------------- |
|
|
592
|
-
| `navi-drag-clone` | the copy being carried
|
|
593
|
-
| `navi-drag-clone-wrapper` | what positions it
|
|
594
|
-
| `navi-drag-clone-source` | the original, still in place (already hidden)
|
|
597
|
+
| Attribute | On |
|
|
598
|
+
| ------------------------- | ---------------------------------------------------- |
|
|
599
|
+
| `navi-drag-clone` | the copy being carried (already shadowed and scaled) |
|
|
600
|
+
| `navi-drag-clone-wrapper` | what positions it, in the top layer |
|
|
601
|
+
| `navi-drag-clone-source` | the original, still in place (already hidden) |
|
|
595
602
|
|
|
596
603
|
```css
|
|
597
604
|
.task[navi-drag-clone] {
|
|
@@ -603,19 +610,19 @@ it:
|
|
|
603
610
|
Reusing the item's own class is the point: the copy is that item, so it is styled
|
|
604
611
|
as that item plus whatever being carried changes.
|
|
605
612
|
|
|
606
|
-
**Which is also the trap, for anything positioned on a board.** The copy is
|
|
607
|
-
|
|
608
|
-
attribute
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
613
|
+
**Which is also the trap, for anything positioned on a board.** The copy is a
|
|
614
|
+
deep clone of the element put in a carrier box, so a geometry written in the style
|
|
615
|
+
attribute comes with it. navi takes back where it stands (`position`, `inset`,
|
|
616
|
+
`margin` and `translate` are reset on the copy) but not its size —
|
|
617
|
+
`width: calc(50% - 2 * var(--gap))` then means half of the carrier instead of
|
|
618
|
+
half of the board, and the piece is carried at the wrong size. Put what a piece
|
|
619
|
+
LOOKS like in a class and leave only which place it is inline (two custom
|
|
620
|
+
properties will do), then let it fill its carrier:
|
|
612
621
|
|
|
613
622
|
```css
|
|
614
623
|
[navi-drag-clone-wrapper] .piece {
|
|
615
|
-
position: static;
|
|
616
624
|
width: 100%;
|
|
617
625
|
height: 100%;
|
|
618
|
-
translate: none;
|
|
619
626
|
}
|
|
620
627
|
```
|
|
621
628
|
|
|
@@ -654,8 +661,8 @@ from for as long as the question is open — and if the answer refuses, the copy
|
|
|
654
661
|
back to it.
|
|
655
662
|
|
|
656
663
|
`data-drag-axis` says which axes the drag walks, and its default is not the same
|
|
657
|
-
for every outcome: `reorder` alone walks the list (`y
|
|
658
|
-
|
|
664
|
+
for every outcome: `reorder` alone walks the list (`y`; a list that runs sideways
|
|
665
|
+
says `data-drag-axis="x"`), while a `move` goes wherever it is put, a `land` wherever the board has
|
|
659
666
|
places, a `toss` wherever it was thrown and a `leave` out by whichever edge (`xy`).
|
|
660
667
|
`data-drag-delay`,
|
|
661
668
|
`data-drag-slop`, `data-drag-threshold` tune when the press becomes a grab.
|
|
@@ -728,6 +735,9 @@ leaves the paint alone:
|
|
|
728
735
|
import { createDragGestureController, dragAfterIntent } from "@jsenv/navi";
|
|
729
736
|
|
|
730
737
|
const onPointerDown = (pointerDownEvent) => {
|
|
738
|
+
// Read now: the callback below runs once the intent shows, after dispatch,
|
|
739
|
+
// when `currentTarget` is null.
|
|
740
|
+
const element = pointerDownEvent.currentTarget;
|
|
731
741
|
dragAfterIntent(pointerDownEvent, () => {
|
|
732
742
|
const controller = createDragGestureController({
|
|
733
743
|
onDrag: (gestureInfo) => {
|
|
@@ -741,9 +751,7 @@ const onPointerDown = (pointerDownEvent) => {
|
|
|
741
751
|
COURT.PUT.run({ angle: angleSignal.peek() });
|
|
742
752
|
},
|
|
743
753
|
});
|
|
744
|
-
return controller.grabViaPointer(pointerDownEvent, {
|
|
745
|
-
element: pointerDownEvent.currentTarget,
|
|
746
|
-
});
|
|
754
|
+
return controller.grabViaPointer(pointerDownEvent, { element });
|
|
747
755
|
});
|
|
748
756
|
};
|
|
749
757
|
```
|
package/docs/drag_to_travel.md
CHANGED
|
@@ -192,12 +192,13 @@ laptop a horizontal two-finger swipe IS the back-navigation gesture, and a
|
|
|
192
192
|
region that neither takes it nor lets it go is the worst of the three — the page
|
|
193
193
|
rocks and nothing happens.
|
|
194
194
|
|
|
195
|
-
Not the same thing as the
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
document,
|
|
199
|
-
keys. Here nothing is picked up and the page must keep
|
|
200
|
-
scrolling while a screen slides
|
|
195
|
+
Not the same thing as **carrying an object** across the page, although both read
|
|
196
|
+
the pointer through the same loop
|
|
197
|
+
([drag_gesture.js](../../dom/src/interaction/drag/drag_gesture.js)): a carry has
|
|
198
|
+
it lay a backdrop over the document, make everything else `inert`, take the
|
|
199
|
+
focus and block the scroll keys. Here nothing is picked up and the page must keep
|
|
200
|
+
its focus and its scrolling while a screen slides, so the travel asks it for none
|
|
201
|
+
of that (`documentInteractions: "manual"`). Same loop, other gesture.
|
|
201
202
|
|
|
202
203
|
## Who owns a gesture
|
|
203
204
|
|
|
@@ -333,8 +334,9 @@ the browser for what those two properties do not cover.
|
|
|
333
334
|
|
|
334
335
|
### What is contained, and what still leaks
|
|
335
336
|
|
|
336
|
-
Containing is only ever read on a **scroll container** — an element
|
|
337
|
-
|
|
337
|
+
Containing is only ever read on a **scroll container** — an element whose
|
|
338
|
+
`overflow` is `hidden`, `auto` or `scroll`, whether or not it has anything to
|
|
339
|
+
scroll (`clip` cuts without making one). Three places
|
|
338
340
|
could carry it: the travelling box, everything inside it, or the scrollers
|
|
339
341
|
themselves. Which of the three works is an engine question, and the answer
|
|
340
342
|
splits in two:
|
|
@@ -365,12 +367,14 @@ them is that an empty `textarea` is contained too — a browser cannot be asked
|
|
|
365
367
|
"only if it scrolls" — so on Blink a wheel over one moves nothing rather than
|
|
366
368
|
the list around it.
|
|
367
369
|
|
|
368
|
-
On Blink this leaves the boxes that **
|
|
369
|
-
asked. Of navi's own: `
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
On Blink this leaves the boxes that are **not scroll containers**, since those
|
|
371
|
+
are never asked. Of navi's own: a `SidePanel` is a `Dialog`, which scrolls
|
|
372
|
+
(`overflow: auto`), and is asked. A `SlideContainer` cuts with `overflow: clip`,
|
|
373
|
+
which is not a scroll container (`hidden` would be one, and would let the slides
|
|
374
|
+
off stage be scrolled to), so it is not asked. A `RouteTravel` box does not clip
|
|
375
|
+
at all and must not be made a scroll container: one there would become the
|
|
376
|
+
nearest one for every `position: sticky` inside the pages it holds. A row marked
|
|
377
|
+
swipeable by `interactions` is not one either.
|
|
374
378
|
|
|
375
379
|
Which is why navi contains what it KNOWS scrolls, in `box.jsx`:
|
|
376
380
|
|
|
@@ -386,7 +390,8 @@ does, and it puts the containment exactly where every engine reads it. A list, a
|
|
|
386
390
|
dialog body, a scrolling panel inside a travelling page are covered on Blink
|
|
387
391
|
again.
|
|
388
392
|
|
|
389
|
-
What still leaks, and only on Blink, and only under a box that
|
|
393
|
+
What still leaks, and only on Blink, and only under a box that is not a scroll
|
|
394
|
+
container: a
|
|
390
395
|
scroller **nobody declared and no tag names** — a bare
|
|
391
396
|
`<div style="overflow: auto">`, a widget from elsewhere. Its leftovers reach the
|
|
392
397
|
page, which is the old symptom in a much smaller corner. Two ways out, per case:
|
|
@@ -447,10 +452,10 @@ The two ends cost differently, and it is worth knowing which one is being felt:
|
|
|
447
452
|
and there is no gap at all.
|
|
448
453
|
|
|
449
454
|
What the browser will not turn around with it is everything ELSE the
|
|
450
|
-
transition carries — see
|
|
451
|
-
|
|
455
|
+
transition carries — see [One gesture that bar cannot
|
|
456
|
+
follow](#one-gesture-that-bar-cannot-follow).
|
|
452
457
|
|
|
453
|
-
|
|
458
|
+
What a travel in hand must never lose:
|
|
454
459
|
|
|
455
460
|
- **a travel being undone is not up for grabs.** Its end is already decided;
|
|
456
461
|
held again mid-revert, its animations never finish, the wait for them never
|
|
@@ -546,13 +551,13 @@ travel had started finishes without anyone. It is invisible with a mouse, which
|
|
|
546
551
|
is why it survives a whole session of desktop testing.
|
|
547
552
|
|
|
548
553
|
So a travel that has become ours refuses the `touchmove` (`preventDefault`), and
|
|
549
|
-
only then — a finger that means to scroll must still scroll.
|
|
550
|
-
hold:
|
|
554
|
+
only then — a finger that means to scroll must still scroll. Three details make
|
|
555
|
+
it hold:
|
|
551
556
|
|
|
552
|
-
- the listener sits on the element the touch LANDED on as well as on the
|
|
557
|
+
- the listener sits on the element the touch LANDED on as well as on the window: a
|
|
553
558
|
touch keeps being dispatched at the node it started on, and a travel may
|
|
554
559
|
replace the DOM under the finger (a page that travels navigates), after which
|
|
555
|
-
that node no longer passes through the
|
|
560
|
+
that node no longer passes through the window on its way anywhere;
|
|
556
561
|
- the pointer is captured on the BOX rather than on what the finger landed on,
|
|
557
562
|
for the same reason: what the caller does may take that target away, and a
|
|
558
563
|
capture whose element leaves the document is a capture the browser drops;
|
|
@@ -659,7 +664,8 @@ aimed decides what that costs:
|
|
|
659
664
|
at half of its time a travel has covered ~80% of its distance, and rewound
|
|
660
665
|
at `-1` the visible way home collapses into the steep end of the curve — a
|
|
661
666
|
snap, not a return. The pictures walk home over how far they visibly are
|
|
662
|
-
from home, at the travel's own pace (`
|
|
667
|
+
from home, at the travel's own pace (`walkPicturesHome` in
|
|
668
|
+
`src/transition/view_transition_revert.js`);
|
|
663
669
|
- both of the above run straight into the compositor traps: the distance is
|
|
664
670
|
computed from the clock through the easing curve (the pseudo-elements'
|
|
665
671
|
animated position cannot be read), and the rate is handed over with
|
package/docs/dynamic_import.md
CHANGED
|
@@ -126,9 +126,9 @@ what asks, not to the render.**
|
|
|
126
126
|
|
|
127
127
|
It starts one render late, and per instance: a component fetched this way
|
|
128
128
|
in three places asks three times (the browser fetches the module once), and
|
|
129
|
-
coming back to it
|
|
130
|
-
from the module map. Reach for it when there is genuinely no state to
|
|
131
|
-
the request off — not to save declaring one.
|
|
129
|
+
coming back to it draws its wait again for the microtask the import takes to
|
|
130
|
+
answer from the module map. Reach for it when there is genuinely no state to
|
|
131
|
+
read the request off — not to save declaring one.
|
|
132
132
|
|
|
133
133
|
Delegating instead (the hook without `loading: true`, under a `<Loading>`
|
|
134
134
|
around the frame) follows the rule for data: **the `<Loading>` goes where the
|
|
@@ -210,8 +210,10 @@ The page or the component reading its code says it where it stands, as
|
|
|
210
210
|
above. A failure left to a boundary — a page reading its code without
|
|
211
211
|
`error: true` — reaches it like a failed read does; a boundary that shows only
|
|
212
212
|
what came from the network and rethrows the rest as a bug recognises an import
|
|
213
|
-
by its action
|
|
214
|
-
|
|
213
|
+
by its action rather than by the browser's message. `error.action` is the
|
|
214
|
+
instance that ran, not the route action itself (a route action hands out the
|
|
215
|
+
instance for its params), and every instance bound from one action carries its
|
|
216
|
+
`callback`: `error.action.callback === GAME_PAGE_CODE.callback`.
|
|
215
217
|
|
|
216
218
|
## Preact's `lazy()` is not this
|
|
217
219
|
|
package/docs/error_handling.md
CHANGED
|
@@ -184,12 +184,16 @@ report — window `error`, the jsenv overlay in dev — and that is the right an
|
|
|
184
184
|
for it: nothing had to guess whether someone was waiting.
|
|
185
185
|
|
|
186
186
|
**Some runs have nobody to reject at**, and they say so where they start: one
|
|
187
|
-
started from a signal effect because its params changed, one
|
|
188
|
-
|
|
187
|
+
started from a signal effect because its params changed, one started by the
|
|
188
|
+
render that reads it (`{ run: true }`), one whose failure the control that
|
|
189
|
+
started it already draws, a routing whose result every caller drops.
|
|
189
190
|
They pass through [`runUnwatched()`](../src/action/run_unwatched.js), which takes
|
|
190
|
-
the rejection without hiding the error — it stays in `errorSignal`. That is
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
the rejection without hiding the error — it stays in `errorSignal`. That is
|
|
192
|
+
every place navi swallows one but the prefetch on intent, dropped without a
|
|
193
|
+
word since nothing on screen asked for it and the arrival runs the action again
|
|
194
|
+
([dynamic_import.md](./dynamic_import.md#ahead-of-the-render-intent)). Taking
|
|
195
|
+
it is what obliges that place to ask the next question: did anything display
|
|
196
|
+
this error? (below).
|
|
193
197
|
|
|
194
198
|
Two things must never become a rejection, and do not. **A navigation**: the
|
|
195
199
|
routing's promise is what the browser calls the navigation itself, and rejecting
|
|
@@ -212,24 +216,19 @@ Whatever displays the error sets `error.__handled_by__` (via
|
|
|
212
216
|
one thing and only one: **this error is on screen somewhere.**
|
|
213
217
|
|
|
214
218
|
It is the same mark the jsenv supervisor reads to keep its dev overlay out of
|
|
215
|
-
the way
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// when an error was handled by an ErrorBoundary we will nonetheless emit an error
|
|
221
|
-
// event on the window object. This is to make up for react compatibility in dev mode
|
|
222
|
-
// and thus make the Next.js dev overlay work.
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Without the mark, an app calmly displaying "you are offline" gets a crash
|
|
226
|
-
overlay thrown over it.
|
|
219
|
+
the way: a window `error` or `unhandledrejection` carrying it is not shown. An
|
|
220
|
+
error on screen can still reach window another way — a run started by hand and
|
|
221
|
+
never awaited — and without the mark an app calmly displaying "you are offline"
|
|
222
|
+
gets a crash overlay thrown over it. navi's own report reads the same mark
|
|
223
|
+
(below).
|
|
227
224
|
|
|
228
225
|
Everything navi ships already marks: `<ErrorBoundary>` when it renders its
|
|
229
|
-
fallback, `<ActionRenderer>`
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
fallback, `<ActionRenderer>` when it renders its error branch,
|
|
227
|
+
`useAsyncData({ error: true })` when it hands the error to the component, and a
|
|
228
|
+
run given an `onError` — asking for the error is taking it. Every control with
|
|
229
|
+
an `action` runs it that way, so the control claims the failure whatever it
|
|
230
|
+
then shows: a callout, a boundary (`actionErrorEffect="throw"`), or nothing
|
|
231
|
+
(`"none"`, a mapping returning nothing).
|
|
233
232
|
|
|
234
233
|
## Taken by nobody: the report
|
|
235
234
|
|
|
@@ -240,10 +239,9 @@ code that produced it.
|
|
|
240
239
|
**Being read is enough to call the report off**, displayed or not. Once a render
|
|
241
240
|
has the error, everything that can happen next is already covered without this
|
|
242
241
|
module: it is displayed (and marked), or it is thrown — and a thrown error either
|
|
243
|
-
finds a boundary that displays it, or reaches window on its own, since
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this module cannot see which of those happened.
|
|
242
|
+
finds a boundary that displays it, or reaches window on its own, since preact
|
|
243
|
+
re-throws what no boundary handled. Reporting it here too would be a second
|
|
244
|
+
voice, and the wrong one: this module cannot see which of those happened.
|
|
247
245
|
|
|
248
246
|
**And a render stopped in the middle read nothing after it.** `useAsyncData`
|
|
249
247
|
delegates a failure by throwing it out of the render — that is how the error
|
|
@@ -287,8 +285,9 @@ easy to miss when writing another one:
|
|
|
287
285
|
|
|
288
286
|
- **Mark only what you actually render.** Marking on catch, before knowing
|
|
289
287
|
whether anything is displayed, turns a boundary into a bug swallower: a
|
|
290
|
-
`TypeError`
|
|
291
|
-
|
|
288
|
+
`TypeError` it then re-throws reaches window marked as on screen, so the
|
|
289
|
+
overlay that would have pointed at it stays out of the way — a blank page AND
|
|
290
|
+
a silent one. navi's boundary marks
|
|
292
291
|
just before rendering its fallback, and re-throws untouched when it has no
|
|
293
292
|
fallback — nothing to display means nothing handled.
|
|
294
293
|
- **Reset on the document URL, not only on the rerun.** Re-running the failed
|
package/docs/field_validation.md
CHANGED
|
@@ -25,7 +25,8 @@ a validity rule, and the constraint is only its browser-side caller.
|
|
|
25
25
|
|
|
26
26
|
## Constraints
|
|
27
27
|
|
|
28
|
-
A constraint is `{ name, check(field) }
|
|
28
|
+
A constraint is `{ name, check(field) }` — a bare `check` function is accepted
|
|
29
|
+
too. `check` returns `null` when the value
|
|
29
30
|
passes, or the message to show — a string, or `{ message, target }` when the
|
|
30
31
|
callout belongs on another element than the control itself.
|
|
31
32
|
|
|
@@ -44,7 +45,7 @@ plain HTML.
|
|
|
44
45
|
| ------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
45
46
|
| `required`, `pattern` | same | what the platform's attributes mean |
|
|
46
47
|
| `minLength`, `maxLength` | same | a string too short or too long |
|
|
47
|
-
| `min`, `max`, `step` | same | a number, date, time or duration out of range
|
|
48
|
+
| `min`, `max`, `step` | same | a number, date, time or duration out of range, or off its step |
|
|
48
49
|
| `singleSpace` | `data-single-space` | a leading or trailing space, two in a row |
|
|
49
50
|
| `displayable` | `data-displayable` | zalgo, a value showing nothing, blank lines in series, a joiner joining nothing |
|
|
50
51
|
| `maxStackedMarks` | `data-max-stacked-marks` | (parameter of `displayable`) |
|
|
@@ -64,9 +65,34 @@ A boolean switch (`displayable`, `singleSpace`, `noEmoji`) passed as `false` is
|
|
|
64
65
|
off, so `noEmoji={settings.strictNames}` says what it looks like it says. The
|
|
65
66
|
others carry a value — a count, a selector — and are off by being absent.
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
**`singleSpace="autoFix"` corrects the value instead of refusing it.** A text
|
|
69
|
+
field ends with a space more often than anyone means it to — a pause after a
|
|
70
|
+
word, a mobile keyboard after a suggestion, a paste — and a refusal then asks
|
|
71
|
+
the person to find and delete a character they cannot see. With `"autoFix"`,
|
|
72
|
+
the leading and trailing spaces are removed and a run of spaces becomes one:
|
|
73
|
+
|
|
74
|
+
```jsx
|
|
75
|
+
<Input name="title" singleSpace="autoFix" />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **At commit, never while typing.** The correction runs when the field is
|
|
79
|
+
left, and before an action reads the value. Correcting on each keystroke would
|
|
80
|
+
eat the space the person is about to follow with a word, so an action run
|
|
81
|
+
from typing (a search, debounced or not) reads the value as typed.
|
|
82
|
+
- **Nothing to say.** The correction is validity's own `SINGLE_SPACE_RULE`, so
|
|
83
|
+
the value always passes the rule: no callout, and a server re-checking with
|
|
84
|
+
`singleSpace: true` accepts what it receives.
|
|
85
|
+
- **It lands everywhere the value lives**: the field, its ui state, the bound
|
|
86
|
+
`signal`. A `disabled` or `readOnly` field is left alone, since its value is
|
|
87
|
+
the app's and not the person's.
|
|
88
|
+
- **`singleSpace` is the only constraint that offers it.** A correction is worth
|
|
89
|
+
it only where it cannot change what the person meant; a `maxLength` silently
|
|
90
|
+
cutting a text would.
|
|
91
|
+
|
|
92
|
+
Most constraints take a `<name>Message` prop to replace their sentence for one
|
|
93
|
+
field (`requiredMessage`, `singleSpaceMessage`, `oneOfMessage`…; `step` and the
|
|
94
|
+
time span constraints have none). To change it everywhere, override the i18n
|
|
95
|
+
key instead — see below.
|
|
70
96
|
|
|
71
97
|
Two props sit beside them and belong to the browser alone, because they act
|
|
72
98
|
before there is a value to validate: `charGuard` blocks a keystroke that is not
|
|
@@ -78,7 +104,10 @@ silently swallowing it.
|
|
|
78
104
|
field can refuse the keystroke with the same knowledge the value is checked
|
|
79
105
|
against: `charGuard="tel"`, `charGuard="slug"`, `charGuard="noEmoji"`. Refusing
|
|
80
106
|
the keystroke and refusing the value are different jobs — the pair
|
|
81
|
-
`maxLengthGuard`/`maxLength` is the same split — and a
|
|
107
|
+
`maxLengthGuard`/`maxLength` is the same split — and a guard brings the value
|
|
108
|
+
check along: `maxLengthGuard` is checked at submit as `maxLength`, `charGuard`
|
|
109
|
+
sets an `Input`'s `pattern` from its class unless one is given. `maxLength`
|
|
110
|
+
alone is the submit-only half.
|
|
82
111
|
|
|
83
112
|
**A guard answers for the gesture, never for what the field already holds.** A
|
|
84
113
|
value can arrive already outside the class or already too long — a
|
package/docs/interactions.md
CHANGED
|
@@ -104,20 +104,21 @@ which the carrying interactions answer with).
|
|
|
104
104
|
Two holds on one press — a `longpress` declared on something inside an
|
|
105
105
|
element that declares one too — are answered by the nearer one, the way a
|
|
106
106
|
click is the innermost target's: the inner hold fires, the outer wait is given
|
|
107
|
-
up.
|
|
108
|
-
first.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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.
|
|
112
117
|
|
|
113
118
|
There is no `dblclick` in that table, on purpose: the browser fires it for a
|
|
114
119
|
mouse and never for a finger, so an element declaring it would answer half the
|
|
115
120
|
hands that reach it. `double_click` is the same gesture counted from the
|
|
116
|
-
pointer — see [Twice, whichever hand it is](#twice-whichever-hand-it-is).
|
|
117
|
-
outer hold is a declaration that can never fire, and a picker opened by
|
|
118
|
-
`openOn="longpress"` around a card that already holds one is exactly where it
|
|
119
|
-
happens. One hold has one meaning — two things on one card want two gestures
|
|
120
|
-
(a hold and a click), not two holds.
|
|
121
|
+
pointer — see [Twice, whichever hand it is](#twice-whichever-hand-it-is).
|
|
121
122
|
|
|
122
123
|
A name nothing knows how to detect produces a dev warning naming the detectors
|
|
123
124
|
that exist. The carrying family and the two surface streams each have a file of
|
|
@@ -148,8 +149,9 @@ ancestor can also listen for one, and `preventDefault()` on it means "not this
|
|
|
148
149
|
time".
|
|
149
150
|
|
|
150
151
|
The lower-level event the interaction was read from is reachable too:
|
|
151
|
-
`interactionEvent.detail.event` is the `pointerdown` a
|
|
152
|
-
of
|
|
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.
|
|
153
155
|
|
|
154
156
|
## Reaching the control
|
|
155
157
|
|
|
@@ -157,8 +159,8 @@ Everything goes through the interaction gate of the **nearest control** — itse
|
|
|
157
159
|
an ancestor, or the one control it wraps, in that order. So a disabled, read-only
|
|
158
160
|
or busy control answers a swipe the way it answers a click: it says why, where the
|
|
159
161
|
interaction happened, and nothing runs. A `Box` with no control anywhere near it
|
|
160
|
-
still answers a callback; only `"request_action"
|
|
161
|
-
in dev.
|
|
162
|
+
still answers a callback; only the two requests (`"request_action"`,
|
|
163
|
+
`"request_ui_action"`) have nothing to ask, and say so in dev.
|
|
162
164
|
|
|
163
165
|
A `Box` that lays out **several** controls — a row of badges, a toolbar — belongs
|
|
164
166
|
to none of them: its interactions are its own, answered with no gate, exactly as
|
|
@@ -544,10 +546,11 @@ returns how to undo whatever it did. Listeners, attributes, anything: it is a
|
|
|
544
546
|
plain setup and teardown, so a detector counts what it needs in its own closure
|
|
545
547
|
and nothing has to hold state on its behalf.
|
|
546
548
|
|
|
547
|
-
`claims`
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
which of them were
|
|
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.
|
|
551
554
|
|
|
552
555
|
`trigger(type, originalEvent, detail)` says the interaction happened. Called with
|
|
553
556
|
a single event — `trigger(event)` — the type is the detector's own, which only
|
|
@@ -577,17 +580,18 @@ container above it does not take the gesture:
|
|
|
577
580
|
under the same finger reads it before answering: a list reordered vertically
|
|
578
581
|
inside a row of slides swiped sideways leaves the sideways gesture alone, and a
|
|
579
582
|
piece carried both ways inside a bottom sheet takes the press whole. Nothing to
|
|
580
|
-
wire — see `docs/drag_to_travel.md`. A `Dialog` docked to
|
|
583
|
+
wire — see `docs/drag_to_travel.md`. A `Dialog` docked to an edge goes
|
|
581
584
|
further and reads the press only on its header (plus anything carrying
|
|
582
585
|
`data-swipe-grip`), so its body is free whatever is in it.
|
|
583
586
|
- **A hold does not take the context menu.** Declaring `longpress` says what a
|
|
584
587
|
held finger does; a right click comes from the other button and keeps opening
|
|
585
588
|
the browser's menu. Declare `contextmenu` beside it to make the right click do
|
|
586
589
|
the same thing. (A held _finger_ is the system's own context-menu gesture, and
|
|
587
|
-
that one is refused
|
|
590
|
+
that one is refused for the length of the press, not just of the wait.)
|
|
588
591
|
- **Where the press already means something, text is not selected.** An element
|
|
589
|
-
declaring `longpress
|
|
590
|
-
`
|
|
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
|
|
591
595
|
that same press with a selection of its own — the word under the thumb, blue,
|
|
592
596
|
with handles — and nothing takes it back once the press is over. For every
|
|
593
597
|
pointer, mouse included, which cannot finish a selection begun where the press
|
|
@@ -621,6 +625,7 @@ container above it does not take the gesture:
|
|
|
621
625
|
- `src/control/interaction/interaction_press.js` — swipes, holds and the two
|
|
622
626
|
taps, and what a swipe writes on the element.
|
|
623
627
|
- `src/control/interaction/interaction_keyboard.js`,
|
|
624
|
-
`interaction_native.js` — the
|
|
628
|
+
`interaction_native.js` — shortcuts, and the browser's own events. The
|
|
629
|
+
carrying and surface detectors are referenced from their own docs.
|
|
625
630
|
- `src/control/demos/38_interactions_demo.html` — every case above, plus a
|
|
626
631
|
mailbox, a board, a surface, and a custom gesture registered from the page.
|
package/docs/list_action.md
CHANGED
|
@@ -29,7 +29,8 @@ refuses in the meantime, which is what you want when the rows are alternatives.
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
A press on another row while it runs is refused and says so, in the selection's
|
|
32
|
-
own words ("
|
|
32
|
+
own words ("le choix est en cours d'enregistrement", "la sélection…" in a
|
|
33
|
+
`multiple` list) rather than in a row's.
|
|
33
34
|
|
|
34
35
|
## On a row's control: that row waits alone
|
|
35
36
|
|
|
@@ -38,8 +39,9 @@ part of it: it holds no action, nothing about it is busy, and the other rows
|
|
|
38
39
|
stay live. Two rows can be in flight at once.
|
|
39
40
|
|
|
40
41
|
```jsx
|
|
41
|
-
<List selectable multiple>
|
|
42
|
+
<List id="the_list" selectable multiple>
|
|
42
43
|
<List.Item
|
|
44
|
+
id={rowId}
|
|
43
45
|
selectable
|
|
44
46
|
value={person}
|
|
45
47
|
readOnly={pending}
|
|
@@ -60,7 +62,7 @@ stay live. Two rows can be in flight at once.
|
|
|
60
62
|
|
|
61
63
|
The command is what marks the row, and it runs **only if the action succeeded**
|
|
62
64
|
— a button given both runs its action first and lets its command follow (see
|
|
63
|
-
[actions.md](./actions.md)). So a refused invitation leaves the row unmarked
|
|
65
|
+
[actions.md](./actions.md#a-press-that-opens-something-and-waits-for-the-answer)). So a refused invitation leaves the row unmarked
|
|
64
66
|
without anything to undo.
|
|
65
67
|
|
|
66
68
|
`selectableArea="manual"` is about the row's own surface, not about the button:
|
|
@@ -149,8 +151,9 @@ nothing about a row's own action stops someone from starting one on every row it
|
|
|
149
151
|
draws — a dozen requests in flight because the list happened to be long.
|
|
150
152
|
|
|
151
153
|
So a list allows **four runs at once by default**. While that many are out,
|
|
152
|
-
every
|
|
153
|
-
the next press is possible again the moment one comes
|
|
154
|
+
every control in it that would start another run goes read-only and says how
|
|
155
|
+
many it is waiting on; the next press is possible again the moment one comes
|
|
156
|
+
back.
|
|
154
157
|
|
|
155
158
|
```jsx
|
|
156
159
|
<List parallelGuard={2}> // stricter
|