@jsenv/navi 0.29.389 → 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.
@@ -373,11 +373,19 @@ copy where the hand put it, over the thing appearing there.
373
373
  #### Naming what travels
374
374
 
375
375
  **The copy is already named, and it is the copy that does the visible travel.**
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
+ `syncCloneWithDropTarget` moves it onto the destination inside the callback — so
377
+ the piece the hand let go of slides to its place whether the application names
378
+ anything or not. What is left to name is the OTHER one: the piece that was
379
+ standing there and has to go the other way.
380
+
381
+ The name is the copy's own, because two copies can be on screen at once — one
382
+ still flying away while its `toss` is answered, another landing — and a name
383
+ claimed twice aborts the transition. What every copy shares is the class
384
+ `navi-drag-clone`, which is how a stylesheet reaches the landing:
385
+ `::view-transition-group(.navi-drag-clone)`. A view-transition class is read
386
+ from Chrome 125 on; before that the landing still plays, and only a rule written
387
+ against the class goes unread. The wrapper carrying the copy paints nothing and
388
+ takes neither.
381
389
 
382
390
  **And the original is hidden for the whole landing**, not only for the drag: it
383
391
  wears `navi-drag-clone-source` (`visibility: hidden`) until the promise returned
package/docs/pan_zoom.md CHANGED
@@ -42,14 +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. A pointer pans only once it has travelled a few px
46
- (`data-drag-threshold`), so a tap stays a tap: a `longpress` declared beside
47
- `pan` still gets its hold, and a `double_click` still gets its two presses.
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
55
  whatever else reads it: nothing captures it, waits on it or swallows its click
51
56
  until a second finger lands, so a `click` or a `longpress` declared beside `zoom`
52
- answers it as it would anywhere. `pan` alone leaves the wheel to the page.
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.
53
60
 
54
61
  ### A surface that scrolls past: `data-pan-after-hold`
55
62
 
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)
@@ -359,6 +360,43 @@ const GAME_CANDIDATES = GAME.scopedMany("candidates", {
359
360
  `idKey` names the child's own key inside its owner (`user_id` above); it defaults
360
361
  to `"id"`.
361
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
+
362
400
  ## When the backend answers a sub-route with the whole parent
363
401
 
364
402
  This is the common REST shape, and it is the reason `op` dispatch feels
@@ -405,7 +443,8 @@ parent's own fields change too, either let the parent GET rerun (see below) or
405
443
  Relationship mutations do **not** invalidate their parent by default. The exact
406
444
  rules, verified by `src/state/rest/tests/resource_graph_parent_rerun.test.js`:
407
445
 
408
- - `.scopedMany` child **POST** reruns the owner's singular `GET` — but only when
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
409
448
  the last GET response actually embedded that property. GET_MANY on the parent
410
449
  is never rerun by a child POST (a list of parents is not stale because one of
411
450
  them gained a child).
@@ -459,6 +498,11 @@ share is the **store**: an item updated through one is the same object in the
459
498
  others, so the fields of a row change everywhere without a request — only the
460
499
  membership of each list is a question its own scope answers.
461
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
+
462
506
  `withParams()` chains, merging the params —
463
507
  `USER.withParams({ role: "admin" }).withParams({ gender: "male" })` is
464
508
  `USER.withParams({ role: "admin", gender: "male" })`. Its second argument takes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.389",
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.73",
37
+ "@jsenv/dom": "0.17.74",
38
38
  "@jsenv/humanize": "1.8.5",
39
39
  "@jsenv/validity": "0.4.5"
40
40
  },