vlist 2.2.0 → 2.4.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/README.github.md +33 -15
- package/README.md +14 -12
- package/dist/constants.d.ts +28 -0
- package/dist/core/adapter.d.ts +92 -0
- package/dist/core/pipeline.d.ts +2 -0
- package/dist/core/runway.d.ts +82 -0
- package/dist/core/state.d.ts +6 -2
- package/dist/core/types.d.ts +31 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +1 -1
- package/dist/internals.d.ts +2 -2
- package/dist/internals.js +1 -1
- package/dist/plugins/a11y/plugin.d.ts +10 -1
- package/dist/plugins/carousel/engine.d.ts +56 -0
- package/dist/plugins/carousel/index.d.ts +7 -0
- package/dist/plugins/carousel/plugin.d.ts +44 -0
- package/dist/plugins/carousel/presets.d.ts +18 -0
- package/dist/plugins/grid/renderer.d.ts +1 -4
- package/dist/plugins/groups/sticky.d.ts +1 -1
- package/dist/plugins/masonry/renderer.d.ts +1 -1
- package/dist/plugins/scale/index.d.ts +0 -3
- package/dist/plugins/scale/plugin.d.ts +1 -18
- package/dist/plugins/scrollbar/index.d.ts +0 -1
- package/dist/plugins/search/index.d.ts +8 -0
- package/dist/plugins/search/match.d.ts +34 -0
- package/dist/plugins/search/plugin.d.ts +84 -0
- package/dist/plugins/search/searchbar.d.ts +57 -0
- package/dist/plugins/selection/plugin.d.ts +7 -0
- package/dist/plugins/table/renderer.d.ts +2 -5
- package/dist/rendering/index.d.ts +1 -6
- package/dist/size.json +1 -1
- package/dist/types.d.ts +31 -9
- package/dist/vlist-search.css +1 -0
- package/package.json +2 -1
- package/dist/plugins/scrollbar/controller.d.ts +0 -121
- package/dist/rendering/measured.d.ts +0 -52
- package/dist/rendering/renderer.d.ts +0 -113
- package/dist/rendering/scale.d.ts +0 -121
- package/dist/rendering/scroll.d.ts +0 -23
- package/dist/rendering/viewport.d.ts +0 -139
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist — Search Plugin (RFC-008, Phase 1)
|
|
3
|
+
*
|
|
4
|
+
* A ready-to-use search bar with client-side filtering, match navigation, and
|
|
5
|
+
* `<mark>` highlighting. Operates at the data layer (no `setRenderFn`), so it
|
|
6
|
+
* composes with any layout.
|
|
7
|
+
*
|
|
8
|
+
* - **filter** mode (default): virtually hides non-matching items via
|
|
9
|
+
* `setGetItemFn` + `setVirtualTotalFn` (non-destructive — clear restores).
|
|
10
|
+
* - **navigate** mode: keeps all items, scrolls between matches, highlights
|
|
11
|
+
* the current one.
|
|
12
|
+
*
|
|
13
|
+
* Phase 2 (server-side search via `data()`, column-aware, fuzzy, query syntax)
|
|
14
|
+
* is out of scope here.
|
|
15
|
+
*/
|
|
16
|
+
import type { VListItem } from "../../types";
|
|
17
|
+
import type { VListPlugin } from "../../core/types";
|
|
18
|
+
import { type FieldAccessor } from "./match";
|
|
19
|
+
/**
|
|
20
|
+
* Consumer-facing UI text for the search bar (RFC-010 — Externalized UI Text).
|
|
21
|
+
* This is the plugin's entire human-language surface: static accessible names
|
|
22
|
+
* plus the dynamic counter formatters. All fields are optional; unset fields
|
|
23
|
+
* fall back to {@link DEFAULT_SEARCH_TEXT} (English). Consumers localizing for a
|
|
24
|
+
* non-English document should override these *and* set `lang` on the page.
|
|
25
|
+
*/
|
|
26
|
+
export interface SearchText {
|
|
27
|
+
/** Input placeholder + accessible name. */
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
/** Accessible name for the clear (×) button. */
|
|
30
|
+
clear?: string;
|
|
31
|
+
/** Accessible name for the previous-match button (navigate mode). */
|
|
32
|
+
previous?: string;
|
|
33
|
+
/** Accessible name for the next-match button (navigate mode). */
|
|
34
|
+
next?: string;
|
|
35
|
+
/** Counter text when there are no matches. */
|
|
36
|
+
noResults?: string;
|
|
37
|
+
/** Counter text in filter mode — total match count. */
|
|
38
|
+
results?: (count: number) => string;
|
|
39
|
+
/** Counter text in navigate mode — current position within matches. */
|
|
40
|
+
position?: (current: number, total: number) => string;
|
|
41
|
+
/** Accessible name for the `role="search"` landmark. Unnamed when omitted. */
|
|
42
|
+
region?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Default English UI text for the search plugin. The single, documented,
|
|
46
|
+
* fully-overridable language surface (RFC-010): the only place human-readable
|
|
47
|
+
* strings live in the library.
|
|
48
|
+
*/
|
|
49
|
+
export declare const DEFAULT_SEARCH_TEXT: Required<SearchText>;
|
|
50
|
+
export interface SearchPluginConfig<T extends VListItem = VListItem> {
|
|
51
|
+
/** Hide non-matching items ("filter") or jump between matches ("navigate"). Default "filter". */
|
|
52
|
+
mode?: "filter" | "navigate";
|
|
53
|
+
/** Where to place the search bar. "none" = invisible, keyboard-only. Default "top". */
|
|
54
|
+
position?: "top" | "bottom" | "none";
|
|
55
|
+
/** Field(s) to search — a property key, accessor, or (default) all string values. */
|
|
56
|
+
field?: FieldAccessor<T>;
|
|
57
|
+
/** Case-sensitive matching. Default false. */
|
|
58
|
+
caseSensitive?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Highlight matched substrings in rendered rows by wrapping them in
|
|
61
|
+
* `<mark class="{prefix}-search-match">`. Default `true`.
|
|
62
|
+
*
|
|
63
|
+
* - `false` — disable highlighting.
|
|
64
|
+
* - `{ within }` — restrict highlighting to descendants matching the CSS
|
|
65
|
+
* selector (e.g. `".person__name"`) instead of the whole row. Useful when
|
|
66
|
+
* the query can coincidentally appear in fields you didn't search.
|
|
67
|
+
*/
|
|
68
|
+
highlight?: boolean | {
|
|
69
|
+
within?: string;
|
|
70
|
+
};
|
|
71
|
+
/** Minimum query length to trigger search. Default 1. */
|
|
72
|
+
minLength?: number;
|
|
73
|
+
/** Auto-close the search bar after N ms of inactivity (0 = never). Default 0. */
|
|
74
|
+
cancelTimeout?: number;
|
|
75
|
+
/** Visual style of the search bar. `"md3"` applies a Material Design 3 pill
|
|
76
|
+
* (requires the search stylesheet). Default `"default"`. */
|
|
77
|
+
variant?: "default" | "md3";
|
|
78
|
+
/** Consumer-supplied UI text / localization. Falls back to {@link DEFAULT_SEARCH_TEXT}. */
|
|
79
|
+
text?: SearchText;
|
|
80
|
+
}
|
|
81
|
+
export interface SearchPluginInstance<T extends VListItem = VListItem> extends VListPlugin<T> {
|
|
82
|
+
}
|
|
83
|
+
export declare function search<T extends VListItem = VListItem>(config?: SearchPluginConfig<T>): SearchPluginInstance<T>;
|
|
84
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist/search — Search bar DOM
|
|
3
|
+
*
|
|
4
|
+
* A modular, MD3-aligned structure (mirrors mtrl `_search.scss`):
|
|
5
|
+
*
|
|
6
|
+
* .{p}-search .{p}-search--bar .{p}-search--{top|bottom} (role="search")
|
|
7
|
+
* └── .{p}-search__container
|
|
8
|
+
* ├── span.{p}-search__leading-icon (magnifier — decorative)
|
|
9
|
+
* ├── .{p}-search__input-wrapper
|
|
10
|
+
* │ └── input.{p}-search__input
|
|
11
|
+
* ├── span.{p}-search__counter (match count — empty when idle)
|
|
12
|
+
* ├── button.{p}-search__nav-prev (navigate mode only)
|
|
13
|
+
* ├── button.{p}-search__nav-next (navigate mode only)
|
|
14
|
+
* └── button.{p}-search__clear-button (--hidden when empty)
|
|
15
|
+
*
|
|
16
|
+
* Per RFC-010, the bar carries no inline human-language text: all accessible
|
|
17
|
+
* names come from the `text` parameter (consumer-supplied, with English
|
|
18
|
+
* defaults resolved in the plugin). The leading magnifier is decorative
|
|
19
|
+
* (`aria-hidden`, not focusable, no accessible name) — the input is the
|
|
20
|
+
* meaningful control. The `role="search"` landmark is unnamed unless the
|
|
21
|
+
* consumer passes `text.region`.
|
|
22
|
+
*
|
|
23
|
+
* Invisible mode (`position: "none"`) renders no bar — the plugin drives the
|
|
24
|
+
* query from keystrokes only.
|
|
25
|
+
*/
|
|
26
|
+
export interface SearchBarCallbacks {
|
|
27
|
+
onInput(value: string): void;
|
|
28
|
+
onClear(): void;
|
|
29
|
+
onPrev(): void;
|
|
30
|
+
onNext(): void;
|
|
31
|
+
/** Forwarded keydown from the input (Enter / Escape / arrows). */
|
|
32
|
+
onKeydown(event: KeyboardEvent): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolved accessible names for the bar (consumer-supplied via the plugin's
|
|
36
|
+
* `text` config; the plugin fills English defaults). `region` is `""` when the
|
|
37
|
+
* search landmark should stay unnamed (the default).
|
|
38
|
+
*/
|
|
39
|
+
export interface SearchBarText {
|
|
40
|
+
placeholder: string;
|
|
41
|
+
clear: string;
|
|
42
|
+
previous: string;
|
|
43
|
+
next: string;
|
|
44
|
+
region: string;
|
|
45
|
+
}
|
|
46
|
+
export interface SearchBar {
|
|
47
|
+
readonly root: HTMLElement;
|
|
48
|
+
readonly input: HTMLInputElement;
|
|
49
|
+
setCounter(text: string): void;
|
|
50
|
+
/** Show/hide the prev/next buttons (navigate mode only). */
|
|
51
|
+
showNav(show: boolean): void;
|
|
52
|
+
focus(): void;
|
|
53
|
+
setValue(value: string): void;
|
|
54
|
+
destroy(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare const createSearchBar: (parent: HTMLElement, before: HTMLElement | null, classPrefix: string, position: "top" | "bottom", text: SearchBarText, listId: string | undefined, cb: SearchBarCallbacks) => SearchBar;
|
|
57
|
+
//# sourceMappingURL=searchbar.d.ts.map
|
|
@@ -11,6 +11,13 @@ export interface SelectionPluginConfig {
|
|
|
11
11
|
initial?: Array<string | number>;
|
|
12
12
|
followFocus?: boolean;
|
|
13
13
|
focusOnClick?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether this plugin handles arrow/Home/End/PageUp-Down/Enter/Space
|
|
16
|
+
* keyboard navigation. Default true. Set false to keep click-selection and
|
|
17
|
+
* the selection model while letting an outer system own keyboard navigation
|
|
18
|
+
* (e.g. a global, focus-independent hotkey layer).
|
|
19
|
+
*/
|
|
20
|
+
keyboard?: boolean;
|
|
14
21
|
}
|
|
15
22
|
export declare function selection<T extends VListItem = VListItem>(config?: SelectionPluginConfig): VListPlugin<T>;
|
|
16
23
|
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -31,14 +31,11 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import type { VListItem, Range } from "../../types";
|
|
33
33
|
import type { SizeCache } from "../../rendering/sizes";
|
|
34
|
-
import type { CompressionContext } from "../../rendering/renderer";
|
|
35
34
|
import type { TableLayout, TableColumn } from "./types";
|
|
36
35
|
/** Table renderer instance */
|
|
37
36
|
export interface TableRendererInstance<T extends VListItem = VListItem> {
|
|
38
37
|
/** Render rows for a range, with cell-based layout */
|
|
39
|
-
render: (items: T[], range: Range, selectedIds: Set<string | number>, focusedIndex: number
|
|
40
|
-
/** Update item positions (for compressed scrolling) */
|
|
41
|
-
updatePositions: (compressionCtx: CompressionContext) => void;
|
|
38
|
+
render: (items: T[], range: Range, selectedIds: Set<string | number>, focusedIndex: number) => void;
|
|
42
39
|
/** Update a single row (e.g., after selection change) */
|
|
43
40
|
updateItem: (index: number, item: T, isSelected: boolean, isFocused: boolean) => void;
|
|
44
41
|
/** Update only CSS classes on a rendered row */
|
|
@@ -68,5 +65,5 @@ export interface TableRendererInstance<T extends VListItem = VListItem> {
|
|
|
68
65
|
* @param getTotalItems - Function to get total item count (for ARIA)
|
|
69
66
|
* @returns TableRendererInstance
|
|
70
67
|
*/
|
|
71
|
-
export declare const createTableRenderer: <T extends VListItem = VListItem>(container: HTMLElement, getSizeCache: () => SizeCache, layout: TableLayout<T>, _columns: TableColumn<T>[], classPrefix: string, ariaIdPrefix: string, getTotalItems: () => number, striped?: boolean | "data" | "even" | "odd", stripeIndexFn?: () => (index: number) => number) => TableRendererInstance<T>;
|
|
68
|
+
export declare const createTableRenderer: <T extends VListItem = VListItem>(container: HTMLElement, getSizeCache: () => SizeCache, layout: TableLayout<T>, _columns: TableColumn<T>[], classPrefix: string, ariaIdPrefix: string, getTotalItems: () => number, striped?: boolean | "data" | "even" | "odd", stripeIndexFn?: () => (index: number) => number, getBaseOffset?: () => number) => TableRendererInstance<T>;
|
|
72
69
|
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vlist - Rendering Domain
|
|
3
|
-
*
|
|
3
|
+
* Size cache and DOM sorting shared by the core engine and layout plugins.
|
|
4
4
|
*/
|
|
5
5
|
export { createSizeCache, countVisibleItems, countItemsFittingFromBottom, getOffsetForVirtualIndex, type SizeCache, } from "./sizes";
|
|
6
|
-
export { createMeasuredSizeCache, type MeasuredSizeCache, } from "./measured";
|
|
7
6
|
export { sortRenderedDOM } from "./sort";
|
|
8
|
-
export { createRenderer, createDOMStructure, updateContentHeight, updateContentWidth, resolveContainer, getContainerDimensions, type Renderer, type DOMStructure, type CompressionContext, type CompressedPositionFn, type CompressionStateFn, } from "./renderer";
|
|
9
|
-
export { createViewportState, updateViewportState, updateViewportSize, updateViewportItems, calculateRenderRange, calculateTotalSize, calculateActualSize, calculateItemOffset, calculateScrollToIndex, clampScrollPosition, getScrollDirection, rangesEqual, isInRange, getRangeCount, rangeToIndices, diffRanges, getSimpleCompressionState, simpleVisibleRange, simpleScrollToIndex, NO_COMPRESSION, type CompressionState, type VisibleRangeFn, type ScrollToIndexFn, } from "./viewport";
|
|
10
|
-
export { MAX_VIRTUAL_SIZE, getCompressionState, getCompressionState as getCompression, needsCompression, getMaxItemsWithoutCompression, getCompressionInfo, calculateCompressedVisibleRange, calculateCompressedRenderRange, calculateCompressedItemPosition, calculateCompressedScrollToIndex, calculateIndexFromScrollPosition, } from "./scale";
|
|
11
|
-
export { scrollToFocus, scrollToFocusSimple, } from "./scroll";
|
|
12
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/size.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"base":{"minified":"
|
|
1
|
+
{"base":{"minified":"27.4","gzipped":"9.9","minBytes":28041,"gzBytes":10108},"a11y":{"minified":"30.5","gzipped":"11.0","minBytes":31188,"gzBytes":11254},"selection":{"minified":"36.3","gzipped":"12.5","minBytes":37221,"gzBytes":12828},"data":{"minified":"41.1","gzipped":"14.7","minBytes":42072,"gzBytes":15057},"scrollbar":{"minified":"34.5","gzipped":"11.9","minBytes":35288,"gzBytes":12152},"sortable":{"minified":"36.8","gzipped":"12.9","minBytes":37674,"gzBytes":13175},"groups":{"minified":"43.3","gzipped":"15.2","minBytes":44362,"gzBytes":15525},"page":{"minified":"29.8","gzipped":"10.6","minBytes":30527,"gzBytes":10892},"snapshots":{"minified":"30.6","gzipped":"11.0","minBytes":31385,"gzBytes":11289},"transition":{"minified":"34.1","gzipped":"11.7","minBytes":34965,"gzBytes":11981},"autosize":{"minified":"29.8","gzipped":"10.7","minBytes":30517,"gzBytes":10980},"grid":{"minified":"34.4","gzipped":"12.3","minBytes":35224,"gzBytes":12605},"table":{"minified":"45.6","gzipped":"15.6","minBytes":46658,"gzBytes":15992},"masonry":{"minified":"38.4","gzipped":"13.9","minBytes":39362,"gzBytes":14219},"tree":{"minified":"42.5","gzipped":"14.9","minBytes":43528,"gzBytes":15255},"search":{"minified":"36.5","gzipped":"13.0","minBytes":37354,"gzBytes":13354},"carousel":{"minified":"33.9","gzipped":"12.2","minBytes":34710,"gzBytes":12509}}
|
package/dist/types.d.ts
CHANGED
|
@@ -295,7 +295,7 @@ export interface ScrollConfig {
|
|
|
295
295
|
* Scrollbar mode (default: custom scrollbar).
|
|
296
296
|
*
|
|
297
297
|
* - *omitted* — Custom scrollbar (default), native scrollbar hidden via CSS
|
|
298
|
-
* - `'native'` — Browser native scrollbar
|
|
298
|
+
* - `'native'` — Browser native scrollbar
|
|
299
299
|
* - `'none'` — No scrollbar at all (native hidden, custom not created)
|
|
300
300
|
* - `ScrollbarOptions` — Custom scrollbar with fine-tuning options
|
|
301
301
|
*/
|
|
@@ -370,11 +370,23 @@ export interface TreeState {
|
|
|
370
370
|
isLastChild: boolean;
|
|
371
371
|
loading: boolean;
|
|
372
372
|
}
|
|
373
|
+
/** Search-specific state available in templates when the search plugin is active */
|
|
374
|
+
export interface SearchState {
|
|
375
|
+
/** Whether this item matches the current query */
|
|
376
|
+
matched: boolean;
|
|
377
|
+
/** Current search query (empty string when no search is active) */
|
|
378
|
+
query: string;
|
|
379
|
+
/** This item's position in the match list (-1 if not matched) */
|
|
380
|
+
matchIndex: number;
|
|
381
|
+
/** Whether this is the currently focused match (navigate mode) */
|
|
382
|
+
isCurrent: boolean;
|
|
383
|
+
}
|
|
373
384
|
/** State passed to template */
|
|
374
385
|
export interface ItemState {
|
|
375
386
|
selected: boolean;
|
|
376
387
|
focused: boolean;
|
|
377
388
|
tree?: TreeState;
|
|
389
|
+
search?: SearchState;
|
|
378
390
|
}
|
|
379
391
|
/** Selection mode */
|
|
380
392
|
export type SelectionMode = "none" | "single" | "multiple";
|
|
@@ -437,14 +449,8 @@ export interface ViewportState {
|
|
|
437
449
|
scrollPosition: number;
|
|
438
450
|
/** Container size along main axis (height for vertical, width for horizontal) */
|
|
439
451
|
containerSize: number;
|
|
440
|
-
/** Total content size
|
|
452
|
+
/** Total content size */
|
|
441
453
|
totalSize: number;
|
|
442
|
-
/** Actual total size without compression */
|
|
443
|
-
actualSize: number;
|
|
444
|
-
/** Whether compression is active */
|
|
445
|
-
isCompressed: boolean;
|
|
446
|
-
/** Compression ratio (1 = no compression, <1 = compressed) */
|
|
447
|
-
compressionRatio: number;
|
|
448
454
|
/** Visible item range */
|
|
449
455
|
visibleRange: Range;
|
|
450
456
|
/** Render range (includes overscan) */
|
|
@@ -463,7 +469,6 @@ export interface ErrorViewportSnapshot {
|
|
|
463
469
|
end: number;
|
|
464
470
|
};
|
|
465
471
|
totalItems: number;
|
|
466
|
-
isCompressed: boolean;
|
|
467
472
|
}
|
|
468
473
|
/** Event types and their payloads */
|
|
469
474
|
export interface VListEvents<T extends VListItem = VListItem> extends EventMap {
|
|
@@ -601,6 +606,23 @@ export interface VListEvents<T extends VListItem = VListItem> extends EventMap {
|
|
|
601
606
|
item: T;
|
|
602
607
|
error: unknown;
|
|
603
608
|
};
|
|
609
|
+
/** Search bar opened / focused */
|
|
610
|
+
"search:open": undefined;
|
|
611
|
+
/** Search bar closed and query cleared */
|
|
612
|
+
"search:close": undefined;
|
|
613
|
+
/** Query or results changed */
|
|
614
|
+
"search:change": {
|
|
615
|
+
query: string;
|
|
616
|
+
matches: number;
|
|
617
|
+
total: number;
|
|
618
|
+
};
|
|
619
|
+
/** Navigated to a match (navigate mode) */
|
|
620
|
+
"search:match": {
|
|
621
|
+
index: number;
|
|
622
|
+
item: T | undefined;
|
|
623
|
+
matchIndex: number;
|
|
624
|
+
matches: number;
|
|
625
|
+
};
|
|
604
626
|
/** Destroy — fired just before the instance is torn down */
|
|
605
627
|
destroy: undefined;
|
|
606
628
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--vlist-search-match-bg:#fde047;--vlist-search-match-current-bg:#f59e0b;--vlist-search-md3-surface:#ececf1;--vlist-search-md3-state:rgba(0,0,0,0.08)}[data-theme-mode="dark"]{--vlist-search-match-bg:#92710b;--vlist-search-match-current-bg:#b45309;--vlist-search-md3-surface:#2b3039;--vlist-search-md3-state:rgba(255,255,255,0.08)}@media (prefers-color-scheme:dark){:root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]){--vlist-search-match-bg:#92710b;--vlist-search-match-current-bg:#b45309;--vlist-search-md3-surface:#2b3039;--vlist-search-md3-state:rgba(255,255,255,0.08)}}.dark{--vlist-search-match-bg:#92710b;--vlist-search-match-current-bg:#b45309;--vlist-search-md3-surface:#2b3039;--vlist-search-md3-state:rgba(255,255,255,0.08)}.vlist--has-search{display:flex;flex-direction:column}.vlist--has-search .vlist-viewport{flex:1 1 auto;min-height:0}.vlist-search{flex:0 0 auto;box-sizing:border-box;display:flex}.vlist-search__container{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;padding:6px 8px;background-color:var(--vlist-bg);border-bottom:1px solid var(--vlist-border)}.vlist-search--bottom .vlist-search__container{border-bottom:none;border-top:1px solid var(--vlist-border)}.vlist-search__leading-icon,.vlist-search__nav-prev,.vlist-search__nav-next,.vlist-search__clear-button{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:4px;background:transparent;color:var(--vlist-text-muted);cursor:pointer}.vlist-search__leading-icon svg,.vlist-search__nav-prev svg,.vlist-search__nav-next svg,.vlist-search__clear-button svg{width:18px;height:18px;fill:currentColor}.vlist-search__leading-icon:hover,.vlist-search__nav-prev:hover,.vlist-search__nav-next:hover,.vlist-search__clear-button:hover{color:var(--vlist-text);background-color:var(--vlist-bg-hover)}.vlist-search__clear-button--hidden,.vlist-search__nav-prev--hidden,.vlist-search__nav-next--hidden{display:none}.vlist-search__input-wrapper{flex:1 1 auto;min-width:0;display:flex;align-items:center}.vlist-search__input{width:100%;min-width:0;padding:4px 8px;font:inherit;color:var(--vlist-text);background-color:var(--vlist-bg);border:1px solid var(--vlist-border);border-radius:4px;outline:none;box-sizing:border-box}.vlist-search__input:focus{border-color:var(--vlist-focus-ring)}.vlist-search__input:focus,.vlist-search__input:focus-visible{outline:none}.vlist-search__counter{flex:0 0 auto;font-size:0.75rem;color:var(--vlist-text-muted);white-space:nowrap}.vlist-search__counter:empty{display:none}.vlist-search-match{color:inherit;background-color:var(--vlist-search-match-bg);border-radius:2px}.vlist-search-match--current{background-color:var(--vlist-search-match-current-bg)}.vlist-search--md3{display:flex;align-items:center;box-sizing:border-box;height:56px;padding:0 12px;border-bottom:1px solid var(--vlist-border)}.vlist-search--md3 .vlist-search__container{flex:1 1 auto;height:40px;padding:0 16px;gap:0;border:none;border-radius:20px;overflow:hidden;background-color:var(--vlist-search-md3-surface);transition:background-color 200ms cubic-bezier(0.2,0,0,1)}.vlist-search--md3 .vlist-search__leading-icon{width:24px;height:24px;margin-right:16px;border-radius:50%;color:var(--vlist-text)}.vlist-search--md3 .vlist-search__leading-icon svg{width:24px;height:24px}.vlist-search--md3 .vlist-search__input{width:100%;height:100%;padding:8px 0;border:none;background:transparent;font:inherit;font-size:16px;line-height:24px;color:var(--vlist-text);caret-color:var(--vlist-focus-ring)}.vlist-search--md3 .vlist-search__input::placeholder{color:var(--vlist-text-muted)}.vlist-search--md3 .vlist-search__counter{margin-left:16px;font-size:12px;color:var(--vlist-text-muted)}.vlist-search--md3 .vlist-search__nav-prev,.vlist-search--md3 .vlist-search__nav-next,.vlist-search--md3 .vlist-search__clear-button{width:24px;height:24px;margin-left:16px;border-radius:50%;color:var(--vlist-text-muted);transition:background-color 200ms cubic-bezier(0.2,0,0,1)}.vlist-search--md3 .vlist-search__nav-prev svg,.vlist-search--md3 .vlist-search__nav-next svg,.vlist-search--md3 .vlist-search__clear-button svg{width:20px;height:20px}.vlist-search--md3 .vlist-search__leading-icon:hover,.vlist-search--md3 .vlist-search__nav-prev:hover,.vlist-search--md3 .vlist-search__nav-next:hover,.vlist-search--md3 .vlist-search__clear-button:hover{background-color:var(--vlist-search-md3-state)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vlist",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Lightweight, high-performance virtual list with zero dependencies",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Floor IO",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"./styles/table": "./dist/vlist-table.css",
|
|
46
46
|
"./styles/extras": "./dist/vlist-extras.css",
|
|
47
47
|
"./styles/tree": "./dist/vlist-tree.css",
|
|
48
|
+
"./styles/search": "./dist/vlist-search.css",
|
|
48
49
|
"./package.json": "./package.json"
|
|
49
50
|
},
|
|
50
51
|
"files": [
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist - Scroll Controller
|
|
3
|
-
* Handles both native scrolling and manual wheel-based scrolling for compressed lists
|
|
4
|
-
*
|
|
5
|
-
* When compression is active (large lists exceeding browser height limits),
|
|
6
|
-
* we switch from native scrolling to manual wheel event handling.
|
|
7
|
-
* This allows smooth scrolling through millions of items.
|
|
8
|
-
*/
|
|
9
|
-
import type { CompressionState } from "../../rendering/scale";
|
|
10
|
-
/** Scroll direction */
|
|
11
|
-
export type ScrollDirection = "up" | "down" | "left" | "right";
|
|
12
|
-
/** Scroll event data */
|
|
13
|
-
export interface ScrollEventData {
|
|
14
|
-
scrollTop: number;
|
|
15
|
-
direction: ScrollDirection;
|
|
16
|
-
velocity: number;
|
|
17
|
-
}
|
|
18
|
-
/** Scroll controller configuration */
|
|
19
|
-
export interface ScrollControllerConfig {
|
|
20
|
-
/** Enable compressed scroll mode (manual wheel handling) */
|
|
21
|
-
compressed?: boolean;
|
|
22
|
-
/** Compression state for calculating bounds */
|
|
23
|
-
compression?: CompressionState;
|
|
24
|
-
/**
|
|
25
|
-
* External scroll element for window/document scrolling.
|
|
26
|
-
* When set, the controller listens to this element's scroll events
|
|
27
|
-
* and computes list-relative positions from the viewport's bounding rect.
|
|
28
|
-
*/
|
|
29
|
-
scrollElement?: Window;
|
|
30
|
-
/** Allow mouse wheel to scroll (default: true) */
|
|
31
|
-
wheel?: boolean;
|
|
32
|
-
/** Wheel sensitivity multiplier (default: 1) */
|
|
33
|
-
sensitivity?: number;
|
|
34
|
-
/** Enable smooth scrolling interpolation */
|
|
35
|
-
smoothing?: boolean;
|
|
36
|
-
/** Scroll idle detection timeout in ms (default: 150) */
|
|
37
|
-
idleTimeout?: number;
|
|
38
|
-
/**
|
|
39
|
-
* Primary axis is X (horizontal scrolling mode).
|
|
40
|
-
* When true, the controller reads scrollLeft instead of scrollTop,
|
|
41
|
-
* uses clientWidth instead of clientHeight, and maps wheel deltaX.
|
|
42
|
-
*/
|
|
43
|
-
isX?: boolean;
|
|
44
|
-
/** Callback when scroll position changes */
|
|
45
|
-
onScroll?: (data: ScrollEventData) => void;
|
|
46
|
-
/** Callback when scrolling becomes idle */
|
|
47
|
-
onIdle?: () => void;
|
|
48
|
-
}
|
|
49
|
-
/** Scroll controller instance */
|
|
50
|
-
export interface ScrollController {
|
|
51
|
-
/** Get current scroll position */
|
|
52
|
-
getScrollTop: () => number;
|
|
53
|
-
/** Set scroll position */
|
|
54
|
-
scrollTo: (position: number, smooth?: boolean) => void;
|
|
55
|
-
/** Scroll by delta */
|
|
56
|
-
scrollBy: (delta: number) => void;
|
|
57
|
-
/** Check if at top */
|
|
58
|
-
isAtTop: () => boolean;
|
|
59
|
-
/** Check if at bottom */
|
|
60
|
-
isAtBottom: (threshold?: number) => boolean;
|
|
61
|
-
/** Get scroll percentage (0-1) */
|
|
62
|
-
getScrollPercentage: () => number;
|
|
63
|
-
/** Get current scroll velocity (px/ms, absolute value) */
|
|
64
|
-
getVelocity: () => number;
|
|
65
|
-
/**
|
|
66
|
-
* Check if the velocity tracker is actively tracking with enough samples.
|
|
67
|
-
* Returns false during ramp-up (first few frames of a new scroll gesture)
|
|
68
|
-
* when the tracker doesn't have enough samples yet.
|
|
69
|
-
*/
|
|
70
|
-
isTracking: () => boolean;
|
|
71
|
-
/** Check if currently scrolling */
|
|
72
|
-
isScrolling: () => boolean;
|
|
73
|
-
/** Update configuration (e.g., when compression state changes) */
|
|
74
|
-
updateConfig: (config: Partial<ScrollControllerConfig>) => void;
|
|
75
|
-
/** Enable compressed mode */
|
|
76
|
-
enableCompression: (compression: CompressionState) => void;
|
|
77
|
-
/** Disable compressed mode (revert to native scroll) */
|
|
78
|
-
disableCompression: () => void;
|
|
79
|
-
/** Check if compressed mode is active */
|
|
80
|
-
isCompressed: () => boolean;
|
|
81
|
-
/** Check if in window scroll mode */
|
|
82
|
-
isWindowMode: () => boolean;
|
|
83
|
-
/**
|
|
84
|
-
* Update the container height used for scroll calculations.
|
|
85
|
-
* In window mode, call this when the window resizes.
|
|
86
|
-
*/
|
|
87
|
-
updateContainerHeight: (height: number) => void;
|
|
88
|
-
/** Destroy and cleanup */
|
|
89
|
-
destroy: () => void;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Create a scroll controller for a viewport element
|
|
93
|
-
*
|
|
94
|
-
* Supports two modes:
|
|
95
|
-
* 1. Native scrolling (default) - uses browser's built-in scroll
|
|
96
|
-
* 2. Compressed scrolling - manual wheel handling for large lists
|
|
97
|
-
*/
|
|
98
|
-
export declare const createScrollController: (viewport: HTMLElement, config?: ScrollControllerConfig) => ScrollController;
|
|
99
|
-
/**
|
|
100
|
-
* Throttle scroll handler using requestAnimationFrame
|
|
101
|
-
*/
|
|
102
|
-
export declare const rafThrottle: <T extends (...args: any[]) => void>(fn: T) => ((...args: Parameters<T>) => void) & {
|
|
103
|
-
cancel: () => void;
|
|
104
|
-
};
|
|
105
|
-
/**
|
|
106
|
-
* Check if scroll position is at bottom
|
|
107
|
-
*/
|
|
108
|
-
export declare const isAtBottom: (scrollTop: number, scrollHeight: number, clientHeight: number, threshold?: number) => boolean;
|
|
109
|
-
/**
|
|
110
|
-
* Check if scroll position is at top
|
|
111
|
-
*/
|
|
112
|
-
export declare const isAtTop: (scrollTop: number, threshold?: number) => boolean;
|
|
113
|
-
/**
|
|
114
|
-
* Get scroll percentage (0-1)
|
|
115
|
-
*/
|
|
116
|
-
export declare const getScrollPercentage: (scrollTop: number, scrollHeight: number, clientHeight: number) => number;
|
|
117
|
-
/**
|
|
118
|
-
* Check if a range is visible in the scroll viewport
|
|
119
|
-
*/
|
|
120
|
-
export declare const isRangeVisible: (rangeStart: number, rangeEnd: number, visibleStart: number, visibleEnd: number) => boolean;
|
|
121
|
-
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist - Measured Size Cache
|
|
3
|
-
* Auto-measurement support for items with unknown sizes (Mode B)
|
|
4
|
-
*
|
|
5
|
-
* Wraps the existing variable SizeCache with measurement tracking.
|
|
6
|
-
* Once an item is measured, it behaves identically to Mode A (known size).
|
|
7
|
-
* Unmeasured items use the estimated size as a fallback.
|
|
8
|
-
*
|
|
9
|
-
* Fully axis-neutral: works identically for vertical (estimatedHeight)
|
|
10
|
-
* and horizontal (estimatedWidth) orientations. This cache stores plain
|
|
11
|
-
* numbers representing the main-axis dimension — it never knows whether
|
|
12
|
-
* those numbers are heights or widths. The axis-specific translation
|
|
13
|
-
* happens in builder/core.ts at the DOM boundary.
|
|
14
|
-
*
|
|
15
|
-
* Implements the SizeCache interface so all downstream code
|
|
16
|
-
* (viewport, scale, features) works unchanged.
|
|
17
|
-
*/
|
|
18
|
-
import type { SizeCache } from "./sizes";
|
|
19
|
-
/** Extended SizeCache with measurement tracking */
|
|
20
|
-
export interface MeasuredSizeCache extends SizeCache {
|
|
21
|
-
/** Record actual measured size for an item */
|
|
22
|
-
setMeasuredSize(index: number, size: number): void;
|
|
23
|
-
/** Check if an item has been measured */
|
|
24
|
-
isMeasured(index: number): boolean;
|
|
25
|
-
/** Get the estimated size (used for unmeasured items) */
|
|
26
|
-
getEstimatedSize(): number;
|
|
27
|
-
/** Number of items that have been measured */
|
|
28
|
-
measuredCount(): number;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Create a measured size cache for auto-measurement (Mode B)
|
|
32
|
-
*
|
|
33
|
-
* Works for both orientations:
|
|
34
|
-
* - Vertical: estimatedSize = estimatedHeight, measures block size
|
|
35
|
-
* - Horizontal: estimatedSize = estimatedWidth, measures inline size
|
|
36
|
-
*
|
|
37
|
-
* The cache itself is axis-neutral — it only stores numbers. The caller
|
|
38
|
-
* (builder/core.ts) is responsible for reading the correct axis from
|
|
39
|
-
* the config and from ResizeObserver entries (blockSize vs inlineSize).
|
|
40
|
-
*
|
|
41
|
-
* Internally maintains a Map of measured sizes keyed by item index.
|
|
42
|
-
* Unmeasured items fall back to the estimated size. The underlying
|
|
43
|
-
* prefix-sum array is rebuilt when measurements change.
|
|
44
|
-
*
|
|
45
|
-
* The size function fed into the variable SizeCache becomes:
|
|
46
|
-
* (index) => measuredSizes.has(index) ? measuredSizes.get(index) : estimatedSize
|
|
47
|
-
*
|
|
48
|
-
* This means all existing viewport, compression, and range calculations
|
|
49
|
-
* work unchanged — they only see a SizeCache with variable sizes.
|
|
50
|
-
*/
|
|
51
|
-
export declare const createMeasuredSizeCache: (estimatedSize: number, initialTotal: number) => MeasuredSizeCache;
|
|
52
|
-
//# sourceMappingURL=measured.d.ts.map
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist - DOM Rendering
|
|
3
|
-
* Efficient DOM rendering with element pooling
|
|
4
|
-
* Supports compression for large lists (1M+ items)
|
|
5
|
-
*/
|
|
6
|
-
import type { VListItem, ItemTemplate, Range } from "../types";
|
|
7
|
-
import type { CompressionState } from "./viewport";
|
|
8
|
-
import type { SizeCache } from "./sizes";
|
|
9
|
-
/**
|
|
10
|
-
* Optional compression position calculator.
|
|
11
|
-
* Injected by the monolithic factory or the withCompression feature.
|
|
12
|
-
* When not provided, the renderer uses simple sizeCache offsets.
|
|
13
|
-
*/
|
|
14
|
-
export type CompressedPositionFn = (index: number, scrollTop: number, sizeCache: SizeCache, totalItems: number, containerHeight: number, compression: CompressionState, rangeStart?: number) => number;
|
|
15
|
-
/**
|
|
16
|
-
* Optional compression state getter.
|
|
17
|
-
* Injected by the monolithic factory or the withCompression feature.
|
|
18
|
-
* When not provided, the renderer assumes no compression.
|
|
19
|
-
*/
|
|
20
|
-
export type CompressionStateFn = (totalItems: number, sizeCache: SizeCache) => CompressionState;
|
|
21
|
-
/** Element pool for recycling DOM elements */
|
|
22
|
-
export interface ElementPool {
|
|
23
|
-
/** Get an element from the pool (or create new) */
|
|
24
|
-
acquire: () => HTMLElement;
|
|
25
|
-
/** Return an element to the pool */
|
|
26
|
-
release: (element: HTMLElement) => void;
|
|
27
|
-
/** Clear the pool */
|
|
28
|
-
clear: () => void;
|
|
29
|
-
/** Get pool statistics */
|
|
30
|
-
stats: () => {
|
|
31
|
-
poolSize: number;
|
|
32
|
-
created: number;
|
|
33
|
-
reused: number;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/** Compression context for positioning */
|
|
37
|
-
export interface CompressionContext {
|
|
38
|
-
scrollPosition: number;
|
|
39
|
-
totalItems: number;
|
|
40
|
-
containerSize: number;
|
|
41
|
-
rangeStart: number;
|
|
42
|
-
/** Pre-computed compression state (includes force flag) */
|
|
43
|
-
compression?: CompressionState;
|
|
44
|
-
}
|
|
45
|
-
/** DOM structure created by createDOMStructure */
|
|
46
|
-
export interface DOMStructure {
|
|
47
|
-
root: HTMLElement;
|
|
48
|
-
viewport: HTMLElement;
|
|
49
|
-
content: HTMLElement;
|
|
50
|
-
items: HTMLElement;
|
|
51
|
-
/** Visually-hidden live region for screen reader announcements */
|
|
52
|
-
liveRegion: HTMLElement;
|
|
53
|
-
}
|
|
54
|
-
/** Renderer instance */
|
|
55
|
-
export interface Renderer<T extends VListItem = VListItem> {
|
|
56
|
-
/** Render items for a range */
|
|
57
|
-
render: (items: T[], range: Range, selectedIds: Set<string | number>, focusedIndex: number, compressionCtx?: CompressionContext) => void;
|
|
58
|
-
/** Update item positions (for compressed scrolling) */
|
|
59
|
-
updatePositions: (compressionCtx: CompressionContext) => void;
|
|
60
|
-
/** Update a single item */
|
|
61
|
-
updateItem: (index: number, item: T, isSelected: boolean, isFocused: boolean) => void;
|
|
62
|
-
/** Update only CSS classes on a rendered item (no template re-evaluation) */
|
|
63
|
-
updateItemClasses: (index: number, isSelected: boolean, isFocused: boolean) => void;
|
|
64
|
-
/** Get rendered item element by index */
|
|
65
|
-
getElement: (index: number) => HTMLElement | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* Reorder DOM children to match logical item order (by data-index).
|
|
68
|
-
* Called on scroll idle so screen readers encounter items in the correct
|
|
69
|
-
* sequence. Items are position:absolute so visual layout is unaffected.
|
|
70
|
-
*/
|
|
71
|
-
sortDOM: () => void;
|
|
72
|
-
/** Clear all rendered items */
|
|
73
|
-
clear: () => void;
|
|
74
|
-
/** Destroy renderer and cleanup */
|
|
75
|
-
destroy: () => void;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Create an element pool for recycling DOM elements
|
|
79
|
-
* Reduces garbage collection and improves performance
|
|
80
|
-
*/
|
|
81
|
-
export declare const createElementPool: (tagName?: string, maxSize?: number) => ElementPool;
|
|
82
|
-
/**
|
|
83
|
-
* Create a renderer for managing DOM elements
|
|
84
|
-
* Supports compression for large lists
|
|
85
|
-
*/
|
|
86
|
-
export declare const createRenderer: <T extends VListItem = VListItem>(itemsContainer: HTMLElement, template: ItemTemplate<T>, sizeCache: SizeCache, classPrefix: string, totalItemsGetter?: () => number, ariaIdPrefix?: string, isX?: boolean, crossAxisSize?: number, compressionFns?: {
|
|
87
|
-
getState: CompressionStateFn;
|
|
88
|
-
getPosition: CompressedPositionFn;
|
|
89
|
-
}, striped?: boolean | "data" | "even" | "odd", stripeIndexFn?: () => (index: number) => number, ariaPosInSetGetter?: (layoutIndex: number) => number, interactive?: boolean) => Renderer<T>;
|
|
90
|
-
/**
|
|
91
|
-
* Create the vlist DOM structure
|
|
92
|
-
*/
|
|
93
|
-
export declare const createDOMStructure: (container: HTMLElement, classPrefix: string, ariaLabel?: string, isX?: boolean, interactive?: boolean) => DOMStructure;
|
|
94
|
-
/**
|
|
95
|
-
* Update content height for virtual scrolling
|
|
96
|
-
*/
|
|
97
|
-
export declare const updateContentHeight: (content: HTMLElement, totalHeight: number) => void;
|
|
98
|
-
/**
|
|
99
|
-
* Update content width for horizontal virtual scrolling
|
|
100
|
-
*/
|
|
101
|
-
export declare const updateContentWidth: (content: HTMLElement, totalWidth: number) => void;
|
|
102
|
-
/**
|
|
103
|
-
* Get container dimensions
|
|
104
|
-
*/
|
|
105
|
-
export declare const getContainerDimensions: (viewport: HTMLElement) => {
|
|
106
|
-
width: number;
|
|
107
|
-
height: number;
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* Resolve container from selector or element
|
|
111
|
-
*/
|
|
112
|
-
export declare const resolveContainer: (container: HTMLElement | string) => HTMLElement;
|
|
113
|
-
//# sourceMappingURL=renderer.d.ts.map
|