vlist 3.0.0 → 3.0.1-next.1

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.
@@ -90,9 +90,9 @@ export declare function normalizeLogical(sizeCache: SizeCache, pos: LogicalScrol
90
90
  /**
91
91
  * Create a {@link ScrollAdapter} over an existing pixel-based scroll source.
92
92
  *
93
- * During migration the underlying source is still the native viewport scroll;
94
- * later phases can swap `getPixel` / `setPixel` for a bounded-proxy or
95
- * interception input strategy without changing any plugin that uses the adapter.
93
+ * The source behind `getPixel` / `setPixel` is the native viewport scroll or a
94
+ * logical handler (synthetic input, carousel runway); plugins that use the
95
+ * adapter do not change with it.
96
96
  */
97
97
  export declare function createScrollAdapter(config: ScrollAdapterConfig): ScrollAdapter;
98
98
  //# sourceMappingURL=adapter.d.ts.map
@@ -7,12 +7,17 @@
7
7
  import type { VListItem } from "../types";
8
8
  import type { CreateVListConfig, PluginMethods, VListPlugin, VList } from "./types";
9
9
  import type { SizeCache } from "./sizes";
10
- import type { BoundedScrollHandler, BoundedScrollConfig } from "./runway";
10
+ import type { LogicalScrollHandler, LogicalScrollConfig } from "./logical";
11
11
  import type { ScrollHandler, ScrollHandlerConfig } from "./scroll";
12
12
  import { type Emitter } from "../events";
13
13
  import type { VListEvents } from "../types";
14
+ type SyntheticFactory = (config: LogicalScrollConfig & {
15
+ sizeCache: SizeCache;
16
+ }) => LogicalScrollHandler;
17
+ type SyntheticDriver = typeof import("../synthetic/driver");
14
18
  /**
15
- * Create a list with native scrolling. Opt into synthetic input via vlist/synthetic.
19
+ * Create a list. `scroll.mode` chooses who owns input: `"auto"` (default)
20
+ * scrolls natively and goes synthetic past the browser's size limit.
16
21
  *
17
22
  * Let the item type be inferred from the config — its `items`, or the
18
23
  * `template` parameter — rather than passing it as a type argument. The list's
@@ -34,12 +39,12 @@ export declare function createVList<T extends VListItem = VListItem, const P ext
34
39
  /** @internal Shared factory; entries select the input handler. */
35
40
  export declare function createCore<T extends VListItem = VListItem>(rawConfig: CreateVListConfig<T>, plugins?: VListPlugin<T>[],
36
41
  /** @internal Omitted by the native entry. */
37
- logicalHandlerFactory?: (config: BoundedScrollConfig & {
38
- sizeCache: SizeCache;
39
- }) => BoundedScrollHandler, nativeOptions?: {
42
+ logicalHandlerFactory?: SyntheticFactory, nativeOptions?: {
40
43
  native: (config: ScrollHandlerConfig) => ScrollHandler & {
41
44
  commitScroll(pos?: number): void;
42
45
  };
46
+ load: () => Promise<SyntheticDriver>;
43
47
  onContentSize: (size: number, emitter: Emitter<VListEvents<T>>) => void;
44
48
  }): VList<T>;
49
+ export {};
45
50
  //# sourceMappingURL=create.d.ts.map
@@ -5,8 +5,8 @@
5
5
  * positions are unchanged. Re-keying the map (and the `id` that encodes the
6
6
  * virtual index) lets the next commit find them instead of releasing,
7
7
  * re-templating and re-inserting the viewport. Lives here — not in pipeline
8
- * or create — so a native list that never wraps does not import it (runway
9
- * is pulled by carousel). The synthetic handler imports it because wrap
8
+ * or create — so a native list that never wraps does not import it (the
9
+ * carousel runway pulls it). The synthetic handler imports it because wrap
10
10
  * folds for that entry go through commit(), not wrapRebase; putting the
11
11
  * import in create.ts would charge every native list.
12
12
  *
@@ -18,7 +18,7 @@
18
18
  * `focusedIndex` (data space under `indexMap`), snapshots (`dataIndex`).
19
19
  */
20
20
  import type { EngineState } from "./state";
21
- import type { WrapConfig } from "./runway";
21
+ import type { WrapConfig } from "./logical";
22
22
  /**
23
23
  * Move every `rendered` key by `-indexShift` without touching the DOM tree.
24
24
  * `data-index` / `aria-posinset` carry the public index and stay put.
@@ -0,0 +1,93 @@
1
+ /**
2
+ * vlist — logical scroll handlers (RFC-012)
3
+ *
4
+ * A handler that owns the logical position itself, instead of reading it from
5
+ * a native scroll offset: the synthetic driver (`scroll.mode`, RFC-014/015) and
6
+ * the carousel's wrap runway. Both render through `state.baseOffset`: items are
7
+ * placed at `getOffset(index) - baseOffset`, so the content element never has
8
+ * to be as large as the list.
9
+ */
10
+ import type { SizeCache } from "./sizes";
11
+ import type { EngineState } from "./state";
12
+ import type { ScrollHandler } from "./scroll";
13
+ export interface LogicalScrollHandler extends ScrollHandler {
14
+ /** Move to an absolute logical (virtual pixel) position; renders + schedules idle. */
15
+ setLogical(logicalPx: number): void;
16
+ /** Current absolute logical position (== state.scrollPosition). */
17
+ getLogical(): number;
18
+ /** Non-cancelling coordinate correction, when supported by the input provider. */
19
+ shiftBy?(delta: number): void;
20
+ /** Maximum scrollable logical position (`virtualTotal - containerSize`, >= 0). */
21
+ getMaxLogical(): number;
22
+ /** Recompute derived geometry from the total size and resize the content element. */
23
+ refresh(totalSize: number): void;
24
+ }
25
+ /**
26
+ * Infinite-loop (wrap) configuration for a logical handler (carousel, RFC-011).
27
+ *
28
+ * In wrap mode the logical position is never clamped to a maximum: instead it is
29
+ * periodically folded back toward {@link home} by whole laps once it drifts more
30
+ * than {@link thresholdLaps} laps away. Because the consumer maps virtual indices
31
+ * to real items via `index % realTotal`, shifting the logical position (and
32
+ * baseOffset) by a whole lap leaves the rendered items and their on-screen
33
+ * positions unchanged — the loop is seamless. The content element stays a few
34
+ * viewports long, so large item counts never blow the browser's element-size
35
+ * limit.
36
+ */
37
+ export interface WrapConfig {
38
+ /** Current lap period in virtual px (`realTotal × stepSize`). */
39
+ readonly lapSize: () => number;
40
+ /**
41
+ * Real items in one lap (e.g. the carousel's `realTotal`). The wrap handler
42
+ * uses this to re-key mounted elements on a fold so the same DOM nodes
43
+ * survive the virtual-index shift — only the key changed; paint did not.
44
+ */
45
+ readonly itemsPerLap: () => number;
46
+ /** Logical position to fold back toward (the home lap). */
47
+ readonly home: () => number;
48
+ /** Fold the logical position back toward `home` once it drifts this many laps away. */
49
+ readonly thresholdLaps: number;
50
+ /** @internal Notify the wrap owner when its logical coordinates fold. */
51
+ readonly onFold?: (shift: number) => void;
52
+ }
53
+ export interface LogicalScrollConfig {
54
+ /** Logical row geometry for opt-in input providers (keyboard row steps). */
55
+ readonly sizeCache?: Pick<SizeCache, "getSize" | "indexAtOffset" | "getTotalSize">;
56
+ readonly state: EngineState;
57
+ readonly viewport: HTMLElement;
58
+ readonly content: HTMLElement;
59
+ readonly isX: boolean;
60
+ readonly wheelEnabled: boolean;
61
+ readonly idleTimeout: number;
62
+ readonly scrollTarget?: EventTarget;
63
+ /** Main-axis padding folded into the virtual total (matches native content sizing). */
64
+ readonly mainAxisPadding: number;
65
+ /** Runway size as a multiple of the viewport (defaults to RUNWAY_FACTOR). */
66
+ readonly runwayFactor?: number;
67
+ /** Infinite-loop config (carousel). When set, the logical position wraps by
68
+ * whole laps toward `home` instead of clamping to a maximum. */
69
+ readonly wrap?: WrapConfig;
70
+ /**
71
+ * Mounted row map. A wrap fold re-keys it in place so phase 2 finds the
72
+ * same nodes; omitted when the handler is constructed without a viewport.
73
+ */
74
+ readonly rendered?: Map<number, HTMLElement>;
75
+ /**
76
+ * Class prefix for rewriting `id` / `aria-activedescendant` on a wrap fold.
77
+ * Passed explicitly — a prefix containing `-content` cannot be recovered
78
+ * from the content element's class name.
79
+ */
80
+ readonly classPrefix?: string;
81
+ /**
82
+ * Stripe class (`{prefix}-item--odd`). Re-toggled on a wrap fold when
83
+ * `indexShift` is odd, so virtual-index parity survives the re-key.
84
+ */
85
+ readonly oddClass?: string;
86
+ /** @internal Coordinate fold: shift core telemetry references before rendering. */
87
+ readonly onFold?: (shift: number) => void;
88
+ /** Called synchronously per frame — triggers the 2-phase pipeline. */
89
+ readonly onFrame: () => void;
90
+ /** Called when scrolling becomes idle. */
91
+ readonly onIdle: () => void;
92
+ }
93
+ //# sourceMappingURL=logical.d.ts.map
@@ -1,113 +1,27 @@
1
1
  /**
2
- * vlist — Bounded Logical Scroll Handler (RFC-012)
2
+ * vlist — carousel runway (RFC-011, RFC-012)
3
3
  *
4
- * An alternative to the native scroll handler ({@link createScrollHandler}) that
5
- * decouples the native scrollable size from the virtual content size.
4
+ * The carousel's infinite loop scrolls a native element that is only a few
5
+ * viewports long: the *runway*. The logical position is `baseOffset +
6
+ * scrollTop`; as scrollTop nears an edge of the runway, baseOffset and
7
+ * scrollTop shift together by the same delta, so the logical position and
8
+ * what is on screen stay put. The scroll event that shift triggers reads the
9
+ * same logical position and is dropped by the "logical unchanged" guard. In
10
+ * wrap mode the position also folds back by whole laps toward home.
6
11
  *
7
- * The content element is sized to a bounded *runway* (a multiple of the
8
- * viewport, capped at the real virtual size) instead of `totalItems × itemSize`.
9
- * This sidesteps the browser's ~16.7M px element-size limit without compressing
10
- * the coordinate space — no compression ratio leaks into offsets or hit-testing.
11
- *
12
- * Coordinates:
13
- * logical = baseOffset + scrollTop (absolute virtual pixel position)
14
- *
15
- * - `state.scrollPosition` always holds the *logical* (absolute) position, so
16
- * the render pipeline's `indexAtOffset(scrollPosition)` keeps working over
17
- * the full size cache, and the public pixel-equivalent surface is unchanged.
18
- * - `state.baseOffset` is the virtual offset that maps to native scrollTop=0.
19
- * The pipeline renders each item at `getOffset(index) - baseOffset`, so items
20
- * land inside the bounded runway. baseOffset is 0 for small lists (native).
21
- *
22
- * Rebasing: as native scrollTop approaches a runway edge we shift baseOffset and
23
- * scrollTop together by the same delta, preserving `logical`. The synthetic
24
- * scroll event this triggers reads the same logical position and is dropped by
25
- * the "logical unchanged" guard — no suppress flag needed. The repositioning of
26
- * items (their transforms depend on baseOffset) is committed by the explicit
27
- * render that the rebasing scroll frame performs.
12
+ * Engine code (it owns the scroll coordinates), but only `carousel()` imports
13
+ * it: a list without it never bundles it.
28
14
  */
29
- import type { SizeCache } from "./sizes";
30
- import type { EngineState } from "./state";
31
- import type { ScrollHandler } from "./scroll";
32
- export interface BoundedScrollHandler extends ScrollHandler {
33
- /** Move to an absolute logical (virtual pixel) position; renders + schedules idle. */
34
- setLogical(logicalPx: number): void;
35
- /** Current absolute logical position (== state.scrollPosition). */
36
- getLogical(): number;
37
- /** Non-cancelling coordinate correction, when supported by the input provider. */
38
- shiftBy?(delta: number): void;
39
- /** Maximum scrollable logical position (`virtualTotal - containerSize`, >= 0). */
40
- getMaxLogical(): number;
41
- /** Recompute runway/derived values and resize the content element. */
42
- refresh(totalSize: number): void;
43
- }
15
+ import type { LogicalScrollConfig, LogicalScrollHandler } from "./logical";
44
16
  /**
45
- * Infinite-loop (wrap) configuration for the bounded handler (carousel, RFC-011).
46
- *
47
- * In wrap mode the logical position is never clamped to a maximum: instead it is
48
- * periodically folded back toward {@link home} by whole laps once it drifts more
49
- * than {@link thresholdLaps} laps away. Because the consumer maps virtual indices
50
- * to real items via `index % realTotal`, shifting the logical position (and
51
- * baseOffset) by a whole lap leaves the rendered items and their on-screen
52
- * positions unchanged — the loop is seamless. The content element is sized to the
53
- * runway exactly as in non-wrap mode, so large item counts never blow the
54
- * browser's element-size limit.
17
+ * Runway size as a multiple of the viewport: the content element is
18
+ * `containerSize × this` (capped at the total size), which gives the native
19
+ * scrollbar room to move before a rebase shifts the logical origin.
55
20
  */
56
- export interface WrapConfig {
57
- /** Current lap period in virtual px (`realTotal × stepSize`). */
58
- readonly lapSize: () => number;
59
- /**
60
- * Real items in one lap (e.g. the carousel's `realTotal`). The wrap handler
61
- * uses this to re-key mounted elements on a fold so the same DOM nodes
62
- * survive the virtual-index shift — only the key changed; paint did not.
63
- */
64
- readonly itemsPerLap: () => number;
65
- /** Logical position to fold back toward (the home lap). */
66
- readonly home: () => number;
67
- /** Fold the logical position back toward `home` once it drifts this many laps away. */
68
- readonly thresholdLaps: number;
69
- /** @internal Notify the wrap owner when its logical coordinates fold. */
70
- readonly onFold?: (shift: number) => void;
71
- }
72
- export interface BoundedScrollConfig {
73
- /** Logical row geometry for opt-in input providers (keyboard row steps). */
74
- readonly sizeCache?: Pick<SizeCache, "getSize" | "indexAtOffset" | "getTotalSize">;
75
- readonly state: EngineState;
76
- readonly viewport: HTMLElement;
77
- readonly content: HTMLElement;
78
- readonly isX: boolean;
79
- readonly wheelEnabled: boolean;
80
- readonly idleTimeout: number;
81
- readonly scrollTarget?: EventTarget;
82
- /** Main-axis padding folded into the virtual total (matches native content sizing). */
83
- readonly mainAxisPadding: number;
84
- /** Runway size as a multiple of the viewport (defaults to BOUNDED_RUNWAY_FACTOR). */
85
- readonly runwayFactor?: number;
86
- /** Infinite-loop config (carousel). When set, the logical position wraps by
87
- * whole laps toward `home` instead of clamping to a maximum. */
88
- readonly wrap?: WrapConfig;
89
- /**
90
- * Mounted row map. A wrap fold re-keys it in place so phase 2 finds the
91
- * same nodes; omitted when the handler is constructed without a viewport.
92
- */
93
- readonly rendered?: Map<number, HTMLElement>;
94
- /**
95
- * Class prefix for rewriting `id` / `aria-activedescendant` on a wrap fold.
96
- * Passed explicitly — a prefix containing `-content` cannot be recovered
97
- * from the content element's class name.
98
- */
99
- readonly classPrefix?: string;
100
- /**
101
- * Stripe class (`{prefix}-item--odd`). Re-toggled on a wrap fold when
102
- * `indexShift` is odd, so virtual-index parity survives the re-key.
103
- */
104
- readonly oddClass?: string;
105
- /** @internal Coordinate fold: shift core telemetry references before rendering. */
106
- readonly onFold?: (shift: number) => void;
107
- /** Called synchronously per frame — triggers the 2-phase pipeline. */
108
- readonly onFrame: () => void;
109
- /** Called when scrolling becomes idle. */
110
- readonly onIdle: () => void;
111
- }
112
- export declare function createBoundedScrollHandler(config: BoundedScrollConfig): BoundedScrollHandler;
21
+ export declare const RUNWAY_FACTOR = 2;
22
+ /** Rebase back when scrollTop drops below this fraction of the runway (away from the logical start). */
23
+ export declare const RUNWAY_REBASE_LOW = 0.25;
24
+ /** Rebase forward when scrollTop rises above this fraction of the runway (away from the logical end). */
25
+ export declare const RUNWAY_REBASE_HIGH = 0.75;
26
+ export declare function createRunwayHandler(config: LogicalScrollConfig): LogicalScrollHandler;
113
27
  //# sourceMappingURL=runway.d.ts.map
@@ -25,8 +25,8 @@ export interface EngineState {
25
25
  /** 1 = forward, -1 = backward, 0 = idle */
26
26
  scrollDirection: number;
27
27
  /**
28
- * Bounded logical-scroll origin (RFC-012). The virtual offset that maps to
29
- * native scrollTop=0. Items render at `getOffset(index) - baseOffset`.
28
+ * Logical-scroll origin (RFC-012): the virtual offset at the content
29
+ * element's start, moved by a logical handler (synthetic input, carousel runway). Items render at `getOffset(index) - baseOffset`.
30
30
  * Defaults to 0, which makes native mode byte-identical (`offset - 0`).
31
31
  */
32
32
  baseOffset: number;
@@ -90,8 +90,10 @@ export interface ScrollCapability extends ScrollAdapter {
90
90
  onContentSize?(px: number): void;
91
91
  }): void;
92
92
  setTarget(target: EventTarget): void;
93
- /** Request the bounded scroll handler in infinite-loop (wrap) mode (carousel). */
94
- setBoundedWrap(config: import("./runway").WrapConfig, createHandler: (config: import("./runway").BoundedScrollConfig) => import("./runway").BoundedScrollHandler): void;
93
+ /** Request a logical handler in infinite-loop (wrap) mode, built by `createHandler` (carousel). */
94
+ setWrap(config: import("./logical").WrapConfig, createHandler: (config: import("./logical").LogicalScrollConfig) => import("./logical").LogicalScrollHandler): void;
95
+ /** @deprecated Renamed `setWrap` in 3.0.x; kept for 3.0 plugins. */
96
+ setBoundedWrap(config: import("./logical").WrapConfig, createHandler: (config: import("./logical").LogicalScrollConfig) => import("./logical").LogicalScrollHandler): void;
95
97
  setToPosFn(fn: (index: number, sizeCache: import("./sizes").SizeCache, containerSize: number, totalItems: number, align: string) => number): void;
96
98
  setToIndexFn(fn: (index: number, align: string, behavior?: string, duration?: number, easing?: (t: number) => number) => void | false): void;
97
99
  onFrame(): void;