@ai-matrx/design-system 0.17.0 → 0.17.2
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 +26 -0
- package/dist/data-table/index.cjs +28 -7
- package/dist/data-table/index.cjs.map +1 -1
- package/dist/data-table/index.js +29 -7
- package/dist/data-table/index.js.map +1 -1
- package/dist/data-table/scroll-pagination.cjs +18 -7
- package/dist/data-table/scroll-pagination.cjs.map +1 -1
- package/dist/data-table/scroll-pagination.js +18 -7
- package/dist/data-table/scroll-pagination.js.map +1 -1
- package/package.json +1 -1
|
@@ -101,6 +101,7 @@ function useScrollPagination({
|
|
|
101
101
|
const inFlightGenerationRef = (0, import_react.useRef)(null);
|
|
102
102
|
const requestErrorRef = (0, import_react.useRef)(null);
|
|
103
103
|
const mountedRef = (0, import_react.useRef)(false);
|
|
104
|
+
const observedTopRef = (0, import_react.useRef)(/* @__PURE__ */ new WeakMap());
|
|
104
105
|
const effectiveError = error ?? requestError;
|
|
105
106
|
(0, import_react.useEffect)(() => {
|
|
106
107
|
mountedRef.current = true;
|
|
@@ -134,6 +135,7 @@ function useScrollPagination({
|
|
|
134
135
|
};
|
|
135
136
|
(0, import_react.useEffect)(() => {
|
|
136
137
|
intentRef.current = null;
|
|
138
|
+
observedTopRef.current = /* @__PURE__ */ new WeakMap();
|
|
137
139
|
consumedSequenceRef.current = inputSequenceRef.current;
|
|
138
140
|
requestErrorRef.current = null;
|
|
139
141
|
setRequestError(null);
|
|
@@ -143,14 +145,18 @@ function useScrollPagination({
|
|
|
143
145
|
(0, import_react.useEffect)(() => {
|
|
144
146
|
if (mode !== "scroll" || !enabled || typeof window === "undefined") return;
|
|
145
147
|
const attachable = containers.map(resolveContainer).filter((container) => container !== null);
|
|
146
|
-
const setIntent = (container) => {
|
|
148
|
+
const setIntent = (container, startTop) => {
|
|
147
149
|
const current = latestRef.current;
|
|
148
150
|
if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;
|
|
149
151
|
inputSequenceRef.current += 1;
|
|
150
152
|
intentRef.current = {
|
|
151
153
|
queryKey: latestRef.current.queryKey,
|
|
152
154
|
container,
|
|
153
|
-
|
|
155
|
+
// The compositor can advance downward before the passive input
|
|
156
|
+
// listener, while DOM/test callers can move upward before its scroll
|
|
157
|
+
// notification. Either way, the lower position is the only safe
|
|
158
|
+
// baseline for proving that this downward intent caused movement.
|
|
159
|
+
startTop: Math.min(startTop, container.scrollTop),
|
|
154
160
|
sequence: inputSequenceRef.current,
|
|
155
161
|
expiresAt: Date.now() + intentTimeoutMs
|
|
156
162
|
};
|
|
@@ -165,9 +171,11 @@ function useScrollPagination({
|
|
|
165
171
|
void requestNextPage();
|
|
166
172
|
};
|
|
167
173
|
const cleanups = attachable.map((container) => {
|
|
174
|
+
if (!observedTopRef.current.has(container)) observedTopRef.current.set(container, container.scrollTop);
|
|
175
|
+
const lastObservedTop = () => observedTopRef.current.get(container) ?? container.scrollTop;
|
|
168
176
|
const onWheel = (event) => {
|
|
169
177
|
if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
|
|
170
|
-
setIntent(container);
|
|
178
|
+
setIntent(container, lastObservedTop());
|
|
171
179
|
}
|
|
172
180
|
};
|
|
173
181
|
const onTouchStart = (event) => {
|
|
@@ -178,19 +186,22 @@ function useScrollPagination({
|
|
|
178
186
|
const touch = event.touches.item(0);
|
|
179
187
|
const startY = Number(container.dataset.matrxPaginationTouchY);
|
|
180
188
|
if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {
|
|
181
|
-
setIntent(container);
|
|
189
|
+
setIntent(container, lastObservedTop());
|
|
182
190
|
}
|
|
183
191
|
};
|
|
184
192
|
const onTouchEnd = () => {
|
|
185
193
|
delete container.dataset.matrxPaginationTouchY;
|
|
186
194
|
};
|
|
187
195
|
const onKeyDown = (event) => {
|
|
188
|
-
if (isDownwardKeyboardInput(event)) setIntent(container);
|
|
196
|
+
if (isDownwardKeyboardInput(event)) setIntent(container, lastObservedTop());
|
|
189
197
|
};
|
|
190
198
|
const onPointerDown = (event) => {
|
|
191
|
-
if (isNativeScrollbarPointer(event, container)) setIntent(container);
|
|
199
|
+
if (isNativeScrollbarPointer(event, container)) setIntent(container, lastObservedTop());
|
|
200
|
+
};
|
|
201
|
+
const onScroll = () => {
|
|
202
|
+
handleScroll(container);
|
|
203
|
+
observedTopRef.current.set(container, container.scrollTop);
|
|
192
204
|
};
|
|
193
|
-
const onScroll = () => handleScroll(container);
|
|
194
205
|
container.addEventListener("wheel", onWheel, { passive: true });
|
|
195
206
|
container.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
196
207
|
container.addEventListener("touchmove", onTouchMove, { passive: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/data-table/useScrollPagination.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport type ScrollPaginationContainer =\n | HTMLElement\n | RefObject<HTMLElement | null>\n | null;\n\nexport interface UseScrollPaginationOptions {\n /** Every visible desktop/mobile scroll container that may present this table. */\n containers: readonly ScrollPaginationContainer[];\n /** Immutable identity of every source-affecting query value. */\n queryKey: string;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n loading: boolean;\n /** Automatic scrolling never retries a failed append. */\n error: Error | null;\n loadNextPage: () => Promise<void> | void;\n /** Manual mode exposes the explicit request only; it installs no listeners. */\n mode?: \"scroll\" | \"manual\";\n /** Distance from the bottom at which a qualifying user scroll may append. */\n thresholdPx?: number;\n /** How long an input may authorize the following real scroll movement. */\n intentTimeoutMs?: number;\n enabled?: boolean;\n}\n\nexport interface UseScrollPaginationResult {\n /** For the table's accessible Load more / Retry control. */\n requestNextPage: () => Promise<void>;\n canLoadMore: boolean;\n /** Includes an unexpected loader rejection until the person explicitly retries. */\n error: Error | null;\n}\n\ninterface ScrollIntent {\n queryKey: string;\n container: HTMLElement;\n startTop: number;\n sequence: number;\n expiresAt: number;\n}\n\nconst DEFAULT_THRESHOLD_PX = 96;\nconst DEFAULT_INTENT_TIMEOUT_MS = 1_200;\n\nfunction resolveContainer(container: ScrollPaginationContainer): HTMLElement | null {\n if (!container) return null;\n return container instanceof HTMLElement ? container : container.current;\n}\n\nfunction isVisibleContainer(container: HTMLElement): boolean {\n if (!container.isConnected) return false;\n for (let node: HTMLElement | null = container; node; node = node.parentElement) {\n const style = window.getComputedStyle(node);\n if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n }\n return true;\n}\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n return target.isContentEditable || Boolean(target.closest(\"input, textarea, select, [contenteditable='true']\"));\n}\n\nfunction isDownwardKeyboardInput(event: KeyboardEvent): boolean {\n if (isEditableTarget(event.target)) return false;\n return [\"ArrowDown\", \"PageDown\", \"End\", \" \", \"Spacebar\"].includes(event.key);\n}\n\nfunction isNearBottom(container: HTMLElement, thresholdPx: number): boolean {\n return container.scrollHeight - container.clientHeight - container.scrollTop <= thresholdPx;\n}\n\nfunction isNativeScrollbarPointer(event: PointerEvent, container: HTMLElement): boolean {\n // Overlay scrollbars expose no measurable gutter. Do not guess: Load more is\n // the accessible, reliable fallback there. A content click must never grant\n // permission for a later programmatic scroll to append rows.\n if (event.pointerType !== \"mouse\" || event.button !== 0 || !event.isPrimary) return false;\n const gutterWidth = container.offsetWidth - container.clientWidth;\n if (gutterWidth <= 0 || container.scrollHeight <= container.clientHeight) return false;\n const rect = container.getBoundingClientRect();\n return event.clientX >= rect.right - gutterWidth && event.clientX <= rect.right;\n}\n\n/**\n * Gates automatic append behind a fresh, real downward user scroll. The hook\n * owns neither fetching nor row state: callers feed it the data hook receipt\n * and render its explicit request as the accessible fallback control.\n */\nexport function useScrollPagination({\n containers,\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode = \"scroll\",\n thresholdPx = DEFAULT_THRESHOLD_PX,\n intentTimeoutMs = DEFAULT_INTENT_TIMEOUT_MS,\n enabled = true,\n}: UseScrollPaginationOptions): UseScrollPaginationResult {\n const [requestError, setRequestError] = useState<Error | null>(null);\n const latestRef = useRef({\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n });\n latestRef.current = {\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n };\n\n const intentRef = useRef<ScrollIntent | null>(null);\n const inputSequenceRef = useRef(0);\n const consumedSequenceRef = useRef(0);\n const requestGenerationRef = useRef(0);\n const inFlightGenerationRef = useRef<number | null>(null);\n const requestErrorRef = useRef<Error | null>(null);\n const mountedRef = useRef(false);\n const effectiveError = error ?? requestError;\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const requestNextPage = async () => {\n const current = latestRef.current;\n if (\n !current.enabled ||\n (!current.hasNextPage && effectiveError === null) ||\n current.loading ||\n current.isFetchingNextPage ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n const generation = ++requestGenerationRef.current;\n inFlightGenerationRef.current = generation;\n requestErrorRef.current = null;\n setRequestError(null);\n try {\n await current.loadNextPage();\n } catch (raw) {\n const nextError = raw instanceof Error ? raw : new Error(String(raw));\n if (inFlightGenerationRef.current === generation && latestRef.current.queryKey === current.queryKey) {\n requestErrorRef.current = nextError;\n if (mountedRef.current) setRequestError(nextError);\n }\n } finally {\n if (inFlightGenerationRef.current === generation) {\n inFlightGenerationRef.current = null;\n intentRef.current = null;\n }\n }\n };\n\n useEffect(() => {\n intentRef.current = null;\n consumedSequenceRef.current = inputSequenceRef.current;\n requestErrorRef.current = null;\n setRequestError(null);\n // A query change makes every existing completion stale. Its finally block\n // cannot clear a new request because generations are monotonically unique.\n inFlightGenerationRef.current = null;\n requestGenerationRef.current += 1;\n }, [queryKey]);\n\n useEffect(() => {\n if (mode !== \"scroll\" || !enabled || typeof window === \"undefined\") return;\n\n const attachable = containers\n .map(resolveContainer)\n .filter((container): container is HTMLElement => container !== null);\n\n const setIntent = (container: HTMLElement) => {\n const current = latestRef.current;\n if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;\n inputSequenceRef.current += 1;\n intentRef.current = {\n queryKey: latestRef.current.queryKey,\n container,\n startTop: container.scrollTop,\n sequence: inputSequenceRef.current,\n expiresAt: Date.now() + intentTimeoutMs,\n };\n };\n\n const handleScroll = (container: HTMLElement) => {\n const intent = intentRef.current;\n const current = latestRef.current;\n if (\n !intent ||\n intent.queryKey !== current.queryKey ||\n intent.container !== container ||\n Date.now() > intent.expiresAt ||\n container.scrollTop <= intent.startTop ||\n intent.sequence <= consumedSequenceRef.current ||\n !isVisibleContainer(container) ||\n !isNearBottom(container, current.thresholdPx) ||\n !current.hasNextPage ||\n current.loading ||\n current.isFetchingNextPage ||\n current.error !== null ||\n requestErrorRef.current !== null ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n consumedSequenceRef.current = intent.sequence;\n void requestNextPage();\n };\n\n const cleanups = attachable.map((container) => {\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {\n setIntent(container);\n }\n };\n const onTouchStart = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n if (touch) container.dataset.matrxPaginationTouchY = String(touch.clientY);\n };\n const onTouchMove = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n const startY = Number(container.dataset.matrxPaginationTouchY);\n if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {\n setIntent(container);\n }\n };\n const onTouchEnd = () => {\n delete container.dataset.matrxPaginationTouchY;\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (isDownwardKeyboardInput(event)) setIntent(container);\n };\n const onPointerDown = (event: PointerEvent) => {\n if (isNativeScrollbarPointer(event, container)) setIntent(container);\n };\n const onScroll = () => handleScroll(container);\n\n container.addEventListener(\"wheel\", onWheel, { passive: true });\n container.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n container.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n container.addEventListener(\"touchend\", onTouchEnd, { passive: true });\n container.addEventListener(\"touchcancel\", onTouchEnd, { passive: true });\n container.addEventListener(\"keydown\", onKeyDown);\n container.addEventListener(\"pointerdown\", onPointerDown, { passive: true });\n container.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => {\n delete container.dataset.matrxPaginationTouchY;\n container.removeEventListener(\"wheel\", onWheel);\n container.removeEventListener(\"touchstart\", onTouchStart);\n container.removeEventListener(\"touchmove\", onTouchMove);\n container.removeEventListener(\"touchend\", onTouchEnd);\n container.removeEventListener(\"touchcancel\", onTouchEnd);\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeEventListener(\"pointerdown\", onPointerDown);\n container.removeEventListener(\"scroll\", onScroll);\n };\n });\n return () => cleanups.forEach((cleanup) => cleanup());\n }, [containers, enabled, intentTimeoutMs, mode]);\n\n return {\n requestNextPage,\n canLoadMore: enabled && !loading && !isFetchingNextPage && (hasNextPage || effectiveError !== null),\n error: effectiveError,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/data-table/useScrollPagination.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport type ScrollPaginationContainer =\n | HTMLElement\n | RefObject<HTMLElement | null>\n | null;\n\nexport interface UseScrollPaginationOptions {\n /** Every visible desktop/mobile scroll container that may present this table. */\n containers: readonly ScrollPaginationContainer[];\n /** Immutable identity of every source-affecting query value. */\n queryKey: string;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n loading: boolean;\n /** Automatic scrolling never retries a failed append. */\n error: Error | null;\n loadNextPage: () => Promise<void> | void;\n /** Manual mode exposes the explicit request only; it installs no listeners. */\n mode?: \"scroll\" | \"manual\";\n /** Distance from the bottom at which a qualifying user scroll may append. */\n thresholdPx?: number;\n /** How long an input may authorize the following real scroll movement. */\n intentTimeoutMs?: number;\n enabled?: boolean;\n}\n\nexport interface UseScrollPaginationResult {\n /** For the table's accessible Load more / Retry control. */\n requestNextPage: () => Promise<void>;\n canLoadMore: boolean;\n /** Includes an unexpected loader rejection until the person explicitly retries. */\n error: Error | null;\n}\n\ninterface ScrollIntent {\n queryKey: string;\n container: HTMLElement;\n startTop: number;\n sequence: number;\n expiresAt: number;\n}\n\nconst DEFAULT_THRESHOLD_PX = 96;\nconst DEFAULT_INTENT_TIMEOUT_MS = 1_200;\n\nfunction resolveContainer(container: ScrollPaginationContainer): HTMLElement | null {\n if (!container) return null;\n return container instanceof HTMLElement ? container : container.current;\n}\n\nfunction isVisibleContainer(container: HTMLElement): boolean {\n if (!container.isConnected) return false;\n for (let node: HTMLElement | null = container; node; node = node.parentElement) {\n const style = window.getComputedStyle(node);\n if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n }\n return true;\n}\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n return target.isContentEditable || Boolean(target.closest(\"input, textarea, select, [contenteditable='true']\"));\n}\n\nfunction isDownwardKeyboardInput(event: KeyboardEvent): boolean {\n if (isEditableTarget(event.target)) return false;\n return [\"ArrowDown\", \"PageDown\", \"End\", \" \", \"Spacebar\"].includes(event.key);\n}\n\nfunction isNearBottom(container: HTMLElement, thresholdPx: number): boolean {\n return container.scrollHeight - container.clientHeight - container.scrollTop <= thresholdPx;\n}\n\nfunction isNativeScrollbarPointer(event: PointerEvent, container: HTMLElement): boolean {\n // Overlay scrollbars expose no measurable gutter. Do not guess: Load more is\n // the accessible, reliable fallback there. A content click must never grant\n // permission for a later programmatic scroll to append rows.\n if (event.pointerType !== \"mouse\" || event.button !== 0 || !event.isPrimary) return false;\n const gutterWidth = container.offsetWidth - container.clientWidth;\n if (gutterWidth <= 0 || container.scrollHeight <= container.clientHeight) return false;\n const rect = container.getBoundingClientRect();\n return event.clientX >= rect.right - gutterWidth && event.clientX <= rect.right;\n}\n\n/**\n * Gates automatic append behind a fresh, real downward user scroll. The hook\n * owns neither fetching nor row state: callers feed it the data hook receipt\n * and render its explicit request as the accessible fallback control.\n */\nexport function useScrollPagination({\n containers,\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode = \"scroll\",\n thresholdPx = DEFAULT_THRESHOLD_PX,\n intentTimeoutMs = DEFAULT_INTENT_TIMEOUT_MS,\n enabled = true,\n}: UseScrollPaginationOptions): UseScrollPaginationResult {\n const [requestError, setRequestError] = useState<Error | null>(null);\n const latestRef = useRef({\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n });\n latestRef.current = {\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n };\n\n const intentRef = useRef<ScrollIntent | null>(null);\n const inputSequenceRef = useRef(0);\n const consumedSequenceRef = useRef(0);\n const requestGenerationRef = useRef(0);\n const inFlightGenerationRef = useRef<number | null>(null);\n const requestErrorRef = useRef<Error | null>(null);\n const mountedRef = useRef(false);\n // Listener effects reattach when consumers recreate their container arrays.\n // Keep the last real scroll position with the element, not with one effect.\n const observedTopRef = useRef<WeakMap<HTMLElement, number>>(new WeakMap());\n const effectiveError = error ?? requestError;\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const requestNextPage = async () => {\n const current = latestRef.current;\n if (\n !current.enabled ||\n (!current.hasNextPage && effectiveError === null) ||\n current.loading ||\n current.isFetchingNextPage ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n const generation = ++requestGenerationRef.current;\n inFlightGenerationRef.current = generation;\n requestErrorRef.current = null;\n setRequestError(null);\n try {\n await current.loadNextPage();\n } catch (raw) {\n const nextError = raw instanceof Error ? raw : new Error(String(raw));\n if (inFlightGenerationRef.current === generation && latestRef.current.queryKey === current.queryKey) {\n requestErrorRef.current = nextError;\n if (mountedRef.current) setRequestError(nextError);\n }\n } finally {\n if (inFlightGenerationRef.current === generation) {\n inFlightGenerationRef.current = null;\n intentRef.current = null;\n }\n }\n };\n\n useEffect(() => {\n intentRef.current = null;\n observedTopRef.current = new WeakMap();\n consumedSequenceRef.current = inputSequenceRef.current;\n requestErrorRef.current = null;\n setRequestError(null);\n // A query change makes every existing completion stale. Its finally block\n // cannot clear a new request because generations are monotonically unique.\n inFlightGenerationRef.current = null;\n requestGenerationRef.current += 1;\n }, [queryKey]);\n\n useEffect(() => {\n if (mode !== \"scroll\" || !enabled || typeof window === \"undefined\") return;\n\n const attachable = containers\n .map(resolveContainer)\n .filter((container): container is HTMLElement => container !== null);\n\n const setIntent = (container: HTMLElement, startTop: number) => {\n const current = latestRef.current;\n if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;\n inputSequenceRef.current += 1;\n intentRef.current = {\n queryKey: latestRef.current.queryKey,\n container,\n // The compositor can advance downward before the passive input\n // listener, while DOM/test callers can move upward before its scroll\n // notification. Either way, the lower position is the only safe\n // baseline for proving that this downward intent caused movement.\n startTop: Math.min(startTop, container.scrollTop),\n sequence: inputSequenceRef.current,\n expiresAt: Date.now() + intentTimeoutMs,\n };\n };\n\n const handleScroll = (container: HTMLElement) => {\n const intent = intentRef.current;\n const current = latestRef.current;\n if (\n !intent ||\n intent.queryKey !== current.queryKey ||\n intent.container !== container ||\n Date.now() > intent.expiresAt ||\n container.scrollTop <= intent.startTop ||\n intent.sequence <= consumedSequenceRef.current ||\n !isVisibleContainer(container) ||\n !isNearBottom(container, current.thresholdPx) ||\n !current.hasNextPage ||\n current.loading ||\n current.isFetchingNextPage ||\n current.error !== null ||\n requestErrorRef.current !== null ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n consumedSequenceRef.current = intent.sequence;\n void requestNextPage();\n };\n\n const cleanups = attachable.map((container) => {\n // A passive wheel listener may run after the compositor has already\n // advanced scrollTop, but before the asynchronous scroll notification.\n // Preserve the last *observed* position so that ordering still proves\n // movement from this trusted input without authorizing scroll-only code.\n if (!observedTopRef.current.has(container)) observedTopRef.current.set(container, container.scrollTop);\n const lastObservedTop = () => observedTopRef.current.get(container) ?? container.scrollTop;\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {\n setIntent(container, lastObservedTop());\n }\n };\n const onTouchStart = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n if (touch) container.dataset.matrxPaginationTouchY = String(touch.clientY);\n };\n const onTouchMove = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n const startY = Number(container.dataset.matrxPaginationTouchY);\n if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {\n setIntent(container, lastObservedTop());\n }\n };\n const onTouchEnd = () => {\n delete container.dataset.matrxPaginationTouchY;\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (isDownwardKeyboardInput(event)) setIntent(container, lastObservedTop());\n };\n const onPointerDown = (event: PointerEvent) => {\n if (isNativeScrollbarPointer(event, container)) setIntent(container, lastObservedTop());\n };\n const onScroll = () => {\n handleScroll(container);\n observedTopRef.current.set(container, container.scrollTop);\n };\n\n container.addEventListener(\"wheel\", onWheel, { passive: true });\n container.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n container.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n container.addEventListener(\"touchend\", onTouchEnd, { passive: true });\n container.addEventListener(\"touchcancel\", onTouchEnd, { passive: true });\n container.addEventListener(\"keydown\", onKeyDown);\n container.addEventListener(\"pointerdown\", onPointerDown, { passive: true });\n container.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => {\n delete container.dataset.matrxPaginationTouchY;\n container.removeEventListener(\"wheel\", onWheel);\n container.removeEventListener(\"touchstart\", onTouchStart);\n container.removeEventListener(\"touchmove\", onTouchMove);\n container.removeEventListener(\"touchend\", onTouchEnd);\n container.removeEventListener(\"touchcancel\", onTouchEnd);\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeEventListener(\"pointerdown\", onPointerDown);\n container.removeEventListener(\"scroll\", onScroll);\n };\n });\n return () => cleanups.forEach((cleanup) => cleanup());\n }, [containers, enabled, intentTimeoutMs, mode]);\n\n return {\n requestNextPage,\n canLoadMore: enabled && !loading && !isFetchingNextPage && (hasNextPage || effectiveError !== null),\n error: effectiveError,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -78,6 +78,7 @@ function useScrollPagination({
|
|
|
78
78
|
const inFlightGenerationRef = useRef(null);
|
|
79
79
|
const requestErrorRef = useRef(null);
|
|
80
80
|
const mountedRef = useRef(false);
|
|
81
|
+
const observedTopRef = useRef(/* @__PURE__ */ new WeakMap());
|
|
81
82
|
const effectiveError = error ?? requestError;
|
|
82
83
|
useEffect(() => {
|
|
83
84
|
mountedRef.current = true;
|
|
@@ -111,6 +112,7 @@ function useScrollPagination({
|
|
|
111
112
|
};
|
|
112
113
|
useEffect(() => {
|
|
113
114
|
intentRef.current = null;
|
|
115
|
+
observedTopRef.current = /* @__PURE__ */ new WeakMap();
|
|
114
116
|
consumedSequenceRef.current = inputSequenceRef.current;
|
|
115
117
|
requestErrorRef.current = null;
|
|
116
118
|
setRequestError(null);
|
|
@@ -120,14 +122,18 @@ function useScrollPagination({
|
|
|
120
122
|
useEffect(() => {
|
|
121
123
|
if (mode !== "scroll" || !enabled || typeof window === "undefined") return;
|
|
122
124
|
const attachable = containers.map(resolveContainer).filter((container) => container !== null);
|
|
123
|
-
const setIntent = (container) => {
|
|
125
|
+
const setIntent = (container, startTop) => {
|
|
124
126
|
const current = latestRef.current;
|
|
125
127
|
if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;
|
|
126
128
|
inputSequenceRef.current += 1;
|
|
127
129
|
intentRef.current = {
|
|
128
130
|
queryKey: latestRef.current.queryKey,
|
|
129
131
|
container,
|
|
130
|
-
|
|
132
|
+
// The compositor can advance downward before the passive input
|
|
133
|
+
// listener, while DOM/test callers can move upward before its scroll
|
|
134
|
+
// notification. Either way, the lower position is the only safe
|
|
135
|
+
// baseline for proving that this downward intent caused movement.
|
|
136
|
+
startTop: Math.min(startTop, container.scrollTop),
|
|
131
137
|
sequence: inputSequenceRef.current,
|
|
132
138
|
expiresAt: Date.now() + intentTimeoutMs
|
|
133
139
|
};
|
|
@@ -142,9 +148,11 @@ function useScrollPagination({
|
|
|
142
148
|
void requestNextPage();
|
|
143
149
|
};
|
|
144
150
|
const cleanups = attachable.map((container) => {
|
|
151
|
+
if (!observedTopRef.current.has(container)) observedTopRef.current.set(container, container.scrollTop);
|
|
152
|
+
const lastObservedTop = () => observedTopRef.current.get(container) ?? container.scrollTop;
|
|
145
153
|
const onWheel = (event) => {
|
|
146
154
|
if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
|
|
147
|
-
setIntent(container);
|
|
155
|
+
setIntent(container, lastObservedTop());
|
|
148
156
|
}
|
|
149
157
|
};
|
|
150
158
|
const onTouchStart = (event) => {
|
|
@@ -155,19 +163,22 @@ function useScrollPagination({
|
|
|
155
163
|
const touch = event.touches.item(0);
|
|
156
164
|
const startY = Number(container.dataset.matrxPaginationTouchY);
|
|
157
165
|
if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {
|
|
158
|
-
setIntent(container);
|
|
166
|
+
setIntent(container, lastObservedTop());
|
|
159
167
|
}
|
|
160
168
|
};
|
|
161
169
|
const onTouchEnd = () => {
|
|
162
170
|
delete container.dataset.matrxPaginationTouchY;
|
|
163
171
|
};
|
|
164
172
|
const onKeyDown = (event) => {
|
|
165
|
-
if (isDownwardKeyboardInput(event)) setIntent(container);
|
|
173
|
+
if (isDownwardKeyboardInput(event)) setIntent(container, lastObservedTop());
|
|
166
174
|
};
|
|
167
175
|
const onPointerDown = (event) => {
|
|
168
|
-
if (isNativeScrollbarPointer(event, container)) setIntent(container);
|
|
176
|
+
if (isNativeScrollbarPointer(event, container)) setIntent(container, lastObservedTop());
|
|
177
|
+
};
|
|
178
|
+
const onScroll = () => {
|
|
179
|
+
handleScroll(container);
|
|
180
|
+
observedTopRef.current.set(container, container.scrollTop);
|
|
169
181
|
};
|
|
170
|
-
const onScroll = () => handleScroll(container);
|
|
171
182
|
container.addEventListener("wheel", onWheel, { passive: true });
|
|
172
183
|
container.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
173
184
|
container.addEventListener("touchmove", onTouchMove, { passive: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/data-table/useScrollPagination.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport type ScrollPaginationContainer =\n | HTMLElement\n | RefObject<HTMLElement | null>\n | null;\n\nexport interface UseScrollPaginationOptions {\n /** Every visible desktop/mobile scroll container that may present this table. */\n containers: readonly ScrollPaginationContainer[];\n /** Immutable identity of every source-affecting query value. */\n queryKey: string;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n loading: boolean;\n /** Automatic scrolling never retries a failed append. */\n error: Error | null;\n loadNextPage: () => Promise<void> | void;\n /** Manual mode exposes the explicit request only; it installs no listeners. */\n mode?: \"scroll\" | \"manual\";\n /** Distance from the bottom at which a qualifying user scroll may append. */\n thresholdPx?: number;\n /** How long an input may authorize the following real scroll movement. */\n intentTimeoutMs?: number;\n enabled?: boolean;\n}\n\nexport interface UseScrollPaginationResult {\n /** For the table's accessible Load more / Retry control. */\n requestNextPage: () => Promise<void>;\n canLoadMore: boolean;\n /** Includes an unexpected loader rejection until the person explicitly retries. */\n error: Error | null;\n}\n\ninterface ScrollIntent {\n queryKey: string;\n container: HTMLElement;\n startTop: number;\n sequence: number;\n expiresAt: number;\n}\n\nconst DEFAULT_THRESHOLD_PX = 96;\nconst DEFAULT_INTENT_TIMEOUT_MS = 1_200;\n\nfunction resolveContainer(container: ScrollPaginationContainer): HTMLElement | null {\n if (!container) return null;\n return container instanceof HTMLElement ? container : container.current;\n}\n\nfunction isVisibleContainer(container: HTMLElement): boolean {\n if (!container.isConnected) return false;\n for (let node: HTMLElement | null = container; node; node = node.parentElement) {\n const style = window.getComputedStyle(node);\n if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n }\n return true;\n}\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n return target.isContentEditable || Boolean(target.closest(\"input, textarea, select, [contenteditable='true']\"));\n}\n\nfunction isDownwardKeyboardInput(event: KeyboardEvent): boolean {\n if (isEditableTarget(event.target)) return false;\n return [\"ArrowDown\", \"PageDown\", \"End\", \" \", \"Spacebar\"].includes(event.key);\n}\n\nfunction isNearBottom(container: HTMLElement, thresholdPx: number): boolean {\n return container.scrollHeight - container.clientHeight - container.scrollTop <= thresholdPx;\n}\n\nfunction isNativeScrollbarPointer(event: PointerEvent, container: HTMLElement): boolean {\n // Overlay scrollbars expose no measurable gutter. Do not guess: Load more is\n // the accessible, reliable fallback there. A content click must never grant\n // permission for a later programmatic scroll to append rows.\n if (event.pointerType !== \"mouse\" || event.button !== 0 || !event.isPrimary) return false;\n const gutterWidth = container.offsetWidth - container.clientWidth;\n if (gutterWidth <= 0 || container.scrollHeight <= container.clientHeight) return false;\n const rect = container.getBoundingClientRect();\n return event.clientX >= rect.right - gutterWidth && event.clientX <= rect.right;\n}\n\n/**\n * Gates automatic append behind a fresh, real downward user scroll. The hook\n * owns neither fetching nor row state: callers feed it the data hook receipt\n * and render its explicit request as the accessible fallback control.\n */\nexport function useScrollPagination({\n containers,\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode = \"scroll\",\n thresholdPx = DEFAULT_THRESHOLD_PX,\n intentTimeoutMs = DEFAULT_INTENT_TIMEOUT_MS,\n enabled = true,\n}: UseScrollPaginationOptions): UseScrollPaginationResult {\n const [requestError, setRequestError] = useState<Error | null>(null);\n const latestRef = useRef({\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n });\n latestRef.current = {\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n };\n\n const intentRef = useRef<ScrollIntent | null>(null);\n const inputSequenceRef = useRef(0);\n const consumedSequenceRef = useRef(0);\n const requestGenerationRef = useRef(0);\n const inFlightGenerationRef = useRef<number | null>(null);\n const requestErrorRef = useRef<Error | null>(null);\n const mountedRef = useRef(false);\n const effectiveError = error ?? requestError;\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const requestNextPage = async () => {\n const current = latestRef.current;\n if (\n !current.enabled ||\n (!current.hasNextPage && effectiveError === null) ||\n current.loading ||\n current.isFetchingNextPage ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n const generation = ++requestGenerationRef.current;\n inFlightGenerationRef.current = generation;\n requestErrorRef.current = null;\n setRequestError(null);\n try {\n await current.loadNextPage();\n } catch (raw) {\n const nextError = raw instanceof Error ? raw : new Error(String(raw));\n if (inFlightGenerationRef.current === generation && latestRef.current.queryKey === current.queryKey) {\n requestErrorRef.current = nextError;\n if (mountedRef.current) setRequestError(nextError);\n }\n } finally {\n if (inFlightGenerationRef.current === generation) {\n inFlightGenerationRef.current = null;\n intentRef.current = null;\n }\n }\n };\n\n useEffect(() => {\n intentRef.current = null;\n consumedSequenceRef.current = inputSequenceRef.current;\n requestErrorRef.current = null;\n setRequestError(null);\n // A query change makes every existing completion stale. Its finally block\n // cannot clear a new request because generations are monotonically unique.\n inFlightGenerationRef.current = null;\n requestGenerationRef.current += 1;\n }, [queryKey]);\n\n useEffect(() => {\n if (mode !== \"scroll\" || !enabled || typeof window === \"undefined\") return;\n\n const attachable = containers\n .map(resolveContainer)\n .filter((container): container is HTMLElement => container !== null);\n\n const setIntent = (container: HTMLElement) => {\n const current = latestRef.current;\n if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;\n inputSequenceRef.current += 1;\n intentRef.current = {\n queryKey: latestRef.current.queryKey,\n container,\n startTop: container.scrollTop,\n sequence: inputSequenceRef.current,\n expiresAt: Date.now() + intentTimeoutMs,\n };\n };\n\n const handleScroll = (container: HTMLElement) => {\n const intent = intentRef.current;\n const current = latestRef.current;\n if (\n !intent ||\n intent.queryKey !== current.queryKey ||\n intent.container !== container ||\n Date.now() > intent.expiresAt ||\n container.scrollTop <= intent.startTop ||\n intent.sequence <= consumedSequenceRef.current ||\n !isVisibleContainer(container) ||\n !isNearBottom(container, current.thresholdPx) ||\n !current.hasNextPage ||\n current.loading ||\n current.isFetchingNextPage ||\n current.error !== null ||\n requestErrorRef.current !== null ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n consumedSequenceRef.current = intent.sequence;\n void requestNextPage();\n };\n\n const cleanups = attachable.map((container) => {\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {\n setIntent(container);\n }\n };\n const onTouchStart = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n if (touch) container.dataset.matrxPaginationTouchY = String(touch.clientY);\n };\n const onTouchMove = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n const startY = Number(container.dataset.matrxPaginationTouchY);\n if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {\n setIntent(container);\n }\n };\n const onTouchEnd = () => {\n delete container.dataset.matrxPaginationTouchY;\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (isDownwardKeyboardInput(event)) setIntent(container);\n };\n const onPointerDown = (event: PointerEvent) => {\n if (isNativeScrollbarPointer(event, container)) setIntent(container);\n };\n const onScroll = () => handleScroll(container);\n\n container.addEventListener(\"wheel\", onWheel, { passive: true });\n container.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n container.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n container.addEventListener(\"touchend\", onTouchEnd, { passive: true });\n container.addEventListener(\"touchcancel\", onTouchEnd, { passive: true });\n container.addEventListener(\"keydown\", onKeyDown);\n container.addEventListener(\"pointerdown\", onPointerDown, { passive: true });\n container.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => {\n delete container.dataset.matrxPaginationTouchY;\n container.removeEventListener(\"wheel\", onWheel);\n container.removeEventListener(\"touchstart\", onTouchStart);\n container.removeEventListener(\"touchmove\", onTouchMove);\n container.removeEventListener(\"touchend\", onTouchEnd);\n container.removeEventListener(\"touchcancel\", onTouchEnd);\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeEventListener(\"pointerdown\", onPointerDown);\n container.removeEventListener(\"scroll\", onScroll);\n };\n });\n return () => cleanups.forEach((cleanup) => cleanup());\n }, [containers, enabled, intentTimeoutMs, mode]);\n\n return {\n requestNextPage,\n canLoadMore: enabled && !loading && !isFetchingNextPage && (hasNextPage || effectiveError !== null),\n error: effectiveError,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/data-table/useScrollPagination.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport type ScrollPaginationContainer =\n | HTMLElement\n | RefObject<HTMLElement | null>\n | null;\n\nexport interface UseScrollPaginationOptions {\n /** Every visible desktop/mobile scroll container that may present this table. */\n containers: readonly ScrollPaginationContainer[];\n /** Immutable identity of every source-affecting query value. */\n queryKey: string;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n loading: boolean;\n /** Automatic scrolling never retries a failed append. */\n error: Error | null;\n loadNextPage: () => Promise<void> | void;\n /** Manual mode exposes the explicit request only; it installs no listeners. */\n mode?: \"scroll\" | \"manual\";\n /** Distance from the bottom at which a qualifying user scroll may append. */\n thresholdPx?: number;\n /** How long an input may authorize the following real scroll movement. */\n intentTimeoutMs?: number;\n enabled?: boolean;\n}\n\nexport interface UseScrollPaginationResult {\n /** For the table's accessible Load more / Retry control. */\n requestNextPage: () => Promise<void>;\n canLoadMore: boolean;\n /** Includes an unexpected loader rejection until the person explicitly retries. */\n error: Error | null;\n}\n\ninterface ScrollIntent {\n queryKey: string;\n container: HTMLElement;\n startTop: number;\n sequence: number;\n expiresAt: number;\n}\n\nconst DEFAULT_THRESHOLD_PX = 96;\nconst DEFAULT_INTENT_TIMEOUT_MS = 1_200;\n\nfunction resolveContainer(container: ScrollPaginationContainer): HTMLElement | null {\n if (!container) return null;\n return container instanceof HTMLElement ? container : container.current;\n}\n\nfunction isVisibleContainer(container: HTMLElement): boolean {\n if (!container.isConnected) return false;\n for (let node: HTMLElement | null = container; node; node = node.parentElement) {\n const style = window.getComputedStyle(node);\n if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n }\n return true;\n}\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n return target.isContentEditable || Boolean(target.closest(\"input, textarea, select, [contenteditable='true']\"));\n}\n\nfunction isDownwardKeyboardInput(event: KeyboardEvent): boolean {\n if (isEditableTarget(event.target)) return false;\n return [\"ArrowDown\", \"PageDown\", \"End\", \" \", \"Spacebar\"].includes(event.key);\n}\n\nfunction isNearBottom(container: HTMLElement, thresholdPx: number): boolean {\n return container.scrollHeight - container.clientHeight - container.scrollTop <= thresholdPx;\n}\n\nfunction isNativeScrollbarPointer(event: PointerEvent, container: HTMLElement): boolean {\n // Overlay scrollbars expose no measurable gutter. Do not guess: Load more is\n // the accessible, reliable fallback there. A content click must never grant\n // permission for a later programmatic scroll to append rows.\n if (event.pointerType !== \"mouse\" || event.button !== 0 || !event.isPrimary) return false;\n const gutterWidth = container.offsetWidth - container.clientWidth;\n if (gutterWidth <= 0 || container.scrollHeight <= container.clientHeight) return false;\n const rect = container.getBoundingClientRect();\n return event.clientX >= rect.right - gutterWidth && event.clientX <= rect.right;\n}\n\n/**\n * Gates automatic append behind a fresh, real downward user scroll. The hook\n * owns neither fetching nor row state: callers feed it the data hook receipt\n * and render its explicit request as the accessible fallback control.\n */\nexport function useScrollPagination({\n containers,\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode = \"scroll\",\n thresholdPx = DEFAULT_THRESHOLD_PX,\n intentTimeoutMs = DEFAULT_INTENT_TIMEOUT_MS,\n enabled = true,\n}: UseScrollPaginationOptions): UseScrollPaginationResult {\n const [requestError, setRequestError] = useState<Error | null>(null);\n const latestRef = useRef({\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n });\n latestRef.current = {\n queryKey,\n hasNextPage,\n isFetchingNextPage,\n loading,\n error,\n loadNextPage,\n mode,\n thresholdPx,\n enabled,\n };\n\n const intentRef = useRef<ScrollIntent | null>(null);\n const inputSequenceRef = useRef(0);\n const consumedSequenceRef = useRef(0);\n const requestGenerationRef = useRef(0);\n const inFlightGenerationRef = useRef<number | null>(null);\n const requestErrorRef = useRef<Error | null>(null);\n const mountedRef = useRef(false);\n // Listener effects reattach when consumers recreate their container arrays.\n // Keep the last real scroll position with the element, not with one effect.\n const observedTopRef = useRef<WeakMap<HTMLElement, number>>(new WeakMap());\n const effectiveError = error ?? requestError;\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const requestNextPage = async () => {\n const current = latestRef.current;\n if (\n !current.enabled ||\n (!current.hasNextPage && effectiveError === null) ||\n current.loading ||\n current.isFetchingNextPage ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n const generation = ++requestGenerationRef.current;\n inFlightGenerationRef.current = generation;\n requestErrorRef.current = null;\n setRequestError(null);\n try {\n await current.loadNextPage();\n } catch (raw) {\n const nextError = raw instanceof Error ? raw : new Error(String(raw));\n if (inFlightGenerationRef.current === generation && latestRef.current.queryKey === current.queryKey) {\n requestErrorRef.current = nextError;\n if (mountedRef.current) setRequestError(nextError);\n }\n } finally {\n if (inFlightGenerationRef.current === generation) {\n inFlightGenerationRef.current = null;\n intentRef.current = null;\n }\n }\n };\n\n useEffect(() => {\n intentRef.current = null;\n observedTopRef.current = new WeakMap();\n consumedSequenceRef.current = inputSequenceRef.current;\n requestErrorRef.current = null;\n setRequestError(null);\n // A query change makes every existing completion stale. Its finally block\n // cannot clear a new request because generations are monotonically unique.\n inFlightGenerationRef.current = null;\n requestGenerationRef.current += 1;\n }, [queryKey]);\n\n useEffect(() => {\n if (mode !== \"scroll\" || !enabled || typeof window === \"undefined\") return;\n\n const attachable = containers\n .map(resolveContainer)\n .filter((container): container is HTMLElement => container !== null);\n\n const setIntent = (container: HTMLElement, startTop: number) => {\n const current = latestRef.current;\n if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;\n inputSequenceRef.current += 1;\n intentRef.current = {\n queryKey: latestRef.current.queryKey,\n container,\n // The compositor can advance downward before the passive input\n // listener, while DOM/test callers can move upward before its scroll\n // notification. Either way, the lower position is the only safe\n // baseline for proving that this downward intent caused movement.\n startTop: Math.min(startTop, container.scrollTop),\n sequence: inputSequenceRef.current,\n expiresAt: Date.now() + intentTimeoutMs,\n };\n };\n\n const handleScroll = (container: HTMLElement) => {\n const intent = intentRef.current;\n const current = latestRef.current;\n if (\n !intent ||\n intent.queryKey !== current.queryKey ||\n intent.container !== container ||\n Date.now() > intent.expiresAt ||\n container.scrollTop <= intent.startTop ||\n intent.sequence <= consumedSequenceRef.current ||\n !isVisibleContainer(container) ||\n !isNearBottom(container, current.thresholdPx) ||\n !current.hasNextPage ||\n current.loading ||\n current.isFetchingNextPage ||\n current.error !== null ||\n requestErrorRef.current !== null ||\n inFlightGenerationRef.current !== null\n ) {\n return;\n }\n consumedSequenceRef.current = intent.sequence;\n void requestNextPage();\n };\n\n const cleanups = attachable.map((container) => {\n // A passive wheel listener may run after the compositor has already\n // advanced scrollTop, but before the asynchronous scroll notification.\n // Preserve the last *observed* position so that ordering still proves\n // movement from this trusted input without authorizing scroll-only code.\n if (!observedTopRef.current.has(container)) observedTopRef.current.set(container, container.scrollTop);\n const lastObservedTop = () => observedTopRef.current.get(container) ?? container.scrollTop;\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {\n setIntent(container, lastObservedTop());\n }\n };\n const onTouchStart = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n if (touch) container.dataset.matrxPaginationTouchY = String(touch.clientY);\n };\n const onTouchMove = (event: TouchEvent) => {\n const touch = event.touches.item(0);\n const startY = Number(container.dataset.matrxPaginationTouchY);\n if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {\n setIntent(container, lastObservedTop());\n }\n };\n const onTouchEnd = () => {\n delete container.dataset.matrxPaginationTouchY;\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (isDownwardKeyboardInput(event)) setIntent(container, lastObservedTop());\n };\n const onPointerDown = (event: PointerEvent) => {\n if (isNativeScrollbarPointer(event, container)) setIntent(container, lastObservedTop());\n };\n const onScroll = () => {\n handleScroll(container);\n observedTopRef.current.set(container, container.scrollTop);\n };\n\n container.addEventListener(\"wheel\", onWheel, { passive: true });\n container.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n container.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n container.addEventListener(\"touchend\", onTouchEnd, { passive: true });\n container.addEventListener(\"touchcancel\", onTouchEnd, { passive: true });\n container.addEventListener(\"keydown\", onKeyDown);\n container.addEventListener(\"pointerdown\", onPointerDown, { passive: true });\n container.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => {\n delete container.dataset.matrxPaginationTouchY;\n container.removeEventListener(\"wheel\", onWheel);\n container.removeEventListener(\"touchstart\", onTouchStart);\n container.removeEventListener(\"touchmove\", onTouchMove);\n container.removeEventListener(\"touchend\", onTouchEnd);\n container.removeEventListener(\"touchcancel\", onTouchEnd);\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeEventListener(\"pointerdown\", onPointerDown);\n container.removeEventListener(\"scroll\", onScroll);\n };\n });\n return () => cleanups.forEach((cleanup) => cleanup());\n }, [containers, enabled, intentTimeoutMs, mode]);\n\n return {\n requestNextPage,\n canLoadMore: enabled && !loading && !isFetchingNextPage && (hasNextPage || effectiveError !== null),\n error: effectiveError,\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED