@jsenv/navi 0.29.442 → 0.29.444

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.
@@ -114,8 +114,8 @@ start when unsure which export solves a problem.
114
114
  Before any handler whose body only assigns state.
115
115
  - `control_value.md` — who holds a control's value; `--navi-update` for a button
116
116
  proposing one; a `defaultValue` follows the record it was read from; empty
117
- keeps the shape of the question. Before wiring a value with `value` +
118
- `uiAction`.
117
+ keeps the shape of the question; what a checkbox is worth (`boolean` for a
118
+ yes/no). Before wiring a value with `value` + `uiAction`.
119
119
  - `control_object.md` — one value made of several controls: `ControlGroup` versus
120
120
  `Form`, naming, `<Picker type="object">`, a settings sheet, controls that are
121
121
  not there (lazy slides, popups, deferred boxes, items outside the render
@@ -186,10 +186,11 @@ start when unsure which export solves a problem.
186
186
  `scroller`, where a list opens (a position handed back whole, `visibleCount`
187
187
  included), a list that comes back (its `id` names one list of the app), a
188
188
  popup that scrolls, many rows (`<List.Items>`, the render window and its first
189
- picture, `findText`), many sections (`<Box mount="after-paint">` and what it
190
- leaves out of reach), hover while scrolling. Before writing CSS to
191
- make something scroll, before rendering a collection as `<List.Item>`
192
- children, and before deferring part of a page.
189
+ picture, skeletons at the size of their item, `itemSize`, `findText`), many
190
+ sections (`<Box mount="after-paint">` and what it leaves out of reach), hover
191
+ while scrolling. Before writing CSS to make something scroll, before
192
+ rendering a collection as `<List.Item>` children, before writing a
193
+ `renderSkeleton`, and before deferring part of a page.
193
194
  - `z_index.md` — DOM order first; a `z-index` without `isolation: isolate`
194
195
  competes with the page; navi's bands. Before writing a `z-index`.
195
196
  - `typography.md` — text is a component; `maxLines` is the one truncation; one
@@ -225,8 +226,10 @@ start when unsure which export solves a problem.
225
226
  `<Input type="checkbox">`/`<Input type="radio">`; there is no `Checkbox`),
226
227
  `Select`, `Picker` and the others work with nothing wired: inside a `Form`, the
227
228
  form reads them when it sends. When the app needs the value, bind it with
228
- `signal`. `action` reacts to a change that can fail or take time; it is not how
229
- a control is wired. Never `onChange` + manual state.
229
+ `signal`. A checkbox is HTML's (`"on"` or its `value`, `undefined` unchecked);
230
+ a yes/no is `<Input type="checkbox" boolean>`, `true` or `false`. `action`
231
+ reacts to a change that can fail or take time; it is not how a control is
232
+ wired. Never `onChange` + manual state.
230
233
  - **A gesture is named, not read by hand**: `interactions={{ swipe_right: … }}`
231
234
  on any `Box`, so on any component — never a `pointerdown` listener of your own.
232
235
  - **Texts**: a user-visible sentence containing a value is one template with
package/docs/actions.md CHANGED
@@ -313,13 +313,17 @@ the control's value, so the callback receives what the control now holds.
313
313
  ```jsx
314
314
  // ✓ the box shows it is saving, says so if the save fails, and goes back to
315
315
  // where it was — nothing to write for any of it
316
- <Input type="checkbox" action={(visibility) => saveMe({ visibility })} />
316
+ <Input type="checkbox" boolean action={(visibility) => saveMe({ visibility })} />
317
317
 
318
318
  // ✗ same save, and the user learns nothing: no pending state, and a failure
319
319
  // leaves the box showing something the server never accepted
320
- <Input type="checkbox" uiAction={(visibility) => saveMe({ visibility })} />
320
+ <Input type="checkbox" boolean uiAction={(visibility) => saveMe({ visibility })} />
321
321
  ```
322
322
 
323
+ `boolean` makes the box a yes/no, `true` or `false`; without it, unchecked is
324
+ `undefined` and the save sends nothing (see
325
+ [control_value.md](./control_value.md#what-a-checkbox-is-worth)).
326
+
323
327
  The give-away is an `async` `uiAction`, or one that calls something that writes:
324
328
  `uiAction` never waits for what it starts, so nobody is left holding the result.
325
329
  To merely REMEMBER the value rather than send it, neither is the answer: bind a
@@ -14,6 +14,7 @@ somewhere else in the app.
14
14
  - [Drawing what a control holds](#drawing-what-a-control-holds)
15
15
  - [What a signal holds, control by control](#what-a-signal-holds-control-by-control)
16
16
  - [Empty keeps the shape of the question](#empty-keeps-the-shape-of-the-question)
17
+ - [What a checkbox is worth](#what-a-checkbox-is-worth)
17
18
  - [Which controls take a `signal`](#which-controls-take-a-signal)
18
19
  - [The PROP is what controls, not its value](#the-prop-is-what-controls-not-its-value)
19
20
  - [A yes/no shown as two rows](#a-yesno-shown-as-two-rows)
@@ -281,18 +282,37 @@ it holds something. A list of days nobody picked is `[]`, not `""`:
281
282
  | `Picker type="array"`, `List selectable multiple` | `[]` |
282
283
  | `Picker type="object"` | `{}` |
283
284
  | text/number/date `Input`, `Select` | `""` |
284
- | `Input type="checkbox" value={true}` | `false` |
285
- | radio, checkbox holding a value of its own | absent |
285
+ | `Input type="checkbox" boolean` | `false` |
286
+ | radio, any other checkbox | absent |
286
287
 
287
288
  This is what a clear (`--navi-clear`, a row's cross) leaves behind and what the
288
289
  object around it carries, so `value.days || []` is not needed.
289
290
 
290
- A checkbox is a member of a set, the way HTML has it: checked it carries its
291
- `value` (`"on"` when it was given none), unchecked it carries nothing at all —
292
- which is what lets several checkboxes sharing a name aggregate into an array. A
293
- checkbox that is a yes/no says so with `value={true}`, and is then `true` or
294
- `false`. Its bound `signal` holds the boolean either way: what a signal on a
295
- checkbox is about is whether it is checked.
291
+ ## What a checkbox is worth
292
+
293
+ A checkbox answers the way HTML's does: checked it carries its `value`,
294
+ unchecked it carries nothing. `boolean` makes it a yes/no instead:
295
+
296
+ | checkbox | checked | unchecked |
297
+ | -------------- | -------- | ----------- |
298
+ | no `value` | `"on"` | `undefined` |
299
+ | `value="toto"` | `"toto"` | `undefined` |
300
+ | `value={true}` | `true` | `undefined` |
301
+ | `boolean` | `true` | `false` |
302
+
303
+ That is what it hands everywhere: to its own `action` and `uiAction`, and to
304
+ the form or group around it. Only a bound `signal` differs: it holds whether the
305
+ box is checked, a boolean in every row above.
306
+
307
+ Choose by what the box asks:
308
+
309
+ - **A yes/no** (a setting, a consent, a favourite) is `boolean`. Unchecked has
310
+ to say "no", and `undefined` says nothing: `{ favourite: undefined }` is sent
311
+ as `{}`, the server changes nothing, and the box shows a "no" nobody
312
+ accepted.
313
+ - **A member of a set** (several checkboxes sharing a `name`, each adding its
314
+ value) takes its own `value`, or none for `"on"`. Unchecked carries nothing,
315
+ so the group gathers only the checked ones into an array.
296
316
 
297
317
  ## Which controls take a `signal`
298
318
 
@@ -207,8 +207,9 @@ below, though: the obvious ones hide the race.
207
207
  **Chrome**: drive DevTools' touch emulator, which goes through Chrome's
208
208
  gesture detector with the **mobile** configuration (8px slop):
209
209
 
210
- - a headed Chromium (`chromium.launch({ headless: false })`) — headless
211
- delivers nothing through this path;
210
+ - a full browser: Chrome (`chromium.launch({ channel: "chrome" })`), headless
211
+ or not, or a headed Chromium (`headless: false`) — Playwright's default
212
+ headless shell delivers nothing through this path;
212
213
  - on a CDP session, `Emulation.setEmitTouchEventsForMouse({ enabled: true,
213
214
  configuration: "mobile" })`, then `Input.dispatchMouseEvent` with
214
215
  `mousePressed` / `mouseMoved` / `mouseReleased` (`buttons: 1` while down). The
@@ -237,6 +238,18 @@ them exactly as on a phone:
237
238
  and a restarted `safaridriver`. The page's `localStorage` does not survive
238
239
  that, so whatever must be read is read before the swipe.
239
240
 
241
+ A fling — the momentum a lifted finger leaves — is out of `safaridriver`'s
242
+ reach for the same reason. The Simulator turns the Mac's mouse into a finger:
243
+ system mouse events (`CGEvent`, a press, moves a frame apart, a release while
244
+ still moving fast) on the Simulator window fling Safari exactly like a thumb,
245
+ its momentum included (over 3 s: flings closer than that add up). The window
246
+ has to be on screen in the current Space: when it is not, activating the
247
+ Simulator does not bring it, and the events land in whatever window is under
248
+ the pointer — check which window is topmost at the points of the drag before
249
+ sending any, and wait for the Space to switch after activating. What the page
250
+ records is read back through a request it sends itself (the page runs on a
251
+ local server), since nothing can query it.
252
+
240
253
  What to synthesize is the hand's imperfection — a gesture simulated as clean,
241
254
  evenly spaced points passes forever and proves nothing: a slow start landing a
242
255
  report between 6 and 9px, steep first pixels, a report that jumps. What to read:
@@ -244,10 +257,10 @@ the `touchmove`s with `cancelable` and `defaultPrevented`, the `pointercancel`,
244
257
  `scrollY`, and what the gesture itself says (for a travel,
245
258
  `data-drag-travel-walking` on `:root` and the slide that ends up current).
246
259
 
247
- What neither path shows: the lift of a real finger (the last reports before
248
- `pointerup`), a real device's event timing (the order in which the browser's
249
- cancel and the next report reach the page), a momentum tail killed by a landing
250
- finger. Those remain device questions.
260
+ What these paths do not show: the lift of a real finger (the last reports
261
+ before `pointerup`), a real device's event timing (the order in which the
262
+ browser's cancel and the next report reach the page), a momentum tail killed by
263
+ a landing finger. Those remain device questions.
251
264
 
252
265
  Reference: `src/layout/demos/lab/slide_container_touch_race.html` — a row of
253
266
  slides that do not scroll and a row that do, for the two cases above.
package/docs/scroll.md CHANGED
@@ -196,8 +196,10 @@ takes the list back to where it was, which may be nowhere near that row.
196
196
  `loading` (with `loadingFallback`, `loadingSkeletonCount`, `renderSkeleton`)
197
197
  says "I have nothing at all to show yet": placeholder rows stand in for the
198
198
  whole list. `<List.Items count>` says "I know how many rows are coming": the
199
- rows not held yet are skeletons in their own place, asked for as they enter the
200
- render window — a list that knows its count has no use for the first one.
199
+ rows not held yet are skeletons in their own place, at their own size (see
200
+ [What the list knows, and what it guesses](#what-the-list-knows-and-what-it-guesses)),
201
+ asked for as they enter the render window — a list that knows its count has no
202
+ use for the first one.
201
203
 
202
204
  Reference: `src/control/list/list.jsx` (JSDoc on `List` and `List.Items`).
203
205
 
@@ -362,9 +364,9 @@ room it took — the window sliding changes nothing above the screen. What was
362
364
  never drawn it cannot measure, so it guesses:
363
365
 
364
366
  - the fillers hold every item never drawn at one size — `virtualItemSize` when
365
- given, the average of the items measured so far otherwise. A scroll position
366
- inside a filler is read with it too, and an item on its way takes at least
367
- that room;
367
+ given, the average of the items measured so far otherwise — unless its run
368
+ says the room it takes (see below). A scroll position inside a filler is
369
+ read with it too, and an item on its way takes at least that room;
368
370
  - when the window reaches past what it has drawn, it weighs the next items like
369
371
  the drawn ones next to them, and measures them once they are drawn.
370
372
 
@@ -375,10 +377,35 @@ item guessed **bigger** makes it count too few, and a blank shows until they
375
377
  are measured. So a size you give is the **worst case**: in a list whose items
376
378
  differ in height, `virtualItemSize` is the smallest an item can be — the
377
379
  one-line item, in a thread of one-line items and cards. The scrollbar then
378
- under-states a list made mostly of big items; the screen stays covered. One
379
- number for the whole list is the point: a size per kind of item, or per state
380
- of one, is a model to keep in step with the markup, for a scrollbar a little
381
- more exact.
380
+ under-states a list made mostly of big items; the screen stays covered.
381
+
382
+ A guess costs something else when its item is drawn above the screen: the item
383
+ takes its real room, and what is on screen moves by the difference. The list
384
+ puts the screen back by writing the scroll, except where a write would cost the
385
+ user the scroll in progress — and on iOS a fling stops at any write, so during
386
+ a fling every card drawn above the screen pushes it down. A list that knows
387
+ the room of each item before the item arrives says it with `<List.Items
388
+ itemSize>`, and guesses nothing: the fillers hold every item at its own room.
389
+ That is a model to keep in step with the markup (in development, a drawn item
390
+ whose room differs from it is reported), worth it where the items above the
391
+ screen come in a few sizes the data tells apart before it arrives — the
392
+ one-line games and the compact cards of a thread, when the server says which
393
+ past games have a score.
394
+
395
+ The items on their way are held the same way: a skeleton stands where its item
396
+ will be, at the room it is drawn at, and when the page lands each item takes
397
+ its own room — what is below a skeleton of the wrong size moves by the
398
+ difference. The list puts the screen back while a real item is on it, but a
399
+ fling into a part of the collection not loaded yet leaves a screen of skeletons
400
+ alone, with no item to hold: the screen moves with every skeleton that was
401
+ wrong (hundreds of pixels, in a thread drawing one-line skeletons where compact
402
+ cards land). So `renderSkeleton` draws each index at the room its item will
403
+ take, from what is known before the data — the same knowledge `itemSize` gives
404
+ the fillers. Items that cannot be told apart before they arrive are better
405
+ drawn at one height than announced by skeletons that guess. And anything drawn
406
+ above the screen that changes size afterwards — an image without its
407
+ dimensions, a block that expands — moves the screen the same way, which nothing
408
+ puts back during a fling on iOS: reserve its room.
382
409
 
383
410
  The window's own guess past what it has drawn is the one that is not the worst
384
411
  case, on purpose. Guessing the smallest there walks into cards at the one-line
@@ -457,6 +484,9 @@ Reference: `src/control/demos/19_list_find_in_page_demo.html`.
457
484
 
458
485
  ### Doing it well
459
486
 
487
+ - **Skeletons at the size of their item**, and `itemSize` when the items above
488
+ the screen come in sizes the data tells apart before it arrives (see
489
+ [What the list knows, and what it guesses](#what-the-list-knows-and-what-it-guesses)).
460
490
  - **A stable `id` on every item.** The run keys its rows on `item.id` — it is
461
491
  what addresses a row from outside (`--navi-select`, `scrolled={{ id }}`), and
462
492
  what tells a row that moved from a row that changed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.442",
3
+ "version": "0.29.444",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {