@jsenv/navi 0.29.316 → 0.29.317
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 +595 -244
- package/dist/dev/jsenv_navi.js.map +18 -14
- package/dist/jsenv_navi.js +489 -552
- package/dist/jsenv_navi.js.map +18 -15
- package/docs/AI_INSTRUCTIONS.md +4 -2
- package/docs/scroll.md +90 -0
- package/package.json +2 -2
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -202,8 +202,10 @@ to start when unsure which export solves a problem.
|
|
|
202
202
|
a virtual keyboard. Read before hand-writing an offset to clear a `FixedBar`.
|
|
203
203
|
- `scroll.md` — where scrolling happens: `header`/`body`/`footer`, `List`'s
|
|
204
204
|
`scroller`, where a list opens and what a search does to where it is, a popup
|
|
205
|
-
that scrolls, hover while scrolling
|
|
206
|
-
|
|
205
|
+
that scrolls, hover while scrolling; many rows: `<List.Items>` and the render
|
|
206
|
+
window (`renderBudget`, its `{ initial, after }` form for a list opening in
|
|
207
|
+
a click, `virtualItemSize`). Read before writing CSS to make something
|
|
208
|
+
scroll, and before rendering a collection as `<List.Item>` children.
|
|
207
209
|
- `mobile_layout_pitfalls.md` — a horizontal overflow inflates the layout
|
|
208
210
|
viewport on Chrome Android and miscenters every dialog; `overflow-x: clip`.
|
|
209
211
|
- `z_index.md` — DOM order first; a `z-index` without `isolation: isolate`
|
package/docs/scroll.md
CHANGED
|
@@ -12,6 +12,7 @@ it was read at: see
|
|
|
12
12
|
- [1. The document scrolls](#1-the-document-scrolls)
|
|
13
13
|
- [2. A part of the document scrolls](#2-a-part-of-the-document-scrolls)
|
|
14
14
|
- [3. A popup scrolls](#3-a-popup-scrolls)
|
|
15
|
+
- [Many rows: `List.Items` and the render window](#many-rows-listitems-and-the-render-window)
|
|
15
16
|
- [Hover while scrolling](#hover-while-scrolling)
|
|
16
17
|
- [The list border](#the-list-border)
|
|
17
18
|
|
|
@@ -314,6 +315,95 @@ Reference: `src/layout/dialog.jsx`, `src/layout/popover.jsx`,
|
|
|
314
315
|
`src/layout/slide_container.jsx`, and the "One slide much taller than the
|
|
315
316
|
others" case in `src/layout/demos/8_slide_container_demo.html`.
|
|
316
317
|
|
|
318
|
+
## Many rows: `List.Items` and the render window
|
|
319
|
+
|
|
320
|
+
What a list costs must not follow the size of its collection. A row is a
|
|
321
|
+
`<List.Item>` with a checkbox, a text and a button or two — around 150 to 250
|
|
322
|
+
components once every resolver, box and context provider is counted — and the
|
|
323
|
+
browser paints nothing until the render that draws them has ended. Forty rows
|
|
324
|
+
given to a list that opens in a click are forty rows drawn before anything is
|
|
325
|
+
seen, and a hundred are a hundred: with the CPU throttled to a phone's, the
|
|
326
|
+
click that opens a sheet of 40 users took 536 ms, and 952 ms for 100 (wematch,
|
|
327
|
+
September 2026). A screen shows fourteen of them.
|
|
328
|
+
|
|
329
|
+
So the rule: **rows as `<List.Item>` children are all drawn**, and that is the
|
|
330
|
+
right shape only for a list the caller knows to be short — a menu, a settings
|
|
331
|
+
sheet, a handful of tabs. A collection whose size the caller does not decide
|
|
332
|
+
(users, messages, search results, anything read from a resource) goes to a run:
|
|
333
|
+
|
|
334
|
+
```jsx
|
|
335
|
+
// in memory: the whole collection, in order; the list draws a window of it
|
|
336
|
+
<List renderBudget={{ initial: 17, after: 30 }} virtualItemSize={45}>
|
|
337
|
+
<List.Items items={users} renderItem={renderUser} />
|
|
338
|
+
</List>
|
|
339
|
+
|
|
340
|
+
// read a slice at a time: the run asks its source for what it is about to draw
|
|
341
|
+
<List>
|
|
342
|
+
<List.Items itemsAction={USER.GET_RANGE.bindParams({ q })} renderItem={renderUser} />
|
|
343
|
+
</List>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The run draws only the rows inside the **render window** — `renderBudget` of
|
|
347
|
+
them, 100 by default — and holds the room of the others with fillers, so the
|
|
348
|
+
scrollbar says how long the collection is and the DOM says how many rows fit a
|
|
349
|
+
screen and some. The window slides as the user scrolls, and moves only when
|
|
350
|
+
what is on screen nears one of its edges: a row crossed is not a window
|
|
351
|
+
rebuilt. Below 30 the list warns, because a window shorter than a tall screen
|
|
352
|
+
shows blank fillers under the last row.
|
|
353
|
+
|
|
354
|
+
### The first paint of a list that opens in a click
|
|
355
|
+
|
|
356
|
+
A popup's content is built in the click that opens it (see `popup_open.md`),
|
|
357
|
+
and rows below the fold cost the same there as rows on screen. `renderBudget`
|
|
358
|
+
takes `{ initial, after }` for exactly this: `initial` rows in the commit the
|
|
359
|
+
browser paints first — what a phone screen shows, plus a few — and `after` from
|
|
360
|
+
the paint on. The switch waits for the paint itself, not for an effect: preact
|
|
361
|
+
runs a component's pending effects early when that component renders again,
|
|
362
|
+
and something always re-renders before a popup has painted. Do not rebuild
|
|
363
|
+
this by hand with a `useEffect` that widens a slice — that is the thing it
|
|
364
|
+
replaces, and it fails for that reason. The runs ask their source for `after`
|
|
365
|
+
rows from the start, so the smaller first window costs no second request.
|
|
366
|
+
|
|
367
|
+
### Doing it well
|
|
368
|
+
|
|
369
|
+
- **A stable `id` on every item.** The run keys its rows on `item.id` — it is
|
|
370
|
+
what addresses a row from outside (`--navi-select`, `scrolled={{ id }}`), and
|
|
371
|
+
what tells a row that moved from a row that changed.
|
|
372
|
+
- **A stable `renderItem`.** The run keeps the vnode it drew for an item and
|
|
373
|
+
hands preact the same one on its next render — the window sliding, the
|
|
374
|
+
first paint's budget giving way — so an unchanged row is not walked again.
|
|
375
|
+
It can only do that for the same function as last time: `renderItem` written
|
|
376
|
+
inline in a component that re-renders is a new function each time, and every
|
|
377
|
+
row is redrawn with it. Define it outside the component, or `useCallback` it,
|
|
378
|
+
and let it read the item and the index it is given rather than closing over
|
|
379
|
+
state.
|
|
380
|
+
- **`renderItem` returns a `<List.Item>` carrying its own props** —
|
|
381
|
+
`selectable`, `value`, `selected`, the buttons in it — there is no second
|
|
382
|
+
place describing the row.
|
|
383
|
+
- **`virtualItemSize` when the rows are uniform.** Without it the list measures
|
|
384
|
+
a row: once at mount, again when the popup around it opens (a row inside a
|
|
385
|
+
closed dialog measures 0), and after each commit while rows are held off
|
|
386
|
+
screen. Given, nothing is measured, and the fillers are right from the first
|
|
387
|
+
commit.
|
|
388
|
+
- **A `key` on the run when the collection changes** as a whole (`itemsAction`
|
|
389
|
+
with another scope): another collection is another run. With `items`,
|
|
390
|
+
another array is already another collection.
|
|
391
|
+
- **`scroller="parent"` inside a popup body**, as everywhere else in this file.
|
|
392
|
+
- **Groups come from the data**: `groupBy` on the run, `renderGroupLabel` for
|
|
393
|
+
the label — the only way a list discovering its rows page by page has
|
|
394
|
+
sections.
|
|
395
|
+
|
|
396
|
+
A run whose rows all fit the window is just rows: nothing is virtualized, no
|
|
397
|
+
row is ever a skeleton with `items`, and it costs what the same rows would as
|
|
398
|
+
children. There is no reason to hold back from it for a list that might grow.
|
|
399
|
+
|
|
400
|
+
Reference: `src/control/list/list.jsx` (JSDoc on `List` — `renderBudget`,
|
|
401
|
+
`virtualItemSize` — and on `List.Items`),
|
|
402
|
+
`src/control/demos/17_virtual_scroll_and_filter_demo.html` (a run in memory,
|
|
403
|
+
searched), `src/control/demos/integration/1_list_loaded_by_scroll_demo.html`
|
|
404
|
+
(a run reading a slice at a time). What a run reads back after a write is in
|
|
405
|
+
[list_refresh.md](./list_refresh.md).
|
|
406
|
+
|
|
317
407
|
## Hover while scrolling
|
|
318
408
|
|
|
319
409
|
A scroll moves the content under a pointer that does not move. The browser
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.317",
|
|
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.64",
|
|
38
38
|
"@jsenv/humanize": "1.8.3",
|
|
39
39
|
"@jsenv/validity": "0.4.4"
|
|
40
40
|
},
|