ark-floating-scroll 1.0.0 → 1.1.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.md +36 -3
- package/dist/index.d.mts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ark-floating-scroll
|
|
2
2
|
|
|
3
3
|
A high-performance virtualized list component for React. Renders only the items visible in the viewport, dramatically reducing DOM nodes and improving performance for long lists.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install ark-floating-scroll
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
**Peer dependencies:** React ≥ 16.8.0
|
|
@@ -13,7 +13,7 @@ npm install optimised-scroll
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
import { VirtualList } from "
|
|
16
|
+
import { VirtualList } from "ark-floating-scroll";
|
|
17
17
|
|
|
18
18
|
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);
|
|
19
19
|
|
|
@@ -39,6 +39,7 @@ function App() {
|
|
|
39
39
|
|
|
40
40
|
| Prop | Type | Default | Description |
|
|
41
41
|
|------|------|---------|-------------|
|
|
42
|
+
| `ref` | `Ref<VirtualListHandle>` | — | Imperative handle with `scrollToIndex` |
|
|
42
43
|
| `items` | `T[]` | *required* | Array of items to render |
|
|
43
44
|
| `itemHeight` | `number` | *required* | Fixed height of each item in pixels |
|
|
44
45
|
| `height` | `number` | `400` | Height of the scrollable container |
|
|
@@ -54,6 +55,36 @@ function App() {
|
|
|
54
55
|
2. **Range Calculation** — Computes `startIndex` and `endIndex` from `scrollTop / itemHeight`, adding an `overscan` buffer.
|
|
55
56
|
3. **DOM Recycling** — Only the visible slice of items is mounted in the DOM. Items outside the viewport are unmounted, keeping memory usage constant regardless of list size.
|
|
56
57
|
|
|
58
|
+
## Scroll to Index
|
|
59
|
+
|
|
60
|
+
Use an imperative ref to programmatically scroll to any item:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useRef } from "react";
|
|
64
|
+
import { VirtualList, VirtualListHandle } from "ark-floating-scroll";
|
|
65
|
+
|
|
66
|
+
function App() {
|
|
67
|
+
const listRef = useRef<VirtualListHandle>(null);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<button onClick={() => listRef.current?.scrollToIndex(500, "smooth")}>
|
|
72
|
+
Go to item 500
|
|
73
|
+
</button>
|
|
74
|
+
<VirtualList
|
|
75
|
+
ref={listRef}
|
|
76
|
+
items={items}
|
|
77
|
+
itemHeight={40}
|
|
78
|
+
height={500}
|
|
79
|
+
renderItem={(item) => <div>{item}</div>}
|
|
80
|
+
/>
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`scrollToIndex(index, behavior?)` accepts an optional `ScrollBehavior` (`"auto"` or `"smooth"`). Defaults to `"auto"` (instant).
|
|
87
|
+
|
|
57
88
|
## TypeScript
|
|
58
89
|
|
|
59
90
|
Full type definitions are included. The component is generic — your `items` type flows through to `renderItem`:
|
|
@@ -85,3 +116,5 @@ Open DevTools → Elements to confirm only ~20-30 DOM nodes exist regardless of
|
|
|
85
116
|
## License
|
|
86
117
|
|
|
87
118
|
MIT
|
|
119
|
+
|
|
120
|
+
# ark-floating-scroll
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode, CSSProperties, Ref } from 'react';
|
|
2
2
|
|
|
3
|
+
interface VirtualListHandle {
|
|
4
|
+
/** Scroll to bring the item at `index` into view */
|
|
5
|
+
scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;
|
|
6
|
+
}
|
|
3
7
|
interface VirtualListProps<T> {
|
|
4
8
|
/** Array of items to render */
|
|
5
9
|
items: T[];
|
|
@@ -18,10 +22,8 @@ interface VirtualListProps<T> {
|
|
|
18
22
|
/** Inline styles for the outer container */
|
|
19
23
|
style?: CSSProperties;
|
|
20
24
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
declare function VirtualList<T>({ items, itemHeight, height, width, overscan, renderItem, className, style, }: VirtualListProps<T>): React.JSX.Element;
|
|
25
|
+
declare const VirtualList: <T>(props: VirtualListProps<T> & {
|
|
26
|
+
ref?: Ref<VirtualListHandle>;
|
|
27
|
+
}) => ReactNode;
|
|
26
28
|
|
|
27
|
-
export { VirtualList, type VirtualListProps };
|
|
29
|
+
export { VirtualList, type VirtualListHandle, type VirtualListProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode, CSSProperties, Ref } from 'react';
|
|
2
2
|
|
|
3
|
+
interface VirtualListHandle {
|
|
4
|
+
/** Scroll to bring the item at `index` into view */
|
|
5
|
+
scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;
|
|
6
|
+
}
|
|
3
7
|
interface VirtualListProps<T> {
|
|
4
8
|
/** Array of items to render */
|
|
5
9
|
items: T[];
|
|
@@ -18,10 +22,8 @@ interface VirtualListProps<T> {
|
|
|
18
22
|
/** Inline styles for the outer container */
|
|
19
23
|
style?: CSSProperties;
|
|
20
24
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
declare function VirtualList<T>({ items, itemHeight, height, width, overscan, renderItem, className, style, }: VirtualListProps<T>): React.JSX.Element;
|
|
25
|
+
declare const VirtualList: <T>(props: VirtualListProps<T> & {
|
|
26
|
+
ref?: Ref<VirtualListHandle>;
|
|
27
|
+
}) => ReactNode;
|
|
26
28
|
|
|
27
|
-
export { VirtualList, type VirtualListProps };
|
|
29
|
+
export { VirtualList, type VirtualListHandle, type VirtualListProps };
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var import_react = __toESM(require("react"));
|
|
|
39
39
|
function defaultRenderItem(item, index) {
|
|
40
40
|
return /* @__PURE__ */ import_react.default.createElement("div", null, String(item));
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function VirtualListInner({
|
|
43
43
|
items,
|
|
44
44
|
itemHeight,
|
|
45
45
|
height = 400,
|
|
@@ -48,11 +48,19 @@ function VirtualList({
|
|
|
48
48
|
renderItem = defaultRenderItem,
|
|
49
49
|
className,
|
|
50
50
|
style
|
|
51
|
-
}) {
|
|
51
|
+
}, ref) {
|
|
52
52
|
const containerRef = (0, import_react.useRef)(null);
|
|
53
53
|
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
54
54
|
const rafRef = (0, import_react.useRef)(null);
|
|
55
55
|
const totalHeight = items.length * itemHeight;
|
|
56
|
+
(0, import_react.useImperativeHandle)(ref, () => ({
|
|
57
|
+
scrollToIndex(index, behavior = "auto") {
|
|
58
|
+
const container = containerRef.current;
|
|
59
|
+
if (!container) return;
|
|
60
|
+
const clamped = Math.max(0, Math.min(index, items.length - 1));
|
|
61
|
+
container.scrollTo({ top: clamped * itemHeight, behavior });
|
|
62
|
+
}
|
|
63
|
+
}), [items.length, itemHeight]);
|
|
56
64
|
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
|
|
57
65
|
const visibleCount = Math.ceil(height / itemHeight);
|
|
58
66
|
const endIndex = Math.min(
|
|
@@ -126,6 +134,7 @@ function VirtualList({
|
|
|
126
134
|
)
|
|
127
135
|
);
|
|
128
136
|
}
|
|
137
|
+
var VirtualList = (0, import_react.forwardRef)(VirtualListInner);
|
|
129
138
|
// Annotate the CommonJS export names for ESM import in node:
|
|
130
139
|
0 && (module.exports = {
|
|
131
140
|
VirtualList
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/VirtualList.tsx"],"sourcesContent":["export { VirtualList } from \"./VirtualList\";\nexport type { VirtualListProps } from \"./VirtualList\";\n","import React, {\n useState,\n useRef,\n useCallback,\n useEffect,\n CSSProperties,\n ReactNode,\n} from \"react\";\n\nexport interface VirtualListProps<T> {\n /** Array of items to render */\n items: T[];\n /** Fixed height of each item in pixels */\n itemHeight: number;\n /** Height of the scrollable container in pixels (default: 400) */\n height?: number;\n /** Width of the scrollable container (default: \"100%\") */\n width?: number | string;\n /** Number of extra items to render above/below the viewport (default: 5) */\n overscan?: number;\n /** Custom render function for each item */\n renderItem?: (item: T, index: number) => ReactNode;\n /** CSS class for the outer container */\n className?: string;\n /** Inline styles for the outer container */\n style?: CSSProperties;\n}\n\nfunction defaultRenderItem<T>(item: T, index: number): ReactNode {\n return <div>{String(item)}</div>;\n}\n\n/**\n * A virtualized list component that renders only the items visible\n * in the viewport, plus a configurable overscan buffer.\n */\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/VirtualList.tsx"],"sourcesContent":["export { VirtualList } from \"./VirtualList\";\nexport type { VirtualListProps, VirtualListHandle } from \"./VirtualList\";\n","import React, {\n useState,\n useRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n forwardRef,\n CSSProperties,\n ReactNode,\n Ref,\n} from \"react\";\n\nexport interface VirtualListHandle {\n /** Scroll to bring the item at `index` into view */\n scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;\n}\n\nexport interface VirtualListProps<T> {\n /** Array of items to render */\n items: T[];\n /** Fixed height of each item in pixels */\n itemHeight: number;\n /** Height of the scrollable container in pixels (default: 400) */\n height?: number;\n /** Width of the scrollable container (default: \"100%\") */\n width?: number | string;\n /** Number of extra items to render above/below the viewport (default: 5) */\n overscan?: number;\n /** Custom render function for each item */\n renderItem?: (item: T, index: number) => ReactNode;\n /** CSS class for the outer container */\n className?: string;\n /** Inline styles for the outer container */\n style?: CSSProperties;\n}\n\nfunction defaultRenderItem<T>(item: T, index: number): ReactNode {\n return <div>{String(item)}</div>;\n}\n\n/**\n * A virtualized list component that renders only the items visible\n * in the viewport, plus a configurable overscan buffer.\n */\nfunction VirtualListInner<T>(\n {\n items,\n itemHeight,\n height = 400,\n width = \"100%\",\n overscan = 5,\n renderItem = defaultRenderItem,\n className,\n style,\n }: VirtualListProps<T>,\n ref: Ref<VirtualListHandle>\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scrollTop, setScrollTop] = useState(0);\n const rafRef = useRef<number | null>(null);\n\n const totalHeight = items.length * itemHeight;\n\n // Expose scrollToIndex to parent via ref\n useImperativeHandle(ref, () => ({\n scrollToIndex(index: number, behavior: ScrollBehavior = \"auto\") {\n const container = containerRef.current;\n if (!container) return;\n const clamped = Math.max(0, Math.min(index, items.length - 1));\n container.scrollTo({ top: clamped * itemHeight, behavior });\n },\n }), [items.length, itemHeight]);\n\n // Calculate visible range with overscan buffer\n const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);\n const visibleCount = Math.ceil(height / itemHeight);\n const endIndex = Math.min(\n items.length,\n Math.floor(scrollTop / itemHeight) + visibleCount + overscan\n );\n\n const handleScroll = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Use rAF to throttle scroll updates for smooth performance\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n }\n\n rafRef.current = requestAnimationFrame(() => {\n setScrollTop(container.scrollTop);\n rafRef.current = null;\n });\n }, []);\n\n // Clean up rAF on unmount\n useEffect(() => {\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n }\n };\n }, []);\n\n // Build only the visible items\n const visibleItems: ReactNode[] = [];\n for (let i = startIndex; i < endIndex; i++) {\n visibleItems.push(\n <div\n key={i}\n style={{\n position: \"absolute\",\n top: i * itemHeight,\n left: 0,\n right: 0,\n height: itemHeight,\n overflow: \"hidden\",\n }}\n >\n {renderItem(items[i], i)}\n </div>\n );\n }\n\n const containerStyle: CSSProperties = {\n overflow: \"auto\",\n height,\n width,\n position: \"relative\",\n willChange: \"transform\",\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n onScroll={handleScroll}\n >\n {/* Spacer div to maintain correct scrollbar size */}\n <div\n style={{\n height: totalHeight,\n position: \"relative\",\n width: \"100%\",\n }}\n >\n {visibleItems}\n </div>\n </div>\n );\n}\n\n// forwardRef wrapper that preserves the generic type parameter\nexport const VirtualList = forwardRef(VirtualListInner) as <T>(\n props: VirtualListProps<T> & { ref?: Ref<VirtualListHandle> }\n) => ReactNode;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAUO;AA0BP,SAAS,kBAAqB,MAAS,OAA0B;AAC/D,SAAO,6BAAAA,QAAA,cAAC,aAAK,OAAO,IAAI,CAAE;AAC5B;AAMA,SAAS,iBACP;AAAA,EACE;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb;AAAA,EACA;AACF,GACA,KACA;AACA,QAAM,mBAAe,qBAAuB,IAAI;AAChD,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,CAAC;AAC5C,QAAM,aAAS,qBAAsB,IAAI;AAEzC,QAAM,cAAc,MAAM,SAAS;AAGnC,wCAAoB,KAAK,OAAO;AAAA,IAC9B,cAAc,OAAe,WAA2B,QAAQ;AAC9D,YAAM,YAAY,aAAa;AAC/B,UAAI,CAAC,UAAW;AAChB,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM,SAAS,CAAC,CAAC;AAC7D,gBAAU,SAAS,EAAE,KAAK,UAAU,YAAY,SAAS,CAAC;AAAA,IAC5D;AAAA,EACF,IAAI,CAAC,MAAM,QAAQ,UAAU,CAAC;AAG9B,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,UAAU,IAAI,QAAQ;AAC5E,QAAM,eAAe,KAAK,KAAK,SAAS,UAAU;AAClD,QAAM,WAAW,KAAK;AAAA,IACpB,MAAM;AAAA,IACN,KAAK,MAAM,YAAY,UAAU,IAAI,eAAe;AAAA,EACtD;AAEA,QAAM,mBAAe,0BAAY,MAAM;AACrC,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAGhB,QAAI,OAAO,YAAY,MAAM;AAC3B,2BAAqB,OAAO,OAAO;AAAA,IACrC;AAEA,WAAO,UAAU,sBAAsB,MAAM;AAC3C,mBAAa,UAAU,SAAS;AAChC,aAAO,UAAU;AAAA,IACnB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,8BAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,OAAO,YAAY,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AAAA,MACrC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,eAA4B,CAAC;AACnC,WAAS,IAAI,YAAY,IAAI,UAAU,KAAK;AAC1C,iBAAa;AAAA,MACX,6BAAAA,QAAA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,OAAO;AAAA,YACL,UAAU;AAAA,YACV,KAAK,IAAI;AAAA,YACT,MAAM;AAAA,YACN,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,UAAU;AAAA,UACZ;AAAA;AAAA,QAEC,WAAW,MAAM,CAAC,GAAG,CAAC;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,iBAAgC;AAAA,IACpC,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,GAAG;AAAA,EACL;AAEA,SACE,6BAAAA,QAAA;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA;AAAA,IAGV,6BAAAA,QAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,OAAO;AAAA,QACT;AAAA;AAAA,MAEC;AAAA,IACH;AAAA,EACF;AAEJ;AAGO,IAAM,kBAAc,yBAAW,gBAAgB;","names":["React"]}
|
package/dist/index.mjs
CHANGED
|
@@ -3,12 +3,14 @@ import React, {
|
|
|
3
3
|
useState,
|
|
4
4
|
useRef,
|
|
5
5
|
useCallback,
|
|
6
|
-
useEffect
|
|
6
|
+
useEffect,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
forwardRef
|
|
7
9
|
} from "react";
|
|
8
10
|
function defaultRenderItem(item, index) {
|
|
9
11
|
return /* @__PURE__ */ React.createElement("div", null, String(item));
|
|
10
12
|
}
|
|
11
|
-
function
|
|
13
|
+
function VirtualListInner({
|
|
12
14
|
items,
|
|
13
15
|
itemHeight,
|
|
14
16
|
height = 400,
|
|
@@ -17,11 +19,19 @@ function VirtualList({
|
|
|
17
19
|
renderItem = defaultRenderItem,
|
|
18
20
|
className,
|
|
19
21
|
style
|
|
20
|
-
}) {
|
|
22
|
+
}, ref) {
|
|
21
23
|
const containerRef = useRef(null);
|
|
22
24
|
const [scrollTop, setScrollTop] = useState(0);
|
|
23
25
|
const rafRef = useRef(null);
|
|
24
26
|
const totalHeight = items.length * itemHeight;
|
|
27
|
+
useImperativeHandle(ref, () => ({
|
|
28
|
+
scrollToIndex(index, behavior = "auto") {
|
|
29
|
+
const container = containerRef.current;
|
|
30
|
+
if (!container) return;
|
|
31
|
+
const clamped = Math.max(0, Math.min(index, items.length - 1));
|
|
32
|
+
container.scrollTo({ top: clamped * itemHeight, behavior });
|
|
33
|
+
}
|
|
34
|
+
}), [items.length, itemHeight]);
|
|
25
35
|
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
|
|
26
36
|
const visibleCount = Math.ceil(height / itemHeight);
|
|
27
37
|
const endIndex = Math.min(
|
|
@@ -95,6 +105,7 @@ function VirtualList({
|
|
|
95
105
|
)
|
|
96
106
|
);
|
|
97
107
|
}
|
|
108
|
+
var VirtualList = forwardRef(VirtualListInner);
|
|
98
109
|
export {
|
|
99
110
|
VirtualList
|
|
100
111
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/VirtualList.tsx"],"sourcesContent":["import React, {\n useState,\n useRef,\n useCallback,\n useEffect,\n CSSProperties,\n ReactNode,\n} from \"react\";\n\nexport interface VirtualListProps<T> {\n /** Array of items to render */\n items: T[];\n /** Fixed height of each item in pixels */\n itemHeight: number;\n /** Height of the scrollable container in pixels (default: 400) */\n height?: number;\n /** Width of the scrollable container (default: \"100%\") */\n width?: number | string;\n /** Number of extra items to render above/below the viewport (default: 5) */\n overscan?: number;\n /** Custom render function for each item */\n renderItem?: (item: T, index: number) => ReactNode;\n /** CSS class for the outer container */\n className?: string;\n /** Inline styles for the outer container */\n style?: CSSProperties;\n}\n\nfunction defaultRenderItem<T>(item: T, index: number): ReactNode {\n return <div>{String(item)}</div>;\n}\n\n/**\n * A virtualized list component that renders only the items visible\n * in the viewport, plus a configurable overscan buffer.\n */\
|
|
1
|
+
{"version":3,"sources":["../src/VirtualList.tsx"],"sourcesContent":["import React, {\n useState,\n useRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n forwardRef,\n CSSProperties,\n ReactNode,\n Ref,\n} from \"react\";\n\nexport interface VirtualListHandle {\n /** Scroll to bring the item at `index` into view */\n scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;\n}\n\nexport interface VirtualListProps<T> {\n /** Array of items to render */\n items: T[];\n /** Fixed height of each item in pixels */\n itemHeight: number;\n /** Height of the scrollable container in pixels (default: 400) */\n height?: number;\n /** Width of the scrollable container (default: \"100%\") */\n width?: number | string;\n /** Number of extra items to render above/below the viewport (default: 5) */\n overscan?: number;\n /** Custom render function for each item */\n renderItem?: (item: T, index: number) => ReactNode;\n /** CSS class for the outer container */\n className?: string;\n /** Inline styles for the outer container */\n style?: CSSProperties;\n}\n\nfunction defaultRenderItem<T>(item: T, index: number): ReactNode {\n return <div>{String(item)}</div>;\n}\n\n/**\n * A virtualized list component that renders only the items visible\n * in the viewport, plus a configurable overscan buffer.\n */\nfunction VirtualListInner<T>(\n {\n items,\n itemHeight,\n height = 400,\n width = \"100%\",\n overscan = 5,\n renderItem = defaultRenderItem,\n className,\n style,\n }: VirtualListProps<T>,\n ref: Ref<VirtualListHandle>\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scrollTop, setScrollTop] = useState(0);\n const rafRef = useRef<number | null>(null);\n\n const totalHeight = items.length * itemHeight;\n\n // Expose scrollToIndex to parent via ref\n useImperativeHandle(ref, () => ({\n scrollToIndex(index: number, behavior: ScrollBehavior = \"auto\") {\n const container = containerRef.current;\n if (!container) return;\n const clamped = Math.max(0, Math.min(index, items.length - 1));\n container.scrollTo({ top: clamped * itemHeight, behavior });\n },\n }), [items.length, itemHeight]);\n\n // Calculate visible range with overscan buffer\n const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);\n const visibleCount = Math.ceil(height / itemHeight);\n const endIndex = Math.min(\n items.length,\n Math.floor(scrollTop / itemHeight) + visibleCount + overscan\n );\n\n const handleScroll = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Use rAF to throttle scroll updates for smooth performance\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n }\n\n rafRef.current = requestAnimationFrame(() => {\n setScrollTop(container.scrollTop);\n rafRef.current = null;\n });\n }, []);\n\n // Clean up rAF on unmount\n useEffect(() => {\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n }\n };\n }, []);\n\n // Build only the visible items\n const visibleItems: ReactNode[] = [];\n for (let i = startIndex; i < endIndex; i++) {\n visibleItems.push(\n <div\n key={i}\n style={{\n position: \"absolute\",\n top: i * itemHeight,\n left: 0,\n right: 0,\n height: itemHeight,\n overflow: \"hidden\",\n }}\n >\n {renderItem(items[i], i)}\n </div>\n );\n }\n\n const containerStyle: CSSProperties = {\n overflow: \"auto\",\n height,\n width,\n position: \"relative\",\n willChange: \"transform\",\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n onScroll={handleScroll}\n >\n {/* Spacer div to maintain correct scrollbar size */}\n <div\n style={{\n height: totalHeight,\n position: \"relative\",\n width: \"100%\",\n }}\n >\n {visibleItems}\n </div>\n </div>\n );\n}\n\n// forwardRef wrapper that preserves the generic type parameter\nexport const VirtualList = forwardRef(VirtualListInner) as <T>(\n props: VirtualListProps<T> & { ref?: Ref<VirtualListHandle> }\n) => ReactNode;\n"],"mappings":";AAAA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AA0BP,SAAS,kBAAqB,MAAS,OAA0B;AAC/D,SAAO,oCAAC,aAAK,OAAO,IAAI,CAAE;AAC5B;AAMA,SAAS,iBACP;AAAA,EACE;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb;AAAA,EACA;AACF,GACA,KACA;AACA,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,CAAC;AAC5C,QAAM,SAAS,OAAsB,IAAI;AAEzC,QAAM,cAAc,MAAM,SAAS;AAGnC,sBAAoB,KAAK,OAAO;AAAA,IAC9B,cAAc,OAAe,WAA2B,QAAQ;AAC9D,YAAM,YAAY,aAAa;AAC/B,UAAI,CAAC,UAAW;AAChB,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM,SAAS,CAAC,CAAC;AAC7D,gBAAU,SAAS,EAAE,KAAK,UAAU,YAAY,SAAS,CAAC;AAAA,IAC5D;AAAA,EACF,IAAI,CAAC,MAAM,QAAQ,UAAU,CAAC;AAG9B,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,UAAU,IAAI,QAAQ;AAC5E,QAAM,eAAe,KAAK,KAAK,SAAS,UAAU;AAClD,QAAM,WAAW,KAAK;AAAA,IACpB,MAAM;AAAA,IACN,KAAK,MAAM,YAAY,UAAU,IAAI,eAAe;AAAA,EACtD;AAEA,QAAM,eAAe,YAAY,MAAM;AACrC,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAGhB,QAAI,OAAO,YAAY,MAAM;AAC3B,2BAAqB,OAAO,OAAO;AAAA,IACrC;AAEA,WAAO,UAAU,sBAAsB,MAAM;AAC3C,mBAAa,UAAU,SAAS;AAChC,aAAO,UAAU;AAAA,IACnB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,OAAO,YAAY,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AAAA,MACrC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,eAA4B,CAAC;AACnC,WAAS,IAAI,YAAY,IAAI,UAAU,KAAK;AAC1C,iBAAa;AAAA,MACX;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,OAAO;AAAA,YACL,UAAU;AAAA,YACV,KAAK,IAAI;AAAA,YACT,MAAM;AAAA,YACN,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,UAAU;AAAA,UACZ;AAAA;AAAA,QAEC,WAAW,MAAM,CAAC,GAAG,CAAC;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,iBAAgC;AAAA,IACpC,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,GAAG;AAAA,EACL;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA;AAAA,IAGV;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,OAAO;AAAA,QACT;AAAA;AAAA,MAEC;AAAA,IACH;AAAA,EACF;AAEJ;AAGO,IAAM,cAAc,WAAW,gBAAgB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ark-floating-scroll",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A high-performance virtualized list component for React that renders only visible items",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -29,7 +29,11 @@
|
|
|
29
29
|
"performance",
|
|
30
30
|
"windowing"
|
|
31
31
|
],
|
|
32
|
-
"author": "",
|
|
32
|
+
"author": "Annup Raj Kapur",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/AnnupKapur/ark-floating-scroll"
|
|
36
|
+
},
|
|
33
37
|
"license": "MIT",
|
|
34
38
|
"peerDependencies": {
|
|
35
39
|
"react": ">=16.8.0",
|