@jsenv/navi 0.29.388 → 0.29.390
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/dist/dev/jsenv_navi.js +415 -181
- package/dist/dev/jsenv_navi.js.map +20 -20
- 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 +414 -180
- package/dist/jsenv_navi.js.map +20 -20
- package/dist/jsenv_navi_side_effects.js +87 -78
- package/dist/jsenv_navi_side_effects.js.map +4 -4
- package/docs/dialog_shape.md +3 -2
- package/docs/drag_interactions.md +13 -5
- package/docs/drag_to_travel.md +3 -1
- package/docs/error_handling.md +5 -3
- package/docs/i18n.md +16 -7
- package/docs/list_refresh.md +5 -4
- package/docs/network_policy.md +12 -4
- package/docs/pan_zoom.md +24 -10
- package/docs/resource.md +53 -5
- package/docs/safe_area.md +28 -24
- package/package.json +2 -2
package/docs/pan_zoom.md
CHANGED
|
@@ -42,12 +42,21 @@ when it lands; the pan stepping back for what is carried across the surface (a
|
|
|
42
42
|
`move`, a handle, a field, a popover — the same list a travelling box steps back
|
|
43
43
|
for); the pinch not beginning as a pan under its first finger; the wheel and the
|
|
44
44
|
pinch writing one `zoom`; the capture, the pointer the browser drops, the click
|
|
45
|
-
the release leaves behind
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
the release leaves behind, and the next tap's click, which Chrome on Android
|
|
46
|
+
drops after a touch drag whose `touchmove`s nobody refused (see
|
|
47
|
+
[mobile_tap_suppression_after_drag.md](./mobile_tap_suppression_after_drag.md)).
|
|
48
|
+
A pointer pans only once it has travelled a few px (`data-drag-threshold`), so a
|
|
49
|
+
tap stays a tap: a `longpress` declared beside `pan` still gets its hold, and a
|
|
50
|
+
`double_click` still gets its two presses. A press that does pan is the
|
|
51
|
+
surface's: a `longpress` waiting on it gives up the moment the pan begins, even
|
|
52
|
+
inside its own slop.
|
|
48
53
|
|
|
49
54
|
Declared alone, `zoom` takes two fingers and the wheel and leaves one pointer to
|
|
50
|
-
whatever else reads it
|
|
55
|
+
whatever else reads it: nothing captures it, waits on it or swallows its click
|
|
56
|
+
until a second finger lands, so a `click` or a `longpress` declared beside `zoom`
|
|
57
|
+
answers it as it would anywhere. From the second finger on, both are the pinch's,
|
|
58
|
+
and a `longpress` waiting on the first gives up. `pan` alone leaves the wheel to
|
|
59
|
+
the page.
|
|
51
60
|
|
|
52
61
|
### A surface that scrolls past: `data-pan-after-hold`
|
|
53
62
|
|
|
@@ -76,6 +85,10 @@ say so. It spends the hold, though: a `longpress` declared beside a `pan` that
|
|
|
76
85
|
waits asks one finger to answer two waits, and the surface's — the shorter one
|
|
77
86
|
— is the one answered.
|
|
78
87
|
|
|
88
|
+
On a surface that only zooms there is no pan to wait for: one finger is the
|
|
89
|
+
page's to scroll, two are the surface's, and `"kept"` (below) has nothing to
|
|
90
|
+
keep.
|
|
91
|
+
|
|
79
92
|
The wait is then asked before every pan, which is right while each finger
|
|
80
93
|
landing there is genuinely ambiguous. Once the user has settled into the plan it
|
|
81
94
|
is not, and asking again is asking three times for one sentence:
|
|
@@ -152,11 +165,11 @@ place `data-pan-after-hold` is.
|
|
|
152
165
|
|
|
153
166
|
### When the surface has the hand: `grab`, `release`, `[data-grabbed]`
|
|
154
167
|
|
|
155
|
-
A surface that is asked for — by a hold,
|
|
156
|
-
has an instant where it becomes the hand's, and nothing on
|
|
157
|
-
hand then moves too early and scrolls the page instead, or
|
|
158
|
-
moment out of doubt. So the surface says it, the same two
|
|
159
|
-
says (`grab`, `release`) and the same attribute:
|
|
168
|
+
A surface that is asked for — by a hold, by the first few pixels of travel, or
|
|
169
|
+
by a second finger — has an instant where it becomes the hand's, and nothing on
|
|
170
|
+
screen says it. The hand then moves too early and scrolls the page instead, or
|
|
171
|
+
waits long past the moment out of doubt. So the surface says it, the same two
|
|
172
|
+
words a carried element says (`grab`, `release`) and the same attribute:
|
|
160
173
|
|
|
161
174
|
```jsx
|
|
162
175
|
<Box
|
|
@@ -180,7 +193,8 @@ says (`grab`, `release`) and the same attribute:
|
|
|
180
193
|
}
|
|
181
194
|
```
|
|
182
195
|
|
|
183
|
-
`[data-grabbed]` is on the element for as long as a hand is on the surface
|
|
196
|
+
`[data-grabbed]` is on the element for as long as a hand is on the surface (on
|
|
197
|
+
one that only zooms, from the second finger landing to the last one lifting), so
|
|
184
198
|
a contour, a veil or a raised shadow needs no listener — which is what this is
|
|
185
199
|
usually for. (`[data-hand-kept]` is the other one, and it outlasts the finger:
|
|
186
200
|
see [`data-pan-after-hold`](#a-surface-that-scrolls-past-data-pan-after-hold).) `grab` and `release` are for the rest: a vibration, a state kept
|
package/docs/resource.md
CHANGED
|
@@ -42,6 +42,7 @@ looks like ([data_states.md](./data_states.md#data-and-loading-are-independent))
|
|
|
42
42
|
- [`.many(propertyName, childResource, restCallbacks)`](#manypropertyname-childresource-restcallbacks)
|
|
43
43
|
- [`.scopedOne(propertyName, { idKey, GET, POST, PUT, PATCH, DELETE })`](#scopedonepropertyname--idkey-get-post-put-patch-delete-)
|
|
44
44
|
- [`.scopedMany(propertyName, { idKey, GET, GET_MANY, POST, PUT, PATCH, DELETE, … })`](#scopedmanypropertyname--idkey-get-get_many-post-put-patch-delete--)
|
|
45
|
+
- [A relation declared on a scoped child](#a-relation-declared-on-a-scoped-child)
|
|
45
46
|
- [When the backend answers a sub-route with the whole parent](#when-the-backend-answers-a-sub-route-with-the-whole-parent)
|
|
46
47
|
- [Relations and autorerun](#relations-and-autorerun)
|
|
47
48
|
- [`withParams()`: a scope with reruns of its own](#withparams-a-scope-with-reruns-of-its-own)
|
|
@@ -256,7 +257,8 @@ These are not guessable — each relationship method has its own shape.
|
|
|
256
257
|
|
|
257
258
|
### `.one(propertyName, childResource, { GET, PUT, DELETE })`
|
|
258
259
|
|
|
259
|
-
|
|
260
|
+
`GET` and `PUT` return the **parent** object with the child nested inside;
|
|
261
|
+
`DELETE` returns the parent id, or `{ id }`:
|
|
260
262
|
|
|
261
263
|
```js
|
|
262
264
|
const USER_SESSION = USER.one("session", SESSION, {
|
|
@@ -358,6 +360,43 @@ const GAME_CANDIDATES = GAME.scopedMany("candidates", {
|
|
|
358
360
|
`idKey` names the child's own key inside its owner (`user_id` above); it defaults
|
|
359
361
|
to `"id"`.
|
|
360
362
|
|
|
363
|
+
### A relation declared on a scoped child
|
|
364
|
+
|
|
365
|
+
A scoped child takes relations of its own — a column pointing at a shared data
|
|
366
|
+
type:
|
|
367
|
+
|
|
368
|
+
```js
|
|
369
|
+
const TABLE_COLUMNS = TABLE.scopedMany("columns", {
|
|
370
|
+
idKey: "name",
|
|
371
|
+
GET_MANY: async ({ id }) => [id, await fetchJson(`/tables/${id}/columns`)],
|
|
372
|
+
// [id, [{ name: "email", dataType: { id: 2, name: "varchar" } }, …]]
|
|
373
|
+
PATCH: async ({ id, name, ...props }) => [
|
|
374
|
+
id,
|
|
375
|
+
await fetchJson(`/tables/${id}/columns/${name}`, {
|
|
376
|
+
method: "PATCH",
|
|
377
|
+
body: props,
|
|
378
|
+
}),
|
|
379
|
+
],
|
|
380
|
+
});
|
|
381
|
+
TABLE_COLUMNS.one("dataType", DATA_TYPE);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Every column gets a reactive `dataType` read from the `DATA_TYPE` store, fed by
|
|
385
|
+
what the scoped child's callbacks embed. The relation itself takes none of the
|
|
386
|
+
callbacks whose result names the column — `.one()`'s `GET`/`PUT`/`DELETE`,
|
|
387
|
+
`.many()`'s `GET_MANY`/`DELETE`/`DELETE_MANY`, and every callback of a
|
|
388
|
+
`.scopedOne()`/`.scopedMany()`, whose `ownerId` would be the column: they name
|
|
389
|
+
it by its `name` alone, and a column exists only inside its table — two tables
|
|
390
|
+
can each have an `email`. Declaring one throws. A column's data type is read and
|
|
391
|
+
changed through `TABLE_COLUMNS`, whose results say which table
|
|
392
|
+
(`[id, { name, dataType }]`); the fields of a data type itself, through
|
|
393
|
+
`DATA_TYPE`.
|
|
394
|
+
|
|
395
|
+
A `.scopedOne()`/`.scopedMany()` chained on a scoped child works the same way,
|
|
396
|
+
and keeps one value per column: `TABLE_COLUMNS.scopedMany("constraints")` gives
|
|
397
|
+
`users.email` and `admins.email` a collection each, fed by the `constraints`
|
|
398
|
+
embedded in each table's columns.
|
|
399
|
+
|
|
361
400
|
## When the backend answers a sub-route with the whole parent
|
|
362
401
|
|
|
363
402
|
This is the common REST shape, and it is the reason `op` dispatch feels
|
|
@@ -404,7 +443,8 @@ parent's own fields change too, either let the parent GET rerun (see below) or
|
|
|
404
443
|
Relationship mutations do **not** invalidate their parent by default. The exact
|
|
405
444
|
rules, verified by `src/state/rest/tests/resource_graph_parent_rerun.test.js`:
|
|
406
445
|
|
|
407
|
-
- `.scopedMany` child **POST** reruns the owner's singular `GET
|
|
446
|
+
- `.scopedMany` child **POST** reruns the owner's singular `GET`, whether it
|
|
447
|
+
goes through the child or through a `withParams()` scope of it — but only when
|
|
408
448
|
the last GET response actually embedded that property. GET_MANY on the parent
|
|
409
449
|
is never rerun by a child POST (a list of parents is not stale because one of
|
|
410
450
|
them gained a child).
|
|
@@ -419,7 +459,8 @@ rules, verified by `src/state/rest/tests/resource_graph_parent_rerun.test.js`:
|
|
|
419
459
|
rerun) by its own `DELETE`, as a root `GET` is; a scoped `GET` is not reset —
|
|
420
460
|
its data simply resolves to nothing once the child is dropped. Override per
|
|
421
461
|
relation with `rerunOn`/`dependencies`, which every relationship method
|
|
422
|
-
accepts
|
|
462
|
+
accepts; what a relation leaves out is its parent resource's (`rerunOn` key
|
|
463
|
+
by key).
|
|
423
464
|
|
|
424
465
|
Splitting a sub-resource out of a parent `PATCH` therefore changes the refresh
|
|
425
466
|
graph: a parent field the parent's own response kept fresh is, once the relation
|
|
@@ -457,11 +498,18 @@ share is the **store**: an item updated through one is the same object in the
|
|
|
457
498
|
others, so the fields of a row change everywhere without a request — only the
|
|
458
499
|
membership of each list is a question its own scope answers.
|
|
459
500
|
|
|
501
|
+
It scopes a relation as well as a resource, and the scope's actions are the
|
|
502
|
+
relation's: `TABLE_COLUMNS.withParams({ withTypes: true }).GET_MANY` keeps the
|
|
503
|
+
`[ownerId, items]` contract of `.scopedMany()` and replaces that table's
|
|
504
|
+
columns.
|
|
505
|
+
|
|
460
506
|
`withParams()` chains, merging the params —
|
|
461
507
|
`USER.withParams({ role: "admin" }).withParams({ gender: "male" })` is
|
|
462
508
|
`USER.withParams({ role: "admin", gender: "male" })`. Its second argument takes
|
|
463
|
-
`rerunOn` and `dependencies` for that scope;
|
|
464
|
-
resource's
|
|
509
|
+
`rerunOn` and `dependencies` for that scope; what it leaves out is the
|
|
510
|
+
resource's — a `rerunOn` key by key, so `{ rerunOn: { GET: ["PATCH"] } }` keeps
|
|
511
|
+
the resource's `GET_MANY` and `GET_RANGE`. Empty params throw: a scope has to be
|
|
512
|
+
about something.
|
|
465
513
|
|
|
466
514
|
It is not the tool for a param the user types. A search word bound with
|
|
467
515
|
`withParams()` would keep its results from refreshing after a write on the
|
package/docs/safe_area.md
CHANGED
|
@@ -75,13 +75,8 @@ So the app states its own screen once, and never names a component:
|
|
|
75
75
|
|
|
76
76
|
The bands fall out of it (centered), `--navi-app-width` follows, and `FixedBar`
|
|
77
77
|
pins itself to the column's edges rather than the glass. An app wanting them
|
|
78
|
-
uneven writes `--navi-app-inset-left` / `-right` directly instead
|
|
79
|
-
|
|
80
|
-
In pixels: popup placement reads this value back from CSS to compute its own
|
|
81
|
-
margins, and a custom property computes to a token stream rather than to a
|
|
82
|
-
length, so `40rem` would arrive there as the string `"40rem"`. A non-px value
|
|
83
|
-
still caps the popup's size (that part is pure CSS) but leaves its margins and
|
|
84
|
-
its placement viewport-sized, and says so in the console.
|
|
78
|
+
uneven writes `--navi-app-inset-left` / `-right` directly instead; everything
|
|
79
|
+
below follows those the same way, popup placement included.
|
|
85
80
|
|
|
86
81
|
Every popup follows: `Dialog`, `Popover`, and everything built on them
|
|
87
82
|
(`Picker`, `Select`…). It is a ceiling and nothing more — on a screen narrower
|
|
@@ -116,15 +111,19 @@ price.
|
|
|
116
111
|
|
|
117
112
|
#### Placement follows the same rectangle
|
|
118
113
|
|
|
119
|
-
|
|
114
|
+
The app's rectangle moves where a popup is placed, not only how big it may
|
|
120
115
|
get. Placement is computed against the visual viewport narrowed to the level-1
|
|
121
|
-
bands: `getAppInsets` (`src/layout/responsive.js`)
|
|
122
|
-
|
|
116
|
+
bands: `getAppInsets` (`src/layout/responsive.js`) reads `--navi-app-inset-*`
|
|
117
|
+
back off the computed style — the bands `--navi-app-max-width` centers and the
|
|
118
|
+
ones an app writes by hand alike, in any length unit — navi hands them to
|
|
119
|
+
`@jsenv/dom` once (`setPlacementViewportInsets`, wired in
|
|
123
120
|
`navi_css_vars.js`), and `pickPositionRelativeTo` reads them on every
|
|
124
|
-
placement.
|
|
125
|
-
`bottom`, `top`, which is what a dialog does nearly
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
placement. With centered bands it is invisible for anything centered on its
|
|
122
|
+
cross axis — `center`, `bottom`, `top`, which is what a dialog does nearly
|
|
123
|
+
always — and with uneven ones it is what centers that dialog on the app column
|
|
124
|
+
rather than on the window. Either way it is what puts anything anchored to an
|
|
125
|
+
edge (a `positionArea` like `bottom-start`, a `SidePanel`) flush against the
|
|
126
|
+
app column's edge rather than the window's.
|
|
128
127
|
`FixedBar` reads the same description through CSS instead: it is pinned to
|
|
129
128
|
`--navi-app-inset-*`, which says where the app's rectangle is in the window
|
|
130
129
|
rather than how wide it may be.
|
|
@@ -156,20 +155,25 @@ Beware of making that container scrollable by accident — see
|
|
|
156
155
|
`var(--navi-safe-area-inset-bottom)` in any rule. It is always declared, whether
|
|
157
156
|
or not the app ever mounts a bar.
|
|
158
157
|
|
|
159
|
-
From **JS**,
|
|
160
|
-
|
|
158
|
+
From **JS**, both levels are registered as lengths (`@property`, in
|
|
159
|
+
`safe_area.js`), so
|
|
161
160
|
`getComputedStyle(document.documentElement).getPropertyValue("--navi-safe-area-inset-bottom")`
|
|
162
|
-
gives pixels
|
|
163
|
-
`calc()` unresolved there: give a hidden box `height: var(…)` and measure it —
|
|
164
|
-
see `src/layout/demos/fixed_bar/keyboard.html`.
|
|
161
|
+
gives pixels, and so does `--navi-app-inset-*`.
|
|
165
162
|
|
|
166
163
|
### Putting something new into it
|
|
167
164
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
Level 2 has one slot per edge, `--navi-fixed-bar-space-*`, and it belongs to
|
|
166
|
+
`FixedBar`: each bar measures its own border box (notch included), and navi
|
|
167
|
+
writes the largest one on that edge inline on `<html>`
|
|
168
|
+
(`src/layout/fixed_bar/fixed_bar_space.js`). The slot is combined with
|
|
169
|
+
`env(safe-area-inset-*)` through `max()`, not added to it, since a bar measured
|
|
170
|
+
with its notch already covers the notch.
|
|
171
|
+
|
|
172
|
+
Something else taking an edge — a native banner, an OS strip — is published by
|
|
173
|
+
being drawn as a `FixedBar`; every component reading the safe area then clears
|
|
174
|
+
it without learning it exists. Writing the slot by hand holds only while no bar
|
|
175
|
+
takes that edge: the first bar mounting there replaces the value rather than
|
|
176
|
+
adding to it.
|
|
173
177
|
|
|
174
178
|
## What already reads it
|
|
175
179
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.390",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Library of components including navigation to create frontend applications",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"preact": "^11.0.0-rc.2"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@jsenv/dom": "0.17.
|
|
37
|
+
"@jsenv/dom": "0.17.74",
|
|
38
38
|
"@jsenv/humanize": "1.8.5",
|
|
39
39
|
"@jsenv/validity": "0.4.5"
|
|
40
40
|
},
|