sygnal 5.4.0 → 6.0.0
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/CHANGELOG.md +646 -0
- package/README.md +24 -9
- package/dist/astro/client.cjs.js +16 -9
- package/dist/astro/client.cjs.js.map +1 -1
- package/dist/astro/client.mjs +16 -9
- package/dist/astro/client.mjs.map +1 -1
- package/dist/astro/index.cjs.js +362 -91
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +362 -92
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js +3396 -165
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +3396 -165
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +1431 -0
- package/dist/devtools.cjs.js.map +1 -0
- package/dist/devtools.esm.js +1419 -0
- package/dist/devtools.esm.js.map +1 -0
- package/dist/diagnostics.cjs.js +3032 -296
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +3032 -296
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/element.cjs.js +230 -0
- package/dist/element.cjs.js.map +1 -0
- package/dist/element.esm.js +228 -0
- package/dist/element.esm.js.map +1 -0
- package/dist/guide/accessibility.md +257 -0
- package/dist/guide/adapters.md +221 -0
- package/dist/guide/behaviors.md +312 -0
- package/dist/guide/browser-sources.md +203 -0
- package/dist/guide/drag-and-drop.md +342 -0
- package/dist/guide/element-commands.md +306 -0
- package/dist/guide/error-boundaries.md +169 -0
- package/dist/guide/forms-reference.md +381 -0
- package/dist/guide/forms.md +250 -0
- package/dist/guide/http.md +299 -0
- package/dist/guide/inputs.md +173 -0
- package/dist/guide/persistence.md +245 -0
- package/dist/guide/recipes/carousel.md +155 -0
- package/dist/guide/recipes/charts.md +177 -0
- package/dist/guide/recipes/code-editor.md +135 -0
- package/dist/guide/recipes/data-grid.md +155 -0
- package/dist/guide/recipes/data-table.md +196 -0
- package/dist/guide/recipes/i18n.md +222 -0
- package/dist/guide/recipes/icons.md +115 -0
- package/dist/guide/recipes/overview.md +33 -0
- package/dist/guide/recipes/rich-text.md +154 -0
- package/dist/guide/resources.md +471 -0
- package/dist/guide/ssr.md +255 -0
- package/dist/guide/timers.md +178 -0
- package/dist/guide/ui/accordion.md +93 -0
- package/dist/guide/ui/combobox.md +144 -0
- package/dist/guide/ui/dialog.md +124 -0
- package/dist/guide/ui/disclosure.md +60 -0
- package/dist/guide/ui/menu.md +125 -0
- package/dist/guide/ui/overview.md +166 -0
- package/dist/guide/ui/popover.md +101 -0
- package/dist/guide/ui/select.md +114 -0
- package/dist/guide/ui/tabs.md +161 -0
- package/dist/guide/ui/toaster.md +152 -0
- package/dist/guide/ui/tooltip.md +103 -0
- package/dist/guide/undo.md +178 -0
- package/dist/guide/virtual-collections.md +183 -0
- package/dist/guide/web-components.md +179 -0
- package/dist/guide/widgets.md +217 -0
- package/dist/index.cjs.js +13178 -5769
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2908 -276
- package/dist/index.esm.js +13149 -5779
- package/dist/index.esm.js.map +1 -1
- package/dist/jsx-dev-runtime.cjs.js +26 -334
- package/dist/jsx-dev-runtime.cjs.js.map +1 -1
- package/dist/jsx-dev-runtime.esm.js +19 -327
- package/dist/jsx-dev-runtime.esm.js.map +1 -1
- package/dist/jsx-runtime.cjs.js +26 -334
- package/dist/jsx-runtime.cjs.js.map +1 -1
- package/dist/jsx-runtime.esm.js +19 -327
- package/dist/jsx-runtime.esm.js.map +1 -1
- package/dist/jsx.cjs.js +10 -314
- package/dist/jsx.cjs.js.map +1 -1
- package/dist/jsx.esm.js +8 -315
- package/dist/jsx.esm.js.map +1 -1
- package/dist/react.cjs.js +117 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.esm.js +115 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/shims/globalthis.cjs +20 -0
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui-combobox.cjs.js +169 -0
- package/dist/ui-combobox.cjs.js.map +1 -0
- package/dist/ui-combobox.esm.js +148 -0
- package/dist/ui-combobox.esm.js.map +1 -0
- package/dist/ui-menu.cjs.js +87 -0
- package/dist/ui-menu.cjs.js.map +1 -0
- package/dist/ui-menu.esm.js +66 -0
- package/dist/ui-menu.esm.js.map +1 -0
- package/dist/ui-select.cjs.js +104 -0
- package/dist/ui-select.cjs.js.map +1 -0
- package/dist/ui-select.esm.js +83 -0
- package/dist/ui-select.esm.js.map +1 -0
- package/dist/ui.cjs.js +738 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.esm.js +727 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +4 -14
- package/dist/vike/ClientOnly.cjs.js.map +1 -1
- package/dist/vike/ClientOnly.mjs +4 -14
- package/dist/vike/ClientOnly.mjs.map +1 -1
- package/dist/vike/{+config.js → config/+config.js} +9 -1
- package/dist/vike/config/+config.js.map +1 -0
- package/dist/vike/config/package.json +6 -0
- package/dist/vike/onRenderClient.cjs.js +165 -95
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +165 -95
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +59 -24
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +60 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +323 -88
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +323 -89
- package/dist/vite/plugin.mjs.map +1 -1
- package/dist/zag.cjs.js +408 -0
- package/dist/zag.cjs.js.map +1 -0
- package/dist/zag.esm.js +405 -0
- package/dist/zag.esm.js.map +1 -0
- package/llms.txt +166 -103
- package/package.json +98 -14
- package/src/astro/client.ts +27 -16
- package/src/astro/index.d.ts +24 -0
- package/src/astro/index.ts +69 -3
- package/src/astro/server.ts +8 -2
- package/src/collection.ts +6 -93
- package/src/core/actions.ts +134 -0
- package/src/core/cell.ts +202 -0
- package/src/core/debug.ts +23 -0
- package/src/core/define.ts +242 -0
- package/src/core/hooks.ts +314 -0
- package/src/core/hosts/collection.ts +325 -0
- package/src/core/hosts/switchable.ts +146 -0
- package/src/core/instance.ts +592 -0
- package/src/core/markers/clientonly.ts +13 -0
- package/src/core/markers/lazy.ts +40 -0
- package/src/core/markers/portal.ts +96 -0
- package/src/core/markers/suspense.ts +41 -0
- package/src/core/markers/transition.ts +70 -0
- package/src/core/registry.ts +25 -0
- package/src/core/runtime.ts +573 -0
- package/src/core/statics.ts +104 -0
- package/src/core/teardown.ts +60 -0
- package/src/core/view.ts +37 -0
- package/src/cycle/dom/DocumentDOMSource.ts +13 -8
- package/src/cycle/dom/ElementFinder.ts +7 -9
- package/src/cycle/dom/EventDelegator.ts +174 -223
- package/src/cycle/dom/IsolateModule.ts +65 -35
- package/src/cycle/dom/MainDOMSource.ts +9 -2
- package/src/cycle/dom/PriorityQueue.ts +4 -2
- package/src/cycle/dom/SymbolTree.ts +18 -47
- package/src/cycle/dom/classNameModule.ts +9 -7
- package/src/cycle/dom/controlledInputModule.ts +23 -6
- package/src/cycle/dom/enrichEventStream.ts +25 -47
- package/src/cycle/dom/fragment.ts +7 -0
- package/src/cycle/dom/isolate.ts +30 -27
- package/src/cycle/dom/makeDOMDriver.ts +115 -56
- package/src/cycle/dom/mockDOMSource.ts +21 -1
- package/src/cycle/dom/modules.ts +16 -3
- package/src/cycle/dom/propsModule.ts +65 -0
- package/src/cycle/dom/selectModule.ts +22 -17
- package/src/cycle/dom/snabbdom.ts +1 -6
- package/src/cycle/dom/thunk.ts +3 -0
- package/src/cycle/dom/utils.ts +98 -0
- package/src/cycle/dom/viewTransition.ts +43 -0
- package/src/cycle/state/StateSource.ts +19 -3
- package/src/cycle/state/objIsEqual.ts +50 -0
- package/src/cycle/state/types.ts +0 -10
- package/src/defineComponent.ts +34 -0
- package/src/devtools.d.ts +151 -0
- package/src/devtools.ts +34 -0
- package/src/element.d.ts +59 -0
- package/src/element.ts +235 -0
- package/src/extra/backoff.ts +9 -0
- package/src/extra/behaviors.ts +169 -0
- package/src/extra/browserSignals.ts +23 -0
- package/src/extra/browserSources.ts +326 -0
- package/src/extra/command.ts +3 -3
- package/src/extra/controls.ts +47 -0
- package/src/extra/copyAsTest.ts +286 -0
- package/src/extra/devtools.ts +101 -31
- package/src/extra/devtoolsActions.ts +379 -0
- package/src/extra/devtoolsHook.ts +9 -0
- package/src/extra/devtoolsNext.ts +83 -0
- package/src/extra/diagnostics/checks/actionLog.ts +108 -0
- package/src/extra/diagnostics/checks/behaviors.ts +49 -0
- package/src/extra/diagnostics/checks/browserSources.ts +96 -0
- package/src/extra/diagnostics/checks/collections.ts +1 -1
- package/src/extra/diagnostics/checks/controls.ts +148 -0
- package/src/extra/diagnostics/checks/dataset.ts +57 -0
- package/src/extra/diagnostics/checks/dom.ts +20 -11
- package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
- package/src/extra/diagnostics/checks/events.ts +17 -2
- package/src/extra/diagnostics/checks/fetch.ts +135 -0
- package/src/extra/diagnostics/checks/forms.ts +163 -0
- package/src/extra/diagnostics/checks/index.ts +97 -4
- package/src/extra/diagnostics/checks/inspect.ts +140 -32
- package/src/extra/diagnostics/checks/next.ts +417 -0
- package/src/extra/diagnostics/checks/persist.ts +54 -0
- package/src/extra/diagnostics/checks/props.ts +10 -5
- package/src/extra/diagnostics/checks/public.d.ts +98 -7
- package/src/extra/diagnostics/checks/replies.ts +128 -0
- package/src/extra/diagnostics/checks/router.ts +100 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
- package/src/extra/diagnostics/checks/shared.ts +86 -2
- package/src/extra/diagnostics/checks/shorthand.ts +115 -0
- package/src/extra/diagnostics/checks/sortable.ts +129 -0
- package/src/extra/diagnostics/checks/state.ts +100 -7
- package/src/extra/diagnostics/checks/statics.ts +48 -0
- package/src/extra/diagnostics/checks/strict.ts +10 -67
- package/src/extra/diagnostics/checks/timers.ts +80 -0
- package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
- package/src/extra/diagnostics/checks/virtual.ts +73 -0
- package/src/extra/diagnostics/checks/widgets.ts +109 -0
- package/src/extra/diagnostics/checks/wiring.ts +66 -10
- package/src/extra/diagnostics/codes.ts +268 -23
- package/src/extra/diagnostics/index.ts +34 -9
- package/src/extra/diagnostics/legacy.ts +17 -0
- package/src/extra/driverFactories.ts +83 -19
- package/src/extra/elementCommands.ts +53 -0
- package/src/extra/fetchDriver.ts +519 -0
- package/src/extra/focusWithin.ts +27 -0
- package/src/extra/form.ts +268 -0
- package/src/extra/formHelpers.ts +139 -0
- package/src/extra/head.ts +106 -0
- package/src/extra/hmr.ts +2 -3
- package/src/extra/owned.ts +24 -0
- package/src/extra/pager.ts +50 -0
- package/src/extra/persist.ts +133 -0
- package/src/extra/pwa.ts +1 -1
- package/src/extra/queryCache.ts +107 -0
- package/src/extra/reducers.ts +1 -1
- package/src/extra/reduxDevtools.ts +92 -0
- package/src/extra/replies.ts +54 -0
- package/src/extra/router.ts +323 -0
- package/src/extra/run.ts +90 -217
- package/src/extra/selection.ts +76 -0
- package/src/extra/socketDriver.ts +265 -0
- package/src/extra/sortable.ts +377 -0
- package/src/extra/ssr.ts +427 -160
- package/src/extra/standardSchema.ts +27 -0
- package/src/extra/testing.ts +2433 -175
- package/src/extra/timers.ts +95 -0
- package/src/extra/undo.ts +261 -0
- package/src/extra/viewTransitions.ts +38 -0
- package/src/extra/virtual.ts +513 -0
- package/src/extra/widget.ts +201 -0
- package/src/index.d.ts +2631 -117
- package/src/index.ts +25 -4
- package/src/jsx-runtime.ts +15 -1
- package/src/jsx.ts +2 -1
- package/src/lazy.ts +50 -7
- package/src/portal.ts +3 -1
- package/src/pragma/index.ts +256 -133
- package/src/react-peers.d.ts +5 -0
- package/src/react.d.ts +43 -0
- package/src/react.ts +110 -0
- package/src/shared.ts +48 -0
- package/src/slot.ts +1 -1
- package/src/suspense.ts +3 -1
- package/src/switchable.ts +6 -121
- package/src/transition.ts +3 -1
- package/src/ui/accordion.ts +64 -0
- package/src/ui/dialog.ts +129 -0
- package/src/ui/disclosure.ts +35 -0
- package/src/ui/popover.ts +45 -0
- package/src/ui/shared.ts +94 -0
- package/src/ui/tabs.ts +82 -0
- package/src/ui/toaster.ts +198 -0
- package/src/ui/tooltip.ts +70 -0
- package/src/ui/zag/combobox.ts +115 -0
- package/src/ui/zag/menu.ts +40 -0
- package/src/ui/zag/select.ts +54 -0
- package/src/ui/zag/shared.ts +43 -0
- package/src/ui-combobox.d.ts +20 -0
- package/src/ui-combobox.ts +6 -0
- package/src/ui-menu.d.ts +30 -0
- package/src/ui-menu.ts +6 -0
- package/src/ui-select.d.ts +17 -0
- package/src/ui-select.ts +6 -0
- package/src/ui-zag-types.d.ts +42 -0
- package/src/ui.d.ts +208 -0
- package/src/ui.ts +15 -0
- package/src/vike/+config.ts +9 -1
- package/src/vike/ClientOnly.ts +1 -1
- package/src/vike/onRenderClient.ts +144 -96
- package/src/vike/onRenderHtml.ts +69 -26
- package/src/vike/types.ts +7 -1
- package/src/vite/globalthis-shim.ts +18 -0
- package/src/vite/globalthis.ts +56 -0
- package/src/vite/plugin.d.ts +22 -3
- package/src/vite/plugin.ts +222 -24
- package/src/zag.d.ts +64 -0
- package/src/zag.ts +238 -0
- package/dist/vike/+config.cjs.js +0 -66
- package/dist/vike/+config.cjs.js.map +0 -1
- package/dist/vike/+config.js.map +0 -1
- package/src/component.ts +0 -2301
- package/src/cycle/isolate/index.ts +0 -196
- package/src/cycle/run/index.ts +0 -151
- package/src/cycle/run/internals.ts +0 -143
- package/src/cycle/state/Collection.ts +0 -184
- package/src/cycle/state/index.ts +0 -5
- package/src/cycle/state/pickCombine.ts +0 -201
- package/src/cycle/state/pickMerge.ts +0 -122
- package/src/cycle/state/withState.ts +0 -47
- package/src/pragma/fn.ts +0 -55
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-5 V-1: `<VirtualCollection>`, a Collection that renders only the rows in view.
|
|
3
|
+
*
|
|
4
|
+
* <VirtualCollection of={Row} from="rows" className="rows" estimateSize={32} />
|
|
5
|
+
* // a fixed height in CSS: .rows { height: 400px }
|
|
6
|
+
* // jump: JUMP: { ELEMENT: (s, i) => ({ scrollToIndex: '.rows', index: i }) }
|
|
7
|
+
* // FIND: { ELEMENT: (s, id) => ({ scrollToId: '.rows', id, align: 'center' }) }
|
|
8
|
+
*
|
|
9
|
+
* The same `of` / `from` / `filter` / `sort` as Collection (it is the core's Collection host with
|
|
10
|
+
* a window: D169 keys, D177 duplicates, D178 a missing `from`, SYG401/411/412/418), and the other
|
|
11
|
+
* props go to every item. Measurement and the window come from `@tanstack/virtual-core`, a regular
|
|
12
|
+
* dependency of sygnal (D209; tree-shaken when unused).
|
|
13
|
+
*
|
|
14
|
+
* 0 B when unused: nothing runs on import. The tag is a function carrying the pragma's render
|
|
15
|
+
* hook (`__sygnalControl`, as `defineWidget` tags), so its first render registers the host
|
|
16
|
+
* (`hosts['virtual-collection']`) and returns the marker the core's reconcile hands to it.
|
|
17
|
+
*
|
|
18
|
+
* - The element is its own scroll container: a `div` (className, id, style, aria-label/-labelledby/
|
|
19
|
+
* -describedby, `role` (default `list`), tabIndex (default 0, so the keyboard scrolls it)) with
|
|
20
|
+
* `overflow-y: auto` and `overflow-anchor: none` (the virtualizer corrects the offset itself).
|
|
21
|
+
* Inside: a spacer as tall as every row (measured or estimated), and a window moved down to the
|
|
22
|
+
* first rendered row; the rows are in normal flow inside it, so a row's height is its own.
|
|
23
|
+
* - Only the rows in view (+ `overscan`, default 5) have instances. A row scrolled out is disposed
|
|
24
|
+
* like a removed Collection item and made again when it comes back. Its state is its element
|
|
25
|
+
* of the array, so nothing in state is lost; what lives only in the DOM (an uncontrolled input's
|
|
26
|
+
* text, a running EFFECT) is. Keep row state in the array (the canonical Collection item).
|
|
27
|
+
* - G-401: rows are keyed (a row's element is its own). The row holding the focus stays rendered
|
|
28
|
+
* while scrolled out (pinned at its own offset, out of the window's flow) until the focus leaves
|
|
29
|
+
* the list, and no row element is moved in the DOM (a move blurs it in every engine): a window
|
|
30
|
+
* that loses rows at its front while its end changes is patched in two steps. G-424: the pin
|
|
31
|
+
* follows the row's key through a reorder (filter, sort, a new array), and a row element a
|
|
32
|
+
* reorder moves gets the focus back (when it went nowhere; preventScroll; G-462: the element
|
|
33
|
+
* inside a shadow root in the row when the focus is there).
|
|
34
|
+
* - Row heights: `estimateSize` (a number, or `(item, index) => number`, default 32) until a row is
|
|
35
|
+
* rendered; then its measured height (ResizeObserver follows later changes, applied in the next
|
|
36
|
+
* frame: G-402). A 0 measurement (no layout: jsdom) keeps the estimate. A new function at each
|
|
37
|
+
* render is the same estimate (G-394); a number that changes, or a switch between a number and a
|
|
38
|
+
* function, re-measures.
|
|
39
|
+
* - Each row's root element gets `data-index`, `aria-setsize` and `aria-posinset` (1-based, over the
|
|
40
|
+
* filtered list), and `role="listitem"` when the container's role is `list` and the row's root has
|
|
41
|
+
* no role of its own. A row must render one element (SYG432 for a fragment or text).
|
|
42
|
+
* - `scrollToIndex` / `scrollToId` are element commands (methods of the container element):
|
|
43
|
+
* `{ scrollToIndex: '.rows', index, align?, behavior? }`, `{ scrollToId: '.rows', id, align?,
|
|
44
|
+
* behavior? }` (align: 'auto' (default: only when not in view) | 'start' | 'center' | 'end').
|
|
45
|
+
* The row needs no element: the virtualizer scrolls to its offset and keeps correcting it while
|
|
46
|
+
* the rows around it are measured. An id not in the (filtered) list: SYG433 in dev, no scroll.
|
|
47
|
+
* - Without layout (SSR, the mock DOM, jsdom), the window is the first 10 rows' estimate + overscan.
|
|
48
|
+
* - The container must have a bounded height. When it has none (0 tall: SYG430) or grows with its
|
|
49
|
+
* rows (it would render every row: SYG430), the window is clamped to the viewport's height.
|
|
50
|
+
* Whether it grows is measured (G-395, G-427): a max-height that fits the rows bounds it; a
|
|
51
|
+
* percentage of an unbounded parent, calc() or fit-content that follows the rows doesn't.
|
|
52
|
+
* G-462: also under an ancestor's CSS zoom (G-525: not a transform), and the measurement
|
|
53
|
+
* doesn't move the page (scroll anchoring is off around it meanwhile).
|
|
54
|
+
* Items without `id`: SYG431 (index keys: rows and measured heights follow the position).
|
|
55
|
+
* - `viewTransitionName="row"` names each keyed row's root element as Collection does (`row-<id>`,
|
|
56
|
+
* class `row`; G-417), as its SSR markup does.
|
|
57
|
+
*
|
|
58
|
+
* Dev text lives in 'sygnal/diagnostics' (checks/virtual.ts), reached through the core bridge as
|
|
59
|
+
* `virtual(code, owner, extra)`; production reports nothing.
|
|
60
|
+
*/
|
|
61
|
+
import {hosts} from '../core/registry'
|
|
62
|
+
import {CollectionHost, named} from '../core/hosts/collection'
|
|
63
|
+
import {Inst} from '../core/instance'
|
|
64
|
+
import {itemCell, keyName, keyOf} from '../core/cell'
|
|
65
|
+
import {chainHooks} from '../pragma/index'
|
|
66
|
+
import {Virtualizer, defaultRangeExtractor, elementScroll, observeElementOffset, observeElementRect} from '@tanstack/virtual-core'
|
|
67
|
+
|
|
68
|
+
const SEL = 'virtual-collection'
|
|
69
|
+
/** the container's props (not passed to the items) */
|
|
70
|
+
const OWN = /^(estimateSize|overscan|role|tabIndex|style|id|aria-(label|labelledby|describedby))$/
|
|
71
|
+
const NONE: any[] = []
|
|
72
|
+
/** rows in the window when there is no layout (SSR, mock DOM, jsdom) */
|
|
73
|
+
const ROWS0 = 10
|
|
74
|
+
|
|
75
|
+
const dev = (code: number, owner: any, x?: any): any => (globalThis as any).__SYGNAL_DIAGNOSTICS__?.virtual?.(code, owner, x)
|
|
76
|
+
|
|
77
|
+
const px = (n: number) => n + 'px'
|
|
78
|
+
/** the element with the focus in `el`'s root (its shadow root: G-426), if any */
|
|
79
|
+
const act = (el: any): any => el && (el.getRootNode?.() || el.ownerDocument)?.activeElement
|
|
80
|
+
/** G-462: the focused element itself when it is inside open shadow roots (`a` is their host) */
|
|
81
|
+
const deep = (a: any): any => {
|
|
82
|
+
while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement
|
|
83
|
+
return a
|
|
84
|
+
}
|
|
85
|
+
/** the props the container's vnode data is made from (not estimateSize / overscan: G-394) */
|
|
86
|
+
const BOX = (x: string) => x != 'estimateSize' && x != 'overscan'
|
|
87
|
+
const same = (a: any, b: any, d?: any): boolean => {
|
|
88
|
+
const k = Object.keys(a).filter(x => d || BOX(x))
|
|
89
|
+
return k.length == Object.keys(b).filter(x => d || BOX(x)).length && k.every(x => a[x] === b[x] || !d && x == 'style' && a[x] && b[x] && typeof a[x] == 'object' && same(a[x], b[x], 1))
|
|
90
|
+
}
|
|
91
|
+
/** an estimate's kind: a number (by value) or 'f' (any function: an inline one is new each render, G-394) */
|
|
92
|
+
const kind = (e: any) => (typeof e == 'function' ? 'f' : e)
|
|
93
|
+
|
|
94
|
+
export class VirtualHost extends CollectionHost {
|
|
95
|
+
// (no field initializers: the base constructor calls setProps() before they would run)
|
|
96
|
+
declare vp: Record<string, any>
|
|
97
|
+
/** every visible key in order (filter, sort applied); the virtualizer's items */
|
|
98
|
+
declare all: any[]
|
|
99
|
+
declare v: Virtualizer<any, any> | undefined
|
|
100
|
+
/** the scroll container, once on the page */
|
|
101
|
+
declare el: any
|
|
102
|
+
/** the last window: start offset, total size, first index, rows; the container's data */
|
|
103
|
+
declare lw: any
|
|
104
|
+
declare cd: any
|
|
105
|
+
declare gk: (i: number) => any
|
|
106
|
+
/** per item instance: [its vnode, index, set size, role, pinned top, the decorated vnode] */
|
|
107
|
+
declare dc: WeakMap<Inst, any[]>
|
|
108
|
+
/** the shown rows' indexes (aligned with `shown`) */
|
|
109
|
+
declare si: number[]
|
|
110
|
+
/** the focused row's key and index (G-401: kept in the range), and the index pinned outside the window */
|
|
111
|
+
declare fk: any
|
|
112
|
+
declare fi: number
|
|
113
|
+
declare pin: number
|
|
114
|
+
/** the range extractor (a new one when the focused row changes: the virtualizer memoizes the range on it) */
|
|
115
|
+
declare re: (r: any) => number[]
|
|
116
|
+
/** the stable estimateSize the virtualizer calls (it reads the current prop, G-394) */
|
|
117
|
+
declare sz: (i: number) => number
|
|
118
|
+
/** a first patch step that only removed rows ran (G-401) */
|
|
119
|
+
declare p1: boolean
|
|
120
|
+
/** the element in the list with the focus before a patch (G-424) */
|
|
121
|
+
declare fa: any
|
|
122
|
+
declare warned: number
|
|
123
|
+
declare un: (() => void) | undefined
|
|
124
|
+
|
|
125
|
+
setProps(props: Record<string, any>, children: any[], marker?: any, id?: string) {
|
|
126
|
+
const vp: Record<string, any> = {}, rest: Record<string, any> = {}
|
|
127
|
+
for (const k in props) (OWN.test(k) ? vp : rest)[k] = props[k]
|
|
128
|
+
rest.className = props.className
|
|
129
|
+
if (!this.dc) { this.dc = new WeakMap(); this.all = NONE; this.warned = 0; this.si = []; this.fi = this.pin = -1 }
|
|
130
|
+
const was = this.vp, cls = this.props?.className
|
|
131
|
+
this.vp = vp
|
|
132
|
+
super.setProps(rest, children, marker, id)
|
|
133
|
+
// G-394: the virtualizer reads the estimate through a stable function, so a new inline function
|
|
134
|
+
// keeps the measurements; a number that changes, or a switch between a number and a function,
|
|
135
|
+
// re-measures
|
|
136
|
+
const re = was && kind(vp.estimateSize) !== kind(was.estimateSize)
|
|
137
|
+
if (!was || re || vp.overscan !== was.overscan) this.opts(re)
|
|
138
|
+
// the container's data is made again when its props change (a style object one level deep)
|
|
139
|
+
if (!was || cls !== rest.className || !same(vp, was)) this.cd = undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** the virtualizer, (re)configured from the props and the current list */
|
|
143
|
+
opts(remeasure?: any) {
|
|
144
|
+
const p = this.vp, est = p.estimateSize, o = p.overscan
|
|
145
|
+
if (est !== undefined && !(typeof est == 'function' || est > 0)) dev(434, this.owner, {prop: 'estimateSize', value: est})
|
|
146
|
+
if (o !== undefined && !(o >= 0)) dev(434, this.owner, {prop: 'overscan', value: o})
|
|
147
|
+
this.sz ||= (i: number) => {
|
|
148
|
+
const e = this.vp.estimateSize, s = typeof e == 'function' ? e(this.itemAt(i), i) : e
|
|
149
|
+
return s > 0 ? s : 32
|
|
150
|
+
}
|
|
151
|
+
this.re ||= (r: any) => this.range(r)
|
|
152
|
+
const opts: any = {
|
|
153
|
+
count: this.all.length,
|
|
154
|
+
getItemKey: this.gk ||= (i: number) => this.all[i],
|
|
155
|
+
getScrollElement: () => this.el,
|
|
156
|
+
estimateSize: this.sz,
|
|
157
|
+
rangeExtractor: this.re,
|
|
158
|
+
// G-402: ResizeObserver results are applied in the next frame (no ResizeObserver loop error)
|
|
159
|
+
useAnimationFrameWithResizeObserver: typeof requestAnimationFrame == 'function',
|
|
160
|
+
overscan: o >= 0 ? o : 5,
|
|
161
|
+
initialRect: {width: 0, height: (est > 0 ? est : 32) * ROWS0},
|
|
162
|
+
scrollToFn: elementScroll,
|
|
163
|
+
observeElementOffset,
|
|
164
|
+
observeElementRect: (v: any, cb: any) => observeElementRect(v, (r: any) => cb(this.bound(r))),
|
|
165
|
+
// the height unrounded (rows are in normal flow: rounded sizes would drift from where the
|
|
166
|
+
// browser puts them, by up to half a pixel a row); 0 (no layout: jsdom) is no measurement
|
|
167
|
+
measureElement: (e: any, entry: any, v: any) =>
|
|
168
|
+
entry?.borderBoxSize?.[0]?.blockSize || e.getBoundingClientRect().height || e.offsetHeight || v.options.estimateSize(v.indexFromElement(e)),
|
|
169
|
+
onChange: () => this.disposed || this.owner.app.commit(),
|
|
170
|
+
}
|
|
171
|
+
if (this.v) {
|
|
172
|
+
this.v.setOptions(opts)
|
|
173
|
+
if (remeasure) this.v.measure()
|
|
174
|
+
} else this.v = new Virtualizer(opts)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* G-401: the default range, plus the focused row when it is outside it (its element must stay:
|
|
179
|
+
* removing it loses the focus). It is pinned at its own offset, out of the window's flow.
|
|
180
|
+
*/
|
|
181
|
+
range(r: any): number[] {
|
|
182
|
+
const out = defaultRangeExtractor(r), f = this.fk === undefined ? -1 : this.fi
|
|
183
|
+
this.pin = -1
|
|
184
|
+
if (f >= 0 && f < r.count && out.length && (f < out[0] || f > out[out.length - 1])) {
|
|
185
|
+
this.pin = f
|
|
186
|
+
f < out[0] ? out.unshift(f) : out.push(f)
|
|
187
|
+
}
|
|
188
|
+
return out
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** the row element (container > spacer > window > row) holding `t`, if any */
|
|
192
|
+
rowOf(t: any): any {
|
|
193
|
+
const el = this.el
|
|
194
|
+
for (let n = t; n && n !== el; n = n.parentNode) if (n.parentNode?.parentNode?.parentNode === el && n.hasAttribute?.('data-index')) return n
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** the focused row's key changed: a new range (the virtualizer memoizes it on the extractor) */
|
|
198
|
+
focus(k: any) {
|
|
199
|
+
if (k === this.fk || this.disposed) return
|
|
200
|
+
this.fk = k
|
|
201
|
+
this.fi = k === undefined ? -1 : this.all.indexOf(k)
|
|
202
|
+
this.re = (r: any) => this.range(r)
|
|
203
|
+
this.opts()
|
|
204
|
+
this.owner.app.commit()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** the raw array element shown at index i */
|
|
208
|
+
itemAt(i: number) {
|
|
209
|
+
const [a, m] = this.index!()
|
|
210
|
+
return a[m.get(this.all[i])!]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The rect the window is computed from. No height: SYG430 when the element is laid out (in jsdom
|
|
215
|
+
* nothing is), and the no-layout window. As tall as all the rows and taller than the viewport:
|
|
216
|
+
* the container grows with its rows (no bounded height), so the window is the viewport's height
|
|
217
|
+
*/
|
|
218
|
+
bound(r: {width: number; height: number}) {
|
|
219
|
+
const el = this.el, v = this.v!, total = v.getTotalSize(), win = el?.ownerDocument?.defaultView
|
|
220
|
+
if (!r.height) {
|
|
221
|
+
if (el?.getClientRects?.().length) this.warn(1, {reason: 'height'})
|
|
222
|
+
return {width: r.width, height: v.options.initialRect!.height}
|
|
223
|
+
}
|
|
224
|
+
const vh = win?.innerHeight || 0
|
|
225
|
+
// G-462: (the rect is scaled by an ancestor's CSS zoom, clientHeight isn't, as the sizes)
|
|
226
|
+
if (vh && total > vh && Math.max(r.height, el.clientHeight || 0) >= total - 1 && this.grows(el)) {
|
|
227
|
+
this.warn(2, {reason: 'grows', height: r.height})
|
|
228
|
+
// G-502: the viewport's height in the rows' px under an ancestor's CSS zoom. G-525: the zoom
|
|
229
|
+
// only (currentCSSZoom), not a transform (scale(0.02) would make it 50 viewports, and no
|
|
230
|
+
// resize is observed when it goes); without currentCSSZoom the rect / offsetHeight ratio,
|
|
231
|
+
// capped (0.1 to 10)
|
|
232
|
+
const z = el.currentCSSZoom || Math.min(Math.max(el.getBoundingClientRect().height / el.offsetHeight || 1, 0.1), 10)
|
|
233
|
+
return {width: r.width, height: vh / z}
|
|
234
|
+
}
|
|
235
|
+
return r
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* G-395 / G-427: as tall as its rows and taller than the viewport is "grows" only when its
|
|
240
|
+
* height follows its content. Measured, not parsed (a max-height that is a percentage of an
|
|
241
|
+
* unbounded parent, calc(), fit-content, a flex or grid item): the spacer is made 1e6 px taller
|
|
242
|
+
* for one forced layout and put back before anything renders (no resize is observed); a
|
|
243
|
+
* container that grows with it is unbounded, one bounded by a max-height that fits its rows
|
|
244
|
+
* stops at it. G-462: with scroll anchoring off in the scrollers around it meanwhile (the
|
|
245
|
+
* content after the list moves by 1e6 px and back: the page would move with it, or in WebKit not
|
|
246
|
+
* quite back)
|
|
247
|
+
*/
|
|
248
|
+
grows(el: any): boolean {
|
|
249
|
+
const s = el.firstElementChild?.style
|
|
250
|
+
if (!s) return true
|
|
251
|
+
const up: any[] = [], O = 'overflow-anchor'
|
|
252
|
+
// (G-498: a slotted element's scroller can be in the shadow tree: its slot first)
|
|
253
|
+
for (let n = el.assignedSlot || el.parentElement || el.getRootNode?.().host; n?.style; n = n.assignedSlot || n.parentElement || n.getRootNode?.().host) {
|
|
254
|
+
up.push(n, n.style.getPropertyValue(O), n.style.getPropertyPriority(O), n.hasAttribute('style'))
|
|
255
|
+
n.style.setProperty(O, 'none')
|
|
256
|
+
}
|
|
257
|
+
const h = s.height, a = el.offsetHeight
|
|
258
|
+
s.height = (parseFloat(h) || 0) + 1e6 + 'px'
|
|
259
|
+
const b = el.offsetHeight
|
|
260
|
+
s.height = h
|
|
261
|
+
// (laid out again before anchoring is back)
|
|
262
|
+
void el.offsetHeight
|
|
263
|
+
// G-498: put back through the CSSOM (setting the style attribute is blocked under a CSP
|
|
264
|
+
// style-src without 'unsafe-inline'); no style attribute stays none (read first: Chromium and
|
|
265
|
+
// WebKit write the attribute lazily, and would add style="" after the removal)
|
|
266
|
+
for (let i = 0; i < up.length; i += 4) {
|
|
267
|
+
const n = up[i]
|
|
268
|
+
up[i + 1] ? n.style.setProperty(O, up[i + 1], up[i + 2]) : n.style.removeProperty(O)
|
|
269
|
+
if (!up[i + 3]) n.getAttribute('style'), n.removeAttribute('style')
|
|
270
|
+
}
|
|
271
|
+
return b - a > 5e5
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
warn(bit: number, x: any) {
|
|
275
|
+
if (this.warned & bit) return
|
|
276
|
+
this.warned |= bit
|
|
277
|
+
dev(430, this.owner, x)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** the list of keys (as a Collection's: filter, sort, the first of a duplicate key); true when it changed */
|
|
281
|
+
keys(): boolean {
|
|
282
|
+
if (!this.index) return false
|
|
283
|
+
const [a, m] = this.index(), {filter, sort} = this.props
|
|
284
|
+
if (a === this.la && filter === this.lf && sort === this.ls) return false
|
|
285
|
+
// no filter, no sort and no duplicate key: the index's keys are the list, in order (100k rows:
|
|
286
|
+
// no second pass making keys)
|
|
287
|
+
let all: any[]
|
|
288
|
+
if (typeof filter != 'function' && !sort && m.size == a.length) { all = [...m.keys()]; this.ls = sort; this.cmp = undefined }
|
|
289
|
+
else all = this.list(a, m)
|
|
290
|
+
this.la = a; this.lf = filter
|
|
291
|
+
// SYG431 (dev only: a pass over the array)
|
|
292
|
+
if (a.length && !(this.warned & 4) && (globalThis as any).__SYGNAL_DIAGNOSTICS__?.virtual && a.some((x, i) => keyOf(x, i)[0] == '\0')) { this.warned |= 4; dev(431, this.owner, {count: a.length}) }
|
|
293
|
+
// G-407: the same keys in the same order (a row edited): the virtualizer keeps its key
|
|
294
|
+
// function, so it doesn't rebuild every row's measurement
|
|
295
|
+
const old = this.all
|
|
296
|
+
if (all.length == old.length && all.every((x, i) => x === old[i])) return false
|
|
297
|
+
this.all = all
|
|
298
|
+
if (this.fk !== undefined) {
|
|
299
|
+
if ((this.fi = all.indexOf(this.fk)) < 0) this.fk = undefined
|
|
300
|
+
// G-424: the focused row's new index (or none): a new extractor, since the virtualizer
|
|
301
|
+
// memoizes the indexes on it and on the range (a reorder can keep both)
|
|
302
|
+
this.re = (r: any) => this.range(r)
|
|
303
|
+
}
|
|
304
|
+
// a new key function: the virtualizer re-reads the keys (its measurements are by key)
|
|
305
|
+
this.gk = (i: number) => this.all[i]
|
|
306
|
+
this.opts()
|
|
307
|
+
return true
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** the item instances in the window; true when they changed */
|
|
311
|
+
items_(): boolean {
|
|
312
|
+
const kc = this.keys()
|
|
313
|
+
const v = this.v!, app = this.owner.app, o = this.owner, all = this.all
|
|
314
|
+
const vis = all.length ? v.getVirtualIndexes() : NONE
|
|
315
|
+
const want = new Set<any>()
|
|
316
|
+
for (const i of vis) want.add(all[i])
|
|
317
|
+
const prev = this.shown
|
|
318
|
+
// G-401: rows are keyed, and snabbdom moves (detaches: the focus is lost) every kept row when
|
|
319
|
+
// rows leave the front while the end changes too. Then the rows leaving the front are removed
|
|
320
|
+
// in a patch of their own first (the ends match: nothing moves), the rest in the next one
|
|
321
|
+
// (the starts match): a microtask after the first patch, before the frame is painted. The
|
|
322
|
+
// flush may render again before it patches: the first step's rows stay until then.
|
|
323
|
+
if (this.p1 && !kc) return false
|
|
324
|
+
if (!kc && prev.length && vis.length) {
|
|
325
|
+
const k = (j: number) => (prev[j] as any).k
|
|
326
|
+
let h = 0, e = 0
|
|
327
|
+
while (h < prev.length && h < vis.length && k(h) === all[vis[h]]) h++
|
|
328
|
+
for (e = h; e < prev.length && !want.has(k(e)); e++);
|
|
329
|
+
if (e > h && e < prev.length && k(prev.length - 1) !== all[vis[vis.length - 1]]) {
|
|
330
|
+
for (let j = h; j < e; j++) { prev[j].dispose(); this.items.delete(k(j)) }
|
|
331
|
+
this.shown = prev.slice(0, h).concat(prev.slice(e))
|
|
332
|
+
this.si = this.si.slice(0, h).concat(this.si.slice(e))
|
|
333
|
+
this.p1 = true
|
|
334
|
+
queueMicrotask(() => { this.p1 = false; this.disposed || app.commit() })
|
|
335
|
+
return true
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
this.p1 = false
|
|
339
|
+
for (const [k, inst] of this.items) if (!want.has(k)) { inst.dispose(); this.items.delete(k) }
|
|
340
|
+
const shown: Inst[] = []
|
|
341
|
+
for (const i of vis) {
|
|
342
|
+
const k = all[i]
|
|
343
|
+
let inst = this.items.get(k)
|
|
344
|
+
if (!inst) {
|
|
345
|
+
const scope = app.scope()
|
|
346
|
+
inst = new Inst(app, this.def, o, itemCell(this.arr!, this.index!, k), o.dom && o.dom.isolateSource(o.dom, scope),
|
|
347
|
+
this.ip, this.kids, scope, this.uidBase + '-' + keyName(k), 'item')
|
|
348
|
+
this.items.set(k, inst);
|
|
349
|
+
(inst as any).k = k
|
|
350
|
+
}
|
|
351
|
+
shown.push(inst)
|
|
352
|
+
}
|
|
353
|
+
const changed = shown.length != prev.length || shown.some((s, i) => s !== prev[i])
|
|
354
|
+
this.shown = shown
|
|
355
|
+
this.si = vis as number[]
|
|
356
|
+
return changed
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
clear() {
|
|
360
|
+
super.clear()
|
|
361
|
+
this.all = NONE
|
|
362
|
+
this.si = []
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** a row's root with its index, ARIA position and measuring hook (the same vnode while nothing changed) */
|
|
366
|
+
deco(inst: Inst, x: any, i: number, n: number, list: boolean, top?: string): any {
|
|
367
|
+
const c = this.dc.get(inst)
|
|
368
|
+
if (c && c[0] === x && c[1] === i && c[2] === n && c[3] === list && c[4] === top) return c[5]
|
|
369
|
+
let out = x
|
|
370
|
+
if (!x.sel) {
|
|
371
|
+
// a fragment or text: nothing to measure or label
|
|
372
|
+
if (!(this.warned & 8)) { this.warned |= 8; dev(432, this.owner, {component: this.def.name}) }
|
|
373
|
+
} else {
|
|
374
|
+
// the position for a row with a role (its own, or listitem in a list): not on a generic element
|
|
375
|
+
const d = x.data || {}, role = d.attrs?.role || d.props?.role, attrs: any = {...d.attrs, 'data-index': i}
|
|
376
|
+
if (list || role) attrs['aria-posinset'] = i + 1, attrs['aria-setsize'] = n
|
|
377
|
+
if (list && !role) attrs.role = 'listitem'
|
|
378
|
+
const data: any = {...d, attrs}
|
|
379
|
+
// G-401: the focused row out of the window, at its own offset (relative to the window)
|
|
380
|
+
if (top !== undefined) data.style = {...d.style, position: 'absolute', top, left: '0', width: '100%'}
|
|
381
|
+
chainHooks(data, {insert: (y: any) => this.v?.measureElement(y.elm)})
|
|
382
|
+
// keyed (G-401): a row's element is its own, not the one at its position before
|
|
383
|
+
out = {...x, data, key: (inst as any).k}
|
|
384
|
+
}
|
|
385
|
+
this.dc.set(inst, [x, i, n, list, top, out])
|
|
386
|
+
return out
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** the container's vnode data: its props, attributes, style and the hooks that attach the virtualizer */
|
|
390
|
+
box() {
|
|
391
|
+
if (this.cd) return this.cd
|
|
392
|
+
const p = this.vp, attrs: any = {role: p.role === undefined ? 'list' : p.role, tabindex: p.tabIndex ?? 0}
|
|
393
|
+
for (const k of ['id', 'aria-label', 'aria-labelledby', 'aria-describedby']) if (p[k] != null) attrs[k] = p[k]
|
|
394
|
+
if (attrs.role == null) delete attrs.role
|
|
395
|
+
return (this.cd = {
|
|
396
|
+
props: this.props.className ? {className: this.props.className} : {},
|
|
397
|
+
attrs,
|
|
398
|
+
style: {overflowY: 'auto', overflowAnchor: 'none', ...p.style},
|
|
399
|
+
// (the mock DOM's command check: these methods are the container's)
|
|
400
|
+
vc: {commands: {scrollToIndex: 1, scrollToId: 1}},
|
|
401
|
+
hook: {
|
|
402
|
+
// (`s`: Sygnal's own, so hydration adopts the container: postpatch attaches it, G-521)
|
|
403
|
+
insert: Object.assign((y: any) => this.attach(y.elm), {s: 1}),
|
|
404
|
+
// G-424: a row element the patch moves (a reorder) loses the focus in every engine: the
|
|
405
|
+
// element in the list that had it gets it back when it is still on the page and the
|
|
406
|
+
// focus went nowhere
|
|
407
|
+
// G-462: none when the cached vnode is patched again (snabbdom calls no postpatch then:
|
|
408
|
+
// nothing would let go of the element); in a shadow root in a row, its focused element
|
|
409
|
+
prepatch: (o: any, y: any) => { const a = o !== y && act(this.el); this.fa = a && this.el.contains(a) ? deep(a) : null },
|
|
410
|
+
// G-500: "went nowhere" is the document's active element (body or none): in a shadow root,
|
|
411
|
+
// the root's own is none when the focus went to an element outside it
|
|
412
|
+
postpatch: (_: any, y: any) => {
|
|
413
|
+
const a = this.fa, b = a?.ownerDocument.activeElement
|
|
414
|
+
this.fa = null
|
|
415
|
+
if (a?.isConnected && (!b || b === a.ownerDocument.body)) a.focus({preventScroll: true})
|
|
416
|
+
y.elm !== this.el && this.attach(y.elm)
|
|
417
|
+
},
|
|
418
|
+
destroy: () => this.detach(),
|
|
419
|
+
},
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
attach(el: any) {
|
|
424
|
+
if (this.el) this.detach()
|
|
425
|
+
this.el = el
|
|
426
|
+
el.scrollToIndex = (x: any) => this.scrollTo(typeof x == 'number' ? x : x?.index, x)
|
|
427
|
+
el.scrollToId = (x: any) => {
|
|
428
|
+
const id = typeof x == 'object' && x ? x.id : x, i = id == null ? -1 : this.all.indexOf('' + id)
|
|
429
|
+
if (i < 0) return void dev(433, this.owner, {id, count: this.all.length})
|
|
430
|
+
this.scrollTo(i, x)
|
|
431
|
+
}
|
|
432
|
+
// G-401: which row holds the focus (a focusout to outside the list: none)
|
|
433
|
+
const fin = (e: any) => this.focus(this.all[this.rowOf(e.target)?.getAttribute('data-index')])
|
|
434
|
+
// G-428: no relatedTarget while the focus is still in the list: the window lost the focus
|
|
435
|
+
// (another window, devtools), and it comes back to the same element
|
|
436
|
+
const fout = (e: any) => el.contains(e.relatedTarget || act(el)) || this.focus(undefined)
|
|
437
|
+
el.addEventListener('focusin', fin)
|
|
438
|
+
el.addEventListener('focusout', fout)
|
|
439
|
+
const un = this.v!._didMount()
|
|
440
|
+
this.un = () => { un(); el.removeEventListener('focusin', fin); el.removeEventListener('focusout', fout) }
|
|
441
|
+
this.v!._willUpdate()
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
scrollTo(i: any, x: any) {
|
|
445
|
+
const n = this.all.length
|
|
446
|
+
if (!(i >= 0 && i < n) || i % 1) return void dev(433, this.owner, {index: i, count: n})
|
|
447
|
+
this.v!.scrollToIndex(i, {align: x?.align, behavior: x?.behavior})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
detach() {
|
|
451
|
+
const el = this.el
|
|
452
|
+
if (!el) return
|
|
453
|
+
this.el = undefined
|
|
454
|
+
delete el.scrollToIndex
|
|
455
|
+
delete el.scrollToId
|
|
456
|
+
this.un?.()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
render(): any {
|
|
460
|
+
if (this.disposed) return this.outv
|
|
461
|
+
const app = this.owner.app
|
|
462
|
+
let changed = !this.outv || this.ep !== app.ep
|
|
463
|
+
this.ep = app.ep
|
|
464
|
+
if (this.items_()) changed = true
|
|
465
|
+
const v = this.v!, all = this.all, n = all.length, shown = this.shown, si = this.si
|
|
466
|
+
const ms: any[] = n && si.length ? (v as any).getMeasurements() : NONE, pin = this.pin
|
|
467
|
+
// the window starts at its first row in the flow (a pinned row is out of it)
|
|
468
|
+
const w0 = si[0] === pin && si.length > 1 ? si[1] : si[0]
|
|
469
|
+
const start = ms[w0] ? ms[w0].start : 0, total = v.getTotalSize()
|
|
470
|
+
const list = (this.vp.role === undefined ? 'list' : this.vp.role) == 'list'
|
|
471
|
+
const lw = this.lw
|
|
472
|
+
if (!lw || lw[0] !== start || lw[1] !== total || lw[2] !== si[0]) changed = true
|
|
473
|
+
const out: any[] = []
|
|
474
|
+
const vn = this.props.viewTransitionName
|
|
475
|
+
for (let j = 0; j < shown.length; j++) {
|
|
476
|
+
const inst = shown[j], x = inst.render(), i = si[j]
|
|
477
|
+
if (x !== inst.last) { inst.last = x; changed = true }
|
|
478
|
+
// an item without state yet (or a removed one) is left out; G-417: named as a Collection's
|
|
479
|
+
if (x !== undefined) out.push(this.deco(inst, vn ? named(inst, x, vn) : x, i, n, list, i === pin && ms[i] ? px(ms[i].start - start) : undefined))
|
|
480
|
+
}
|
|
481
|
+
const data = this.box()
|
|
482
|
+
if (data !== lw?.[3]) changed = true
|
|
483
|
+
if (!changed) return this.outv
|
|
484
|
+
this.lw = [start, total, si[0], data]
|
|
485
|
+
const win = {sel: 'div', data: {style: {position: 'absolute', top: '0', left: '0', width: '100%', transform: `translateY(${px(start)})`}}, children: out, text: undefined, elm: undefined, key: undefined}
|
|
486
|
+
const spacer = {sel: 'div', data: {style: {position: 'relative', width: '100%', height: px(total)}}, children: [win], text: undefined, elm: undefined, key: undefined}
|
|
487
|
+
return (this.outv = {sel: 'div', data, children: [spacer], text: undefined, elm: undefined, key: this.key})
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
dispose() {
|
|
491
|
+
if (this.disposed) return
|
|
492
|
+
super.dispose()
|
|
493
|
+
this.detach()
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const make = (owner: any, props: Record<string, any>, children: any[], id: string, marker: any) => new VirtualHost(owner, props, children, id, marker)
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* `<VirtualCollection of={Row} from="rows" className="rows" estimateSize={32} overscan={5} />`:
|
|
501
|
+
* a Collection that instantiates only the rows in view (see the module comment)
|
|
502
|
+
*/
|
|
503
|
+
export const VirtualCollection: any = () => {}
|
|
504
|
+
VirtualCollection.__sygnalControl = (props: any, children: any) => {
|
|
505
|
+
hosts[SEL] ||= make
|
|
506
|
+
const {key, children: c, ...p} = props || {}
|
|
507
|
+
const kids = children?.length ? children : c ? [].concat(c) : NONE
|
|
508
|
+
// (G-493: `m`, a marker mark as the pragma's marker vnodes carry (there, the function): SYG669
|
|
509
|
+
// tells it from a plain element. Not the function itself: the self-reference keeps the module
|
|
510
|
+
// in bundles that don't use it)
|
|
511
|
+
return {sel: SEL, data: {props: p, m: 1}, children: kids, text: undefined, elm: undefined, key}
|
|
512
|
+
}
|
|
513
|
+
VirtualCollection.componentName = SEL
|