@jsenv/navi 0.29.387 → 0.29.389
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 +556 -392
- package/dist/dev/jsenv_navi.js.map +81 -81
- package/dist/dev/jsenv_navi_side_effects.js +87 -78
- package/dist/dev/jsenv_navi_side_effects.js.map +4 -4
- package/dist/jsenv_navi.js +551 -387
- package/dist/jsenv_navi.js.map +81 -81
- package/dist/jsenv_navi_side_effects.js +87 -78
- package/dist/jsenv_navi_side_effects.js.map +4 -4
- 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 +15 -10
- package/docs/drag_interactions.md +49 -41
- package/docs/drag_to_travel.md +32 -24
- package/docs/dynamic_import.md +7 -5
- package/docs/error_handling.md +31 -30
- package/docs/field_validation.md +35 -6
- package/docs/i18n.md +16 -7
- package/docs/interactions.md +28 -23
- package/docs/list_action.md +8 -5
- package/docs/list_refresh.md +8 -1
- package/docs/mobile_layout_pitfalls.md +6 -5
- package/docs/navigation.md +7 -5
- package/docs/network_policy.md +22 -7
- package/docs/pan_zoom.md +16 -9
- 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 +31 -20
- package/docs/route_transitions.md +13 -10
- package/docs/safe_area.md +59 -39
- 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
package/docs/autofocus.md
CHANGED
|
@@ -43,7 +43,8 @@ in order, and stops at the first that leads somewhere focusable:
|
|
|
43
43
|
so a field naming itself wins over the surface around it;
|
|
44
44
|
3. the first focusable element — what one came to do;
|
|
45
45
|
4. the deepest `autoFocus="last-resort"`, the container itself included;
|
|
46
|
-
5. nothing, and the
|
|
46
|
+
5. nothing: the focus stays where it was, and [when the opening places
|
|
47
|
+
nothing](#when-the-opening-places-nothing) says what follows.
|
|
47
48
|
|
|
48
49
|
Step 1 is why reopening a popup comes back to where the user was, rather than to
|
|
49
50
|
what the content asks for on a fresh open.
|
package/docs/badge_list.md
CHANGED
|
@@ -21,9 +21,11 @@ for, and nothing is set up for a case that cannot happen:
|
|
|
21
21
|
|
|
22
22
|
## How badges are counted
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
Once the list has something to decide — a `max`, a `fallback`, `maxLines` — a
|
|
25
|
+
`Badge` inside it does not render itself: it hands its props to the list
|
|
26
|
+
through `BadgeListContext` and renders nothing (a plain list leaves its badges
|
|
27
|
+
to render themselves). Badges register in tree order, so by the time the list
|
|
28
|
+
gets to its own content it holds them all
|
|
27
29
|
and knows how many there are before deciding what to show — without walking
|
|
28
30
|
children vnodes, and without rendering a badge it then has to take back.
|
|
29
31
|
|
package/docs/control_group.md
CHANGED
|
@@ -31,9 +31,10 @@ Stacked, one setting per row:
|
|
|
31
31
|
</Group>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
`row` (
|
|
35
|
-
vertically) is the only prop that changes the
|
|
36
|
-
read off the members themselves.
|
|
34
|
+
`row` (or `vertical`, the same prop — each member is a row, so they stack
|
|
35
|
+
vertically and meet along horizontal seams) is the only prop that changes the
|
|
36
|
+
arrangement; everything else is read off the members themselves. Say it rather
|
|
37
|
+
than `flex="y"`: the seams follow `row`, not the layout.
|
|
37
38
|
|
|
38
39
|
Live examples: `src/control/demos/15_group_demo.html`.
|
|
39
40
|
|
package/docs/control_object.md
CHANGED
|
@@ -36,7 +36,7 @@ It is also the brick composite controls are built from:
|
|
|
36
36
|
with a value of your own — two wheels that are one number of minutes, three
|
|
37
37
|
fields that are one date. A group with them takes and hands back a single
|
|
38
38
|
value, so it can be driven by one `value`/`signal` like any other control. It is
|
|
39
|
-
what `
|
|
39
|
+
what `TimeSpin` is made of, and the same mechanism `InputDuration`, `SpinGroup`
|
|
40
40
|
and `WheelGroup` use for their own members.
|
|
41
41
|
|
|
42
42
|
## `<Form>`: the shape, plus a send
|
package/docs/control_value.md
CHANGED
|
@@ -115,9 +115,10 @@ is a value being offered to that control. It is not an action, and it is not a
|
|
|
115
115
|
signal to write by hand:
|
|
116
116
|
|
|
117
117
|
```jsx
|
|
118
|
-
<
|
|
119
|
-
<
|
|
120
|
-
|
|
118
|
+
<WheelGroup id="duration" signal={durationSignal}>
|
|
119
|
+
<Wheel name="hours">…</Wheel>
|
|
120
|
+
<Wheel name="minutes">…</Wheel>
|
|
121
|
+
</WheelGroup>
|
|
121
122
|
|
|
122
123
|
<Button
|
|
123
124
|
command="--navi-update"
|
|
@@ -217,9 +218,9 @@ They are not competing, they answer two different questions:
|
|
|
217
218
|
- `defaultValue` is where the control starts, and where a reset goes back to.
|
|
218
219
|
|
|
219
220
|
```jsx
|
|
220
|
-
// "
|
|
221
|
+
// "which levels" is what my account usually answers, unless this game says
|
|
221
222
|
// otherwise — no `??` to write, and no first render showing the wrong one
|
|
222
|
-
<List selectable signal={gameLevelsSignal} defaultValue={me.levels}>
|
|
223
|
+
<List selectable multiple signal={gameLevelsSignal} defaultValue={me.levels}>
|
|
223
224
|
```
|
|
224
225
|
|
|
225
226
|
An emptied signal (`signal.value = undefined`) puts the control back on its
|
|
@@ -371,8 +372,8 @@ ignored** — on a leaf control as on a group (a selectable list, a checkbox
|
|
|
371
372
|
group) — and navi says so in dev. One owner, whichever half of the binding you
|
|
372
373
|
look at.
|
|
373
374
|
|
|
374
|
-
Replacing `value` with `signal` also means dropping the `uiAction` that
|
|
375
|
-
|
|
375
|
+
Replacing `value` with `signal` also means dropping the `uiAction` that wrote
|
|
376
|
+
the signal by hand — writing it is exactly what the binding does. Keep
|
|
376
377
|
`uiAction` only for what is not "remember the value": logging, a side effect,
|
|
377
378
|
something else moving with it.
|
|
378
379
|
|
|
@@ -418,10 +419,8 @@ Reference: `src/control/picker/preset/spin_time.jsx`,
|
|
|
418
419
|
## See also
|
|
419
420
|
|
|
420
421
|
- [state_binding.md](./state_binding.md) — the same rule beyond controls:
|
|
421
|
-
|
|
422
422
|
a `SlideContainer`'s area, a popup being open, the position in the app — all
|
|
423
423
|
bound rather than copied back by a callback
|
|
424
|
-
|
|
425
424
|
- [form_changed.md](./form_changed.md) — what a form makes of each of these:
|
|
426
425
|
which fields it counts as already answered, and when it sends nothing
|
|
427
426
|
- [control_object.md](./control_object.md) — several controls reading as one
|
package/docs/css_architecture.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
Navi components are styled through a combination of CSS custom properties (variables) and scoped CSS rules. The architecture is designed so that:
|
|
23
23
|
|
|
24
24
|
1. **Navi wins by default** — the rules that paint and lay a component out are not placed inside `@layer`, so they beat any layered global style. The exception is deliberate and always says so in a comment: a rule navi is happy to hand back sits in `@layer navi` next to the defaults — see [The exception](#the-exception-a-rule-navi-offers-back).
|
|
25
|
-
2. **Defaults are easy to override** — default values for CSS variables are declared inside `@layer navi`, which
|
|
25
|
+
2. **Defaults are easy to override** — default values for CSS variables are declared inside `@layer navi`, which any unlayered rule on the same element beats whatever its specificity, making them trivially overridable from outside.
|
|
26
26
|
3. **The preferred override surface is component props** — props translate to inline styles or data attributes, not class names.
|
|
27
27
|
|
|
28
28
|
---
|
|
@@ -131,13 +131,15 @@ element.setAttribute(SWIPE_AXES_ATTRIBUTE, "x");
|
|
|
131
131
|
|
|
132
132
|
### Beware when moving a shared sheet out
|
|
133
133
|
|
|
134
|
-
Splitting a chunk out of a template changes **cascade order**: the
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
Splitting a chunk out of a template changes **cascade order**: in the template
|
|
135
|
+
it sits where it was written, while an extracted sheet is adopted by the install
|
|
136
|
+
call, which runs before the component's own assignment — so its rules come
|
|
137
|
+
_before_ the component's sheet. That matters only when the two carry
|
|
137
138
|
the same selector at the same specificity and in the same layer — check for that
|
|
138
139
|
before splitting. In navi, `popup_css.js` and `surface_text_css.js` overlap
|
|
139
140
|
`.navi_dialog` and `.navi_popover` with dialog's and popover's own css, which is
|
|
140
|
-
why
|
|
141
|
+
why dialog.jsx and popover.jsx interpolate them, and mark those two templates
|
|
142
|
+
`jsenv-css-opaque`.
|
|
141
143
|
|
|
142
144
|
### A sheet on the far side of a split
|
|
143
145
|
|
|
@@ -182,15 +184,15 @@ lower it. That is the strongest reason not to write one.
|
|
|
182
184
|
@layer navi {
|
|
183
185
|
/* CSS variable defaults — and, when navi says so, a rule it offers back */
|
|
184
186
|
.navi_button {
|
|
185
|
-
--button-
|
|
186
|
-
--button-
|
|
187
|
+
--button-font-size: var(--navi-control-font-size);
|
|
188
|
+
--button-border-radius: var(--navi-control-border-radius);
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
/* The rules that paint and lay out — outside any layer */
|
|
191
193
|
.navi_button {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
font-size: var(--button-font-size);
|
|
195
|
+
border-radius: var(--button-border-radius);
|
|
194
196
|
}
|
|
195
197
|
```
|
|
196
198
|
|
|
@@ -203,7 +205,7 @@ This means an app can override a Navi default from its own unlayered CSS, withou
|
|
|
203
205
|
```css
|
|
204
206
|
/* App CSS — no layer needed, automatically wins over @layer navi */
|
|
205
207
|
.navi_button {
|
|
206
|
-
--button-
|
|
208
|
+
--button-font-size: 16px;
|
|
207
209
|
}
|
|
208
210
|
```
|
|
209
211
|
|
|
@@ -257,10 +259,10 @@ Props are the primary way to customize appearance. They translate to inline `sty
|
|
|
257
259
|
|
|
258
260
|
```jsx
|
|
259
261
|
// Size and color via props
|
|
260
|
-
<Button size="l"
|
|
262
|
+
<Button size="l" color="secondary" />
|
|
261
263
|
|
|
262
264
|
// Custom CSS variable via style prop
|
|
263
|
-
<Button style={{ "--button-
|
|
265
|
+
<Button style={{ "--button-font-size": "18px" }} />
|
|
264
266
|
```
|
|
265
267
|
|
|
266
268
|
#### Variants set defaults, never resolved values
|
|
@@ -470,8 +472,8 @@ and a paper could no longer say anything.
|
|
|
470
472
|
A dialog lives in the browser's top layer and is calibrated on the viewport, so
|
|
471
473
|
an app that is a column in a wide window states its own screen once, on `:root`,
|
|
472
474
|
with `--navi-app-max-width` — and never caps popups through
|
|
473
|
-
`--dialog-max-width`, a `--component-*` token
|
|
474
|
-
|
|
475
|
+
`--dialog-max-width`, a `--component-*` token every dialog resets on itself and
|
|
476
|
+
a `maxWidth` prop writes inline. The whole of it, placement included, is in
|
|
475
477
|
[safe_area.md](./safe_area.md#an-app-that-is-narrower-than-the-window).
|
|
476
478
|
|
|
477
479
|
### 3. Direct rule override (avoid unless necessary)
|
package/docs/dialog_shape.md
CHANGED
|
@@ -16,7 +16,8 @@ Where a `Dialog` sits and how big it gets. What happens _inside_ it (scrolling,
|
|
|
16
16
|
|
|
17
17
|
## A dialog is sized by its content, never by a `width`
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
`Dialog` has no `width` prop of its own (one handed to it is `Box`'s, still
|
|
20
|
+
under the ceiling below), and that is the whole design: a dialog is a surface
|
|
20
21
|
laid over the app, and what it holds is what knows how wide it should be. What
|
|
21
22
|
a caller states are **bounds** — a floor under a dialog too narrow for its
|
|
22
23
|
content, a ceiling over one that would sprawl:
|
|
@@ -50,8 +51,9 @@ Two consequences worth knowing before writing CSS of your own:
|
|
|
50
51
|
(`--navi-app-width`/`--navi-app-height`, which track the visual viewport), so
|
|
51
52
|
the browser reflows the dialog as the keyboard opens. Nothing to wire.
|
|
52
53
|
- "the container" is the **app's own screen** for `layer="top"` — the visual
|
|
53
|
-
viewport
|
|
54
|
-
|
|
54
|
+
viewport minus the app's bands, whether `--navi-app-max-width` centers them
|
|
55
|
+
or the app writes `--navi-app-inset-*` itself (see
|
|
56
|
+
[`safe_area.md`](./safe_area.md)) — and the positioned ancestor for
|
|
55
57
|
`layer="local"`.
|
|
56
58
|
|
|
57
59
|
## One dialog, two shapes
|
|
@@ -185,9 +187,11 @@ its container is also what its size ceiling is computed from. Writing them
|
|
|
185
187
|
separately is how a dialog ends up flush on one side and inset on the other.
|
|
186
188
|
|
|
187
189
|
It defaults to a share of whatever holds the dialog (`3appw` for `layer="top"`,
|
|
188
|
-
`3cqw` for `layer="local"`) and accepts a
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
`3cqw` for `layer="local"`) and accepts a number of pixels, a viewport length —
|
|
191
|
+
`appw`/`apph` being the app's own screen, `vvw`/`vvh` the visual viewport, which
|
|
192
|
+
shrinks when the keyboard opens — or a container length (`cqw`/`cqh`). A
|
|
193
|
+
spacing token (`"s"`, `"m"`…) cannot be resolved to pixels here: navi warns
|
|
194
|
+
and places the dialog flush.
|
|
191
195
|
Pass `0` for something meant to sit flush (a side panel), and note that docking
|
|
192
196
|
already passes `0` for you.
|
|
193
197
|
|
|
@@ -241,11 +245,12 @@ exposes both sets of bounds under prefixed names, and they mean exactly what
|
|
|
241
245
|
they mean on `Dialog` itself:
|
|
242
246
|
|
|
243
247
|
`dialogMinWidth`, `dialogMinHeight`, `dialogMaxWidth`, `dialogMaxHeight`,
|
|
244
|
-
`dialogExpand`, `dialogExpandX`, `dialogExpandY`, `
|
|
245
|
-
`marginWithContainer` — plus `popoverMaxHeight` and
|
|
246
|
-
the other shape.
|
|
248
|
+
`dialogExpand`, `dialogExpandX`, `dialogExpandY`, `dialogSizeFromAnchor`,
|
|
249
|
+
`dockedOnSmallTouchScreen`, `marginWithContainer` — plus `popoverMaxHeight` and
|
|
250
|
+
`popupWidthFitContent` for the other shape.
|
|
247
251
|
|
|
248
|
-
`SplitButton` forwards the same set to the picker
|
|
252
|
+
`SplitButton` forwards the same set (minus `dialogSizeFromAnchor`) to the picker
|
|
253
|
+
it wraps. Anything not in
|
|
249
254
|
that set lands on the split button's own box instead — so a prop that seems to
|
|
250
255
|
do nothing to the menu is worth checking against that list first (see
|
|
251
256
|
`POPUP_PROP_SET` in `src/control/input/split_button.jsx`).
|
|
@@ -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
|
|
@@ -368,11 +373,11 @@ copy where the hand put it, over the thing appearing there.
|
|
|
368
373
|
#### Naming what travels
|
|
369
374
|
|
|
370
375
|
**The copy is already named, and it is the copy that does the visible travel.**
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
+
It answers to `navi-drag-clone` (the wrapper carrying it paints nothing and takes
|
|
377
|
+
no name), and `syncCloneWithDropTarget` moves it onto the destination inside the
|
|
378
|
+
callback — so the piece the hand let go of slides to its place whether the
|
|
379
|
+
application names anything or not. What is left to name is the OTHER one: the
|
|
380
|
+
piece that was standing there and has to go the other way.
|
|
376
381
|
|
|
377
382
|
**And the original is hidden for the whole landing**, not only for the drag: it
|
|
378
383
|
wears `navi-drag-clone-source` (`visibility: hidden`) until the promise returned
|
|
@@ -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
|
|
|
@@ -324,7 +325,9 @@ page moved a little, and it looked wrong":
|
|
|
324
325
|
rather than the thing that does the work;
|
|
325
326
|
- **the selection** a drag paints across the text it crosses: `user-select:
|
|
326
327
|
none`, but only once the press has become a travel — a press on text IS how
|
|
327
|
-
one selects it, and nothing has said otherwise yet.
|
|
328
|
+
one selects it, and nothing has said otherwise yet. What its first pixels
|
|
329
|
+
selected is collapsed at that same moment: it was the beginning of the
|
|
330
|
+
travel, not a selection.
|
|
328
331
|
|
|
329
332
|
Both are written by the gesture itself (`data-drag-travel-gesture` and
|
|
330
333
|
`data-drag-travel-walking` on `:root`), so a page that bounces the rest of the
|
|
@@ -333,8 +336,9 @@ the browser for what those two properties do not cover.
|
|
|
333
336
|
|
|
334
337
|
### What is contained, and what still leaks
|
|
335
338
|
|
|
336
|
-
Containing is only ever read on a **scroll container** — an element
|
|
337
|
-
|
|
339
|
+
Containing is only ever read on a **scroll container** — an element whose
|
|
340
|
+
`overflow` is `hidden`, `auto` or `scroll`, whether or not it has anything to
|
|
341
|
+
scroll (`clip` cuts without making one). Three places
|
|
338
342
|
could carry it: the travelling box, everything inside it, or the scrollers
|
|
339
343
|
themselves. Which of the three works is an engine question, and the answer
|
|
340
344
|
splits in two:
|
|
@@ -365,12 +369,14 @@ them is that an empty `textarea` is contained too — a browser cannot be asked
|
|
|
365
369
|
"only if it scrolls" — so on Blink a wheel over one moves nothing rather than
|
|
366
370
|
the list around it.
|
|
367
371
|
|
|
368
|
-
On Blink this leaves the boxes that **
|
|
369
|
-
asked. Of navi's own: `
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
On Blink this leaves the boxes that are **not scroll containers**, since those
|
|
373
|
+
are never asked. Of navi's own: a `SidePanel` is a `Dialog`, which scrolls
|
|
374
|
+
(`overflow: auto`), and is asked. A `SlideContainer` cuts with `overflow: clip`,
|
|
375
|
+
which is not a scroll container (`hidden` would be one, and would let the slides
|
|
376
|
+
off stage be scrolled to), so it is not asked. A `RouteTravel` box does not clip
|
|
377
|
+
at all and must not be made a scroll container: one there would become the
|
|
378
|
+
nearest one for every `position: sticky` inside the pages it holds. A row marked
|
|
379
|
+
swipeable by `interactions` is not one either.
|
|
374
380
|
|
|
375
381
|
Which is why navi contains what it KNOWS scrolls, in `box.jsx`:
|
|
376
382
|
|
|
@@ -386,7 +392,8 @@ does, and it puts the containment exactly where every engine reads it. A list, a
|
|
|
386
392
|
dialog body, a scrolling panel inside a travelling page are covered on Blink
|
|
387
393
|
again.
|
|
388
394
|
|
|
389
|
-
What still leaks, and only on Blink, and only under a box that
|
|
395
|
+
What still leaks, and only on Blink, and only under a box that is not a scroll
|
|
396
|
+
container: a
|
|
390
397
|
scroller **nobody declared and no tag names** — a bare
|
|
391
398
|
`<div style="overflow: auto">`, a widget from elsewhere. Its leftovers reach the
|
|
392
399
|
page, which is the old symptom in a much smaller corner. Two ways out, per case:
|
|
@@ -447,10 +454,10 @@ The two ends cost differently, and it is worth knowing which one is being felt:
|
|
|
447
454
|
and there is no gap at all.
|
|
448
455
|
|
|
449
456
|
What the browser will not turn around with it is everything ELSE the
|
|
450
|
-
transition carries — see
|
|
451
|
-
|
|
457
|
+
transition carries — see [One gesture that bar cannot
|
|
458
|
+
follow](#one-gesture-that-bar-cannot-follow).
|
|
452
459
|
|
|
453
|
-
|
|
460
|
+
What a travel in hand must never lose:
|
|
454
461
|
|
|
455
462
|
- **a travel being undone is not up for grabs.** Its end is already decided;
|
|
456
463
|
held again mid-revert, its animations never finish, the wait for them never
|
|
@@ -546,13 +553,13 @@ travel had started finishes without anyone. It is invisible with a mouse, which
|
|
|
546
553
|
is why it survives a whole session of desktop testing.
|
|
547
554
|
|
|
548
555
|
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:
|
|
556
|
+
only then — a finger that means to scroll must still scroll. Three details make
|
|
557
|
+
it hold:
|
|
551
558
|
|
|
552
|
-
- the listener sits on the element the touch LANDED on as well as on the
|
|
559
|
+
- the listener sits on the element the touch LANDED on as well as on the window: a
|
|
553
560
|
touch keeps being dispatched at the node it started on, and a travel may
|
|
554
561
|
replace the DOM under the finger (a page that travels navigates), after which
|
|
555
|
-
that node no longer passes through the
|
|
562
|
+
that node no longer passes through the window on its way anywhere;
|
|
556
563
|
- the pointer is captured on the BOX rather than on what the finger landed on,
|
|
557
564
|
for the same reason: what the caller does may take that target away, and a
|
|
558
565
|
capture whose element leaves the document is a capture the browser drops;
|
|
@@ -659,7 +666,8 @@ aimed decides what that costs:
|
|
|
659
666
|
at half of its time a travel has covered ~80% of its distance, and rewound
|
|
660
667
|
at `-1` the visible way home collapses into the steep end of the curve — a
|
|
661
668
|
snap, not a return. The pictures walk home over how far they visibly are
|
|
662
|
-
from home, at the travel's own pace (`
|
|
669
|
+
from home, at the travel's own pace (`walkPicturesHome` in
|
|
670
|
+
`src/transition/view_transition_revert.js`);
|
|
663
671
|
- both of the above run straight into the compositor traps: the distance is
|
|
664
672
|
computed from the clock through the easing curve (the pseudo-elements'
|
|
665
673
|
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
|
|