@ceriousdevtech/react-cerious-scroll 1.0.3 → 1.0.5
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 +14 -0
- package/README.md +37 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +144 -122
- package/dist/use-cerious-scroll.d.ts +6 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to react-cerious-scroll will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.5] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Table mode support** (`options={{ layout: 'table' }}`). Rows render as real `<td>` cells into the engine's `<tr>`. React renders each row into its own `display: contents` wrapper, which keeps the reconciler isolated from the engine's `<tr>` recycling (so a fast scroll can't trip a `removeChild` on cells the engine already cleared) while the cells still lay out as the row's columns.
|
|
12
|
+
- **`tableHeader`** prop. Declarative header (provide a `<tr>` of `<th>`s) rendered into the engine's `<thead>` via a portal — same `<table>` as the rows, so columns align natively. Reactive like any other portal.
|
|
13
|
+
|
|
14
|
+
### Dependencies
|
|
15
|
+
- Bumped `@ceriousdevtech/cerious-scroll` to `^1.0.6`: native table layout, `table.autoSizeColumns` (auto-sized but stable columns), trackpad-only wheel inertia, overlay-scrollbar gutter fix, and exact bottom snap.
|
|
16
|
+
|
|
17
|
+
## [1.0.4] - 2026-06-04
|
|
18
|
+
|
|
19
|
+
### Dependencies
|
|
20
|
+
- Bumped `@ceriousdevtech/cerious-scroll` to `^1.0.5`. Consumers get the new wheel input classifier (trackpad / free-scroll mice apply input immediately, ratcheted wheel notches still ease smoothly), the new `wheel.wheelBehavior` option (`'auto' | 'immediate' | 'smooth'`), and a fix for horizontal wheel forwarding in layouts where `overflow-x: auto` lives on an ancestor of `[data-cerious-scroll-content]`.
|
|
21
|
+
|
|
8
22
|
## [1.0.3] - 2026-06-03
|
|
9
23
|
|
|
10
24
|
### Dependencies
|
package/README.md
CHANGED
|
@@ -109,7 +109,8 @@ function List() {
|
|
|
109
109
|
| `items` | `readonly TItem[]` | Optional data array. `totalElements` defaults to `items.length`. |
|
|
110
110
|
| `totalElements` | `number` | Total item count. Required if `items` is omitted. |
|
|
111
111
|
| `getItem` | `(index) => TItem` | Lazy item getter for large/sparse datasets. |
|
|
112
|
-
| `
|
|
112
|
+
| `tableHeader` | `ReactNode` | Table mode only. A `<tr>` of `<th>`s rendered into the engine's `<thead>` (see [Table layout](#table-layout)). |
|
|
113
|
+
| `options` | `CeriousScrollOptions` | Engine options (keyboard/touch/wheel/scrollbar/`layout`/etc.). Read once at creation. |
|
|
113
114
|
| `autoRender` | `boolean` | Re-render on scroll/resize/data changes. Default `true`. |
|
|
114
115
|
| `onViewportChange` | `(detail) => void` | Normalized viewport-change callback. |
|
|
115
116
|
| `onMeasuredViewport` | `(range) => void` | Measured range after each render pass. |
|
|
@@ -130,6 +131,41 @@ const ref = useRef<CeriousScrollHandle>(null);
|
|
|
130
131
|
|
|
131
132
|
---
|
|
132
133
|
|
|
134
|
+
## Table layout
|
|
135
|
+
|
|
136
|
+
Pass `options={{ layout: 'table' }}` to render real `<table>` / `<tr>` / `<td>` rows with a frozen header and native column alignment. Your `renderItem` returns the row's `<td>` cells, and `tableHeader` provides the (declarative, reactive) `<thead>` row:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { TABLE_COLUMNS } from './data';
|
|
140
|
+
|
|
141
|
+
<CeriousScroll
|
|
142
|
+
className="my-scroll" // give it a height
|
|
143
|
+
totalElements={100_000}
|
|
144
|
+
getItem={(i) => i}
|
|
145
|
+
options={{ layout: 'table', table: { tableClassName: 'my-table', autoSizeColumns: true } }}
|
|
146
|
+
tableHeader={
|
|
147
|
+
<tr>{TABLE_COLUMNS.map((c) => <th key={c.key}>{c.label}</th>)}</tr>
|
|
148
|
+
}
|
|
149
|
+
renderItem={(index) => {
|
|
150
|
+
const row = makeRow(index);
|
|
151
|
+
return (
|
|
152
|
+
<>
|
|
153
|
+
<td>{row.id}</td>
|
|
154
|
+
<td>{row.name}</td>
|
|
155
|
+
<td>{row.email}</td>
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- **`tableHeader`** is portaled into the engine's `<thead>` — the same `<table>` as the rows, so columns align natively and the header stays frozen.
|
|
163
|
+
- **`renderItem` must return `<td>`s** (a fragment of cells). They're rendered into the row's `<tr>` via a `display: contents` wrapper, so React fully owns them and the engine's row recycling can't tear them out.
|
|
164
|
+
- **`table.autoSizeColumns`** measures column widths once and pins them — auto-sized but stable (no jitter, no manual widths). Or use `table.columnWidths`. Variable row heights work as usual.
|
|
165
|
+
- CSS: `border-collapse: separate` and an **opaque `<thead>` background** (see the core README's [Table Layout](https://github.com/ceriousdevtech/cerious-scroll#-table-layout-layout-table) notes).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
133
169
|
## Notes
|
|
134
170
|
|
|
135
171
|
- **No height estimation.** Rows are committed with `flushSync` so the engine
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ee=require("react/jsx-runtime"),r=require("react"),z=require("react-dom"),te=require("react-dom/server"),G=require("@ceriousdevtech/cerious-scroll"),$="data-cerious-scroll-content",re="data-cerious-scroll-row";function ne(e){const s=e.querySelector(`[${$}]`);if(s)return s;const n=document.createElement("div");return n.setAttribute($,""),n.style.position="relative",n.style.width="100%",n.style.height="100%",n.style.overflowY="clip",n.style.overflowX="auto",e.appendChild(n),n}function ce(e,s){const n=h=>{const l=h.detail;l&&s({percentage:l.percentage,currentElement:l.currentElement,scrollOffset:l.scrollOffset,result:{element:l.result.element,offset:l.result.offset}})},f=h=>{const l=h.detail;l&&s({percentage:l.percentage,currentElement:l.element,scrollOffset:l.scrollOffset,result:{element:l.element,offset:l.scrollOffset}})};return e.addEventListener("cerious-viewport-change",n),e.addEventListener("viewport-change",f),()=>{e.removeEventListener("cerious-viewport-change",n),e.removeEventListener("viewport-change",f)}}function le(e,s){const n=typeof e=="number"?e:typeof s=="number"?s:void 0;if(n===void 0||Number.isNaN(n))throw new Error("useCeriousScroll: provide `totalElements` or `items`.");return Math.max(1,Math.floor(n))}function J(e){var Y;const s=r.useRef(null),n=r.useRef(null),f=r.useRef(new Map),h=r.useRef(new Map),l=r.useRef(null),R=r.useRef(null),S=r.useRef(null),T=r.useRef(e.tableHeader),[,M]=r.useReducer(t=>t+1,0),[A,p]=r.useState(null),b=r.useRef(e.renderItem),g=r.useRef(e.items??null),v=r.useRef(e.getItem),L=r.useRef(e.options),q=r.useRef(e.autoRender??!0),D=r.useRef(e.totalElements??null),H=r.useRef(e.onViewportChange),P=r.useRef(e.onMeasuredViewport),j=r.useRef(e.onReady);b.current=e.renderItem,g.current=e.items??null,v.current=e.getItem,T.current=e.tableHeader,L.current=e.options,q.current=e.autoRender??!0,D.current=e.totalElements??null,H.current=e.onViewportChange,P.current=e.onMeasuredViewport,j.current=e.onReady;const B=r.useCallback(t=>{const c=v.current;if(c)return c(t);const o=g.current;return o?o[t]:void 0},[]),u=r.useCallback(()=>{var I,O;const t=n.current;if(!t)return null;const{scroller:c,contentEl:o,container:a}=t,k=a.clientHeight||a.offsetHeight||0,w=[],m=((I=L.current)==null?void 0:I.layout)==="table",_=(i,d)=>{const C=document.createElement("div");m&&(C.style.display="contents"),C.setAttribute(re,String(i)),C.innerHTML=te.renderToStaticMarkup(r.createElement(r.Fragment,null,b.current(B(i),i))),d.appendChild(C),f.current.set(i,{el:d,mount:C}),w.push(C)},y=c.renderViewport(k,o,_),F=new Set(c.getRenderedIndices());return f.current.forEach((i,d)=>{F.has(d)||f.current.delete(d)}),w.forEach(i=>{i.textContent=""}),z.flushSync(M),(O=P.current)==null||O.call(P,y),y},[]),K=e.totalElements??((Y=e.items)==null?void 0:Y.length)??null;r.useEffect(()=>{var F,I,O;const t=s.current;if(!t)return;const c=ne(t),o=L.current??{},a=o.onScroll,k={...o,onScroll:()=>{a==null||a(),q.current&&u()}};if(o.layout==="table"&&T.current!=null){const i=(F=o.table)==null?void 0:F.header;k.table={...o.table,header:d=>{S.current=d,i==null||i(d)}}}const w=le(D.current,((I=g.current)==null?void 0:I.length)??null),m=new G.CeriousScroll(t,w,k);R.current&&(m.currentElement=Math.min(R.current.currentElement,w-1),m.scrollOffset=R.current.scrollOffset,R.current=null),n.current={scroller:m,contentEl:c,container:t},p(m);const _=ce(t,i=>{var d;(d=H.current)==null||d.call(H,i)});(O=j.current)==null||O.call(j,m);let y=0;return q.current&&(y=requestAnimationFrame(()=>u())),()=>{cancelAnimationFrame(y),_(),R.current={currentElement:m.currentElement,scrollOffset:m.scrollOffset},f.current.clear(),h.current.clear(),l.current=null,c.textContent="",m.detachScrollbar(t),m.dispose(),n.current=null,p(null)}},[K,u]),r.useEffect(()=>{if(!n.current||!q.current)return;const t=requestAnimationFrame(()=>u());return()=>cancelAnimationFrame(t)},[e.items,e.getItem]);const Q=r.useCallback(t=>{const c=n.current;return c?(c.scroller.jumpToElement(t),u()):null},[u]),U=r.useCallback(t=>{const c=n.current;return c?(c.scroller.handleScrollPercentage(t),u()):null},[u]),Z=r.useCallback(()=>{const t=n.current;return t?(t.scroller.reset(),u()):null},[u]),x=r.useCallback(()=>{const t=n.current;return t?(t.scroller.clearAllCaches(),u()):null},[u]),W=g.current,X=!v.current&&W!=null?W.length:null,N=b.current,E=h.current;l.current!==N&&(E.clear(),l.current=N);const V=[];return f.current.forEach((t,c)=>{if(X!==null&&c>=X){E.delete(c);return}const o=B(c);let a=E.get(c);(!a||a.item!==o||a.mount!==t.mount)&&(a={item:o,mount:t.mount,node:z.createPortal(N(o,c),t.mount,String(c))},E.set(c,a)),V.push(a.node)}),E.size>f.current.size&&E.forEach((t,c)=>{f.current.has(c)||E.delete(c)}),e.tableHeader!=null&&S.current&&V.push(z.createPortal(e.tableHeader,S.current,"cerious-thead")),{containerRef:s,portals:V,scroller:A,render:u,jumpToElement:Q,scrollToPercentage:U,reset:Z,recalculate:x}}function oe(e,s){const{className:n,style:f,"data-testid":h,children:l,...R}=e,{containerRef:S,portals:T,scroller:M,render:A,jumpToElement:p,scrollToPercentage:b,reset:g,recalculate:v}=J(R);return r.useImperativeHandle(s,()=>({scroller:M,render:A,jumpToElement:p,scrollToPercentage:b,reset:g,recalculate:v}),[M,A,p,b,g,v]),ee.jsxs("div",{ref:S,className:n,"data-testid":h,style:{position:"relative",overflow:"hidden",...f},children:[l,T]})}const se=r.forwardRef(oe);Object.defineProperty(exports,"CeriousScrollEngine",{enumerable:!0,get:()=>G.CeriousScroll});exports.CeriousScroll=se;exports.useCeriousScroll=J;
|
package/dist/index.js
CHANGED
|
@@ -1,163 +1,185 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import { flushSync as
|
|
4
|
-
import { renderToStaticMarkup as
|
|
5
|
-
import { CeriousScroll as
|
|
6
|
-
import { CeriousScroll as
|
|
7
|
-
const
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
1
|
+
import { jsxs as ee } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as o, useReducer as te, useState as re, useCallback as w, useEffect as Y, createElement as ne, Fragment as ce, forwardRef as oe, useImperativeHandle as le } from "react";
|
|
3
|
+
import { flushSync as se, createPortal as G } from "react-dom";
|
|
4
|
+
import { renderToStaticMarkup as ue } from "react-dom/server";
|
|
5
|
+
import { CeriousScroll as ae } from "@ceriousdevtech/cerious-scroll";
|
|
6
|
+
import { CeriousScroll as ye } from "@ceriousdevtech/cerious-scroll";
|
|
7
|
+
const J = "data-cerious-scroll-content", ie = "data-cerious-scroll-row";
|
|
8
|
+
function fe(e) {
|
|
9
|
+
const s = e.querySelector(`[${J}]`);
|
|
10
|
+
if (s) return s;
|
|
11
11
|
const r = document.createElement("div");
|
|
12
|
-
return r.setAttribute(
|
|
12
|
+
return r.setAttribute(J, ""), r.style.position = "relative", r.style.width = "100%", r.style.height = "100%", r.style.overflowY = "clip", r.style.overflowX = "auto", e.appendChild(r), r;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
const r = (
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
percentage:
|
|
19
|
-
currentElement:
|
|
20
|
-
scrollOffset:
|
|
21
|
-
result: { element:
|
|
14
|
+
function me(e, s) {
|
|
15
|
+
const r = (h) => {
|
|
16
|
+
const c = h.detail;
|
|
17
|
+
c && s({
|
|
18
|
+
percentage: c.percentage,
|
|
19
|
+
currentElement: c.currentElement,
|
|
20
|
+
scrollOffset: c.scrollOffset,
|
|
21
|
+
result: { element: c.result.element, offset: c.result.offset }
|
|
22
22
|
});
|
|
23
|
-
},
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
percentage:
|
|
27
|
-
currentElement:
|
|
28
|
-
scrollOffset:
|
|
29
|
-
result: { element:
|
|
23
|
+
}, f = (h) => {
|
|
24
|
+
const c = h.detail;
|
|
25
|
+
c && s({
|
|
26
|
+
percentage: c.percentage,
|
|
27
|
+
currentElement: c.element,
|
|
28
|
+
scrollOffset: c.scrollOffset,
|
|
29
|
+
result: { element: c.element, offset: c.scrollOffset }
|
|
30
30
|
});
|
|
31
31
|
};
|
|
32
|
-
return e.addEventListener("cerious-viewport-change", r), e.addEventListener("viewport-change",
|
|
33
|
-
e.removeEventListener("cerious-viewport-change", r), e.removeEventListener("viewport-change",
|
|
32
|
+
return e.addEventListener("cerious-viewport-change", r), e.addEventListener("viewport-change", f), () => {
|
|
33
|
+
e.removeEventListener("cerious-viewport-change", r), e.removeEventListener("viewport-change", f);
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
const r = typeof e == "number" ? e : typeof
|
|
36
|
+
function de(e, s) {
|
|
37
|
+
const r = typeof e == "number" ? e : typeof s == "number" ? s : void 0;
|
|
38
38
|
if (r === void 0 || Number.isNaN(r))
|
|
39
39
|
throw new Error("useCeriousScroll: provide `totalElements` or `items`.");
|
|
40
40
|
return Math.max(1, Math.floor(r));
|
|
41
41
|
}
|
|
42
|
-
function
|
|
43
|
-
var
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const n =
|
|
42
|
+
function he(e) {
|
|
43
|
+
var X;
|
|
44
|
+
const s = o(null), r = o(null), f = o(/* @__PURE__ */ new Map()), h = o(/* @__PURE__ */ new Map()), c = o(null), g = o(null), C = o(null), M = o(e.tableHeader), [, A] = te((t) => t + 1, 0), [H, S] = re(null), v = o(e.renderItem), E = o(e.items ?? null), R = o(e.getItem), _ = o(e.options), F = o(e.autoRender ?? !0), $ = o(e.totalElements ?? null), L = o(e.onViewportChange), N = o(e.onMeasuredViewport), P = o(e.onReady);
|
|
45
|
+
v.current = e.renderItem, E.current = e.items ?? null, R.current = e.getItem, M.current = e.tableHeader, _.current = e.options, F.current = e.autoRender ?? !0, $.current = e.totalElements ?? null, L.current = e.onViewportChange, N.current = e.onMeasuredViewport, P.current = e.onReady;
|
|
46
|
+
const B = w((t) => {
|
|
47
|
+
const n = R.current;
|
|
48
48
|
if (n) return n(t);
|
|
49
|
-
const
|
|
50
|
-
return
|
|
51
|
-
}, []),
|
|
52
|
-
var
|
|
49
|
+
const l = E.current;
|
|
50
|
+
return l ? l[t] : void 0;
|
|
51
|
+
}, []), u = w(() => {
|
|
52
|
+
var T, O;
|
|
53
53
|
const t = r.current;
|
|
54
54
|
if (!t) return null;
|
|
55
|
-
const { scroller: n, contentEl:
|
|
56
|
-
const
|
|
57
|
-
m.setAttribute(
|
|
58
|
-
|
|
59
|
-
),
|
|
60
|
-
},
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
}), y.forEach((
|
|
64
|
-
|
|
65
|
-
}),
|
|
66
|
-
}, []),
|
|
67
|
-
|
|
68
|
-
var
|
|
69
|
-
const t =
|
|
55
|
+
const { scroller: n, contentEl: l, container: a } = t, j = a.clientHeight || a.offsetHeight || 0, y = [], m = ((T = _.current) == null ? void 0 : T.layout) === "table", z = (i, d) => {
|
|
56
|
+
const b = document.createElement("div");
|
|
57
|
+
m && (b.style.display = "contents"), b.setAttribute(ie, String(i)), b.innerHTML = ue(
|
|
58
|
+
ne(ce, null, v.current(B(i), i))
|
|
59
|
+
), d.appendChild(b), f.current.set(i, { el: d, mount: b }), y.push(b);
|
|
60
|
+
}, I = n.renderViewport(j, l, z), V = new Set(n.getRenderedIndices());
|
|
61
|
+
return f.current.forEach((i, d) => {
|
|
62
|
+
V.has(d) || f.current.delete(d);
|
|
63
|
+
}), y.forEach((i) => {
|
|
64
|
+
i.textContent = "";
|
|
65
|
+
}), se(A), (O = N.current) == null || O.call(N, I), I;
|
|
66
|
+
}, []), K = e.totalElements ?? ((X = e.items) == null ? void 0 : X.length) ?? null;
|
|
67
|
+
Y(() => {
|
|
68
|
+
var V, T, O;
|
|
69
|
+
const t = s.current;
|
|
70
70
|
if (!t) return;
|
|
71
|
-
const n =
|
|
72
|
-
...
|
|
71
|
+
const n = fe(t), l = _.current ?? {}, a = l.onScroll, j = {
|
|
72
|
+
...l,
|
|
73
73
|
onScroll: () => {
|
|
74
|
-
|
|
74
|
+
a == null || a(), F.current && u();
|
|
75
75
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
};
|
|
77
|
+
if (l.layout === "table" && M.current != null) {
|
|
78
|
+
const i = (V = l.table) == null ? void 0 : V.header;
|
|
79
|
+
j.table = {
|
|
80
|
+
...l.table,
|
|
81
|
+
header: (d) => {
|
|
82
|
+
C.current = d, i == null || i(d);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const y = de($.current, ((T = E.current) == null ? void 0 : T.length) ?? null), m = new ae(t, y, j);
|
|
87
|
+
g.current && (m.currentElement = Math.min(g.current.currentElement, y - 1), m.scrollOffset = g.current.scrollOffset, g.current = null), r.current = { scroller: m, contentEl: n, container: t }, S(m);
|
|
88
|
+
const z = me(t, (i) => {
|
|
89
|
+
var d;
|
|
90
|
+
(d = L.current) == null || d.call(L, i);
|
|
81
91
|
});
|
|
82
|
-
(
|
|
83
|
-
let
|
|
84
|
-
return
|
|
85
|
-
cancelAnimationFrame(
|
|
86
|
-
currentElement:
|
|
87
|
-
scrollOffset:
|
|
88
|
-
},
|
|
92
|
+
(O = P.current) == null || O.call(P, m);
|
|
93
|
+
let I = 0;
|
|
94
|
+
return F.current && (I = requestAnimationFrame(() => u())), () => {
|
|
95
|
+
cancelAnimationFrame(I), z(), g.current = {
|
|
96
|
+
currentElement: m.currentElement,
|
|
97
|
+
scrollOffset: m.scrollOffset
|
|
98
|
+
}, f.current.clear(), h.current.clear(), c.current = null, n.textContent = "", m.detachScrollbar(t), m.dispose(), r.current = null, S(null);
|
|
89
99
|
};
|
|
90
|
-
}, [
|
|
91
|
-
if (!r.current || !
|
|
92
|
-
const t = requestAnimationFrame(() =>
|
|
100
|
+
}, [K, u]), Y(() => {
|
|
101
|
+
if (!r.current || !F.current) return;
|
|
102
|
+
const t = requestAnimationFrame(() => u());
|
|
93
103
|
return () => cancelAnimationFrame(t);
|
|
94
104
|
}, [e.items, e.getItem]);
|
|
95
|
-
const
|
|
105
|
+
const Q = w(
|
|
96
106
|
(t) => {
|
|
97
107
|
const n = r.current;
|
|
98
|
-
return n ? (n.scroller.jumpToElement(t),
|
|
108
|
+
return n ? (n.scroller.jumpToElement(t), u()) : null;
|
|
99
109
|
},
|
|
100
|
-
[
|
|
101
|
-
),
|
|
110
|
+
[u]
|
|
111
|
+
), U = w(
|
|
102
112
|
(t) => {
|
|
103
113
|
const n = r.current;
|
|
104
|
-
return n ? (n.scroller.handleScrollPercentage(t),
|
|
114
|
+
return n ? (n.scroller.handleScrollPercentage(t), u()) : null;
|
|
105
115
|
},
|
|
106
|
-
[
|
|
107
|
-
),
|
|
116
|
+
[u]
|
|
117
|
+
), Z = w(() => {
|
|
108
118
|
const t = r.current;
|
|
109
|
-
return t ? (t.scroller.reset(),
|
|
110
|
-
}, [
|
|
119
|
+
return t ? (t.scroller.reset(), u()) : null;
|
|
120
|
+
}, [u]), x = w(() => {
|
|
111
121
|
const t = r.current;
|
|
112
|
-
return t ? (t.scroller.clearAllCaches(),
|
|
113
|
-
}, [
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
return t ? (t.scroller.clearAllCaches(), u()) : null;
|
|
123
|
+
}, [u]), D = E.current, W = !R.current && D != null ? D.length : null, q = v.current, p = h.current;
|
|
124
|
+
c.current !== q && (p.clear(), c.current = q);
|
|
125
|
+
const k = [];
|
|
126
|
+
return f.current.forEach((t, n) => {
|
|
127
|
+
if (W !== null && n >= W) {
|
|
128
|
+
p.delete(n);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const l = B(n);
|
|
132
|
+
let a = p.get(n);
|
|
133
|
+
(!a || a.item !== l || a.mount !== t.mount) && (a = {
|
|
134
|
+
item: l,
|
|
135
|
+
mount: t.mount,
|
|
136
|
+
node: G(q(l, n), t.mount, String(n))
|
|
137
|
+
}, p.set(n, a)), k.push(a.node);
|
|
138
|
+
}), p.size > f.current.size && p.forEach((t, n) => {
|
|
139
|
+
f.current.has(n) || p.delete(n);
|
|
140
|
+
}), e.tableHeader != null && C.current && k.push(G(e.tableHeader, C.current, "cerious-thead")), {
|
|
141
|
+
containerRef: s,
|
|
142
|
+
portals: k,
|
|
143
|
+
scroller: H,
|
|
144
|
+
render: u,
|
|
145
|
+
jumpToElement: Q,
|
|
146
|
+
scrollToPercentage: U,
|
|
147
|
+
reset: Z,
|
|
148
|
+
recalculate: x
|
|
127
149
|
};
|
|
128
150
|
}
|
|
129
|
-
function
|
|
130
|
-
const { className: r, style:
|
|
131
|
-
containerRef:
|
|
132
|
-
portals:
|
|
133
|
-
scroller:
|
|
134
|
-
render:
|
|
135
|
-
jumpToElement:
|
|
136
|
-
scrollToPercentage:
|
|
137
|
-
reset:
|
|
138
|
-
recalculate:
|
|
139
|
-
} =
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
() => ({ scroller:
|
|
143
|
-
[
|
|
144
|
-
), /* @__PURE__ */
|
|
151
|
+
function ge(e, s) {
|
|
152
|
+
const { className: r, style: f, "data-testid": h, children: c, ...g } = e, {
|
|
153
|
+
containerRef: C,
|
|
154
|
+
portals: M,
|
|
155
|
+
scroller: A,
|
|
156
|
+
render: H,
|
|
157
|
+
jumpToElement: S,
|
|
158
|
+
scrollToPercentage: v,
|
|
159
|
+
reset: E,
|
|
160
|
+
recalculate: R
|
|
161
|
+
} = he(g);
|
|
162
|
+
return le(
|
|
163
|
+
s,
|
|
164
|
+
() => ({ scroller: A, render: H, jumpToElement: S, scrollToPercentage: v, reset: E, recalculate: R }),
|
|
165
|
+
[A, H, S, v, E, R]
|
|
166
|
+
), /* @__PURE__ */ ee(
|
|
145
167
|
"div",
|
|
146
168
|
{
|
|
147
|
-
ref:
|
|
169
|
+
ref: C,
|
|
148
170
|
className: r,
|
|
149
|
-
"data-testid":
|
|
150
|
-
style: { position: "relative", overflow: "hidden", ...
|
|
171
|
+
"data-testid": h,
|
|
172
|
+
style: { position: "relative", overflow: "hidden", ...f },
|
|
151
173
|
children: [
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
c,
|
|
175
|
+
M
|
|
154
176
|
]
|
|
155
177
|
}
|
|
156
178
|
);
|
|
157
179
|
}
|
|
158
|
-
const
|
|
180
|
+
const we = oe(ge);
|
|
159
181
|
export {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
182
|
+
we as CeriousScroll,
|
|
183
|
+
ye as CeriousScrollEngine,
|
|
184
|
+
he as useCeriousScroll
|
|
163
185
|
};
|
|
@@ -10,6 +10,12 @@ export interface UseCeriousScrollOptions<TItem = unknown> {
|
|
|
10
10
|
getItem?: (index: number) => TItem;
|
|
11
11
|
/** Renders a single row. `item` is `undefined` when no data source is given. */
|
|
12
12
|
renderItem: (item: TItem, index: number) => ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Table mode only: declarative header content rendered into the engine's
|
|
15
|
+
* `<thead>` (provide a `<tr>` of `<th>`s). It lives in the same `<table>` as
|
|
16
|
+
* the rows, so columns align natively. Reactive — re-renders like any portal.
|
|
17
|
+
*/
|
|
18
|
+
tableHeader?: ReactNode;
|
|
13
19
|
/** Options forwarded to `new CeriousScroll(...)` (read once, at creation). */
|
|
14
20
|
options?: CeriousScrollOptions;
|
|
15
21
|
/** Automatically render after scroll/resize/data changes. Default: `true`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ceriousdevtech/react-cerious-scroll",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "React bindings for CeriousScroll — high-performance virtual scrolling with O(1) memory and no height estimation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"demo:build": "vite build --config demo/vite.config.ts"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@ceriousdevtech/cerious-scroll": "^1.0.
|
|
59
|
+
"@ceriousdevtech/cerious-scroll": "^1.0.6"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"react": ">=18 <20",
|