@aiquants/virtualscroll 2.6.0 → 2.7.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/CHANGELOG.md +31 -0
- package/README.md +7 -3
- package/dist/ScrollPane.d.cts +15 -0
- package/dist/ScrollPane.d.ts +15 -0
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/VirtualScroll.d.cts +5 -0
- package/dist/VirtualScroll.d.ts +5 -0
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1255 -1249
- package/package.json +1 -1
- package/src/ScrollPane.tsx +44 -9
- package/src/VirtualScroll.tsx +7 -1
package/package.json
CHANGED
package/src/ScrollPane.tsx
CHANGED
|
@@ -99,6 +99,21 @@ export type ScrollPaneProps = {
|
|
|
99
99
|
renderThumbOverlay?: (props: ScrollBarThumbOverlayRenderProps) => React.ReactNode
|
|
100
100
|
/** Multiplier applied to wheel delta for faster or slower scrolling. / スクロール速度を調整するためのホイールデルタの倍率。 */
|
|
101
101
|
wheelSpeedMultiplier?: number
|
|
102
|
+
/**
|
|
103
|
+
* How wheel input behaves at the pane's edges (default: "auto").
|
|
104
|
+
* ペインの端でホイールがどう振る舞うか (既定: "auto")。
|
|
105
|
+
*
|
|
106
|
+
* - `"auto"` (既定): その向きへ**もう動けない**縦ホイールは消費せず、祖先 (ページ) の
|
|
107
|
+
* スクロールへ連鎖させる。ネイティブのスクロール領域と同じ挙動
|
|
108
|
+
* (CSS `overscroll-behavior: auto` 相当)。端に**達する**ノッチは部分消費して端で止まり、
|
|
109
|
+
* **次の**ノッチから連鎖する (ネイティブと同じ段付き)。
|
|
110
|
+
* - `"contain"`: 端でも消費し続け、ページへ連鎖させない (2.6.0 以前の挙動)。
|
|
111
|
+
* モーダル内の一覧など「背後を絶対に動かしたくない」消費側向け。
|
|
112
|
+
*
|
|
113
|
+
* ❗ 対象は**縦ホイールのみ**。横成分は `onWheelHorizontal` の所有者 (消費側) の責務であり、
|
|
114
|
+
* ポインタドラッグ / タッチの連鎖はネイティブ相当の慣性譲渡が別問題のため対象外。
|
|
115
|
+
*/
|
|
116
|
+
overscrollBehavior?: "auto" | "contain"
|
|
102
117
|
/**
|
|
103
118
|
* Callback delegating horizontal wheel/trackpad delta to an upstream owner.
|
|
104
119
|
* When provided, horizontal-dominant (or shift+wheel) gestures are consumed here
|
|
@@ -287,6 +302,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
287
302
|
itemCount,
|
|
288
303
|
renderThumbOverlay,
|
|
289
304
|
wheelSpeedMultiplier = 1,
|
|
305
|
+
overscrollBehavior = "auto",
|
|
290
306
|
onWheelHorizontal,
|
|
291
307
|
contentInsets,
|
|
292
308
|
visibleStartIndex,
|
|
@@ -773,11 +789,11 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
773
789
|
// ❗ **代入のタイミングは `sizeRef` と必ず揃える (どちらも layout effect のみ)。**
|
|
774
790
|
// 片方だけ render 本体に残すと、中断されたトランジション中に `applyWheel` と `scrollTo` が
|
|
775
791
|
// 別々の世界の寸法で判断する最悪の組み合わせになる。
|
|
776
|
-
const wheelPolicyRef = useRef({ isScrollable, viewportSize, wheelSpeedMultiplier })
|
|
792
|
+
const wheelPolicyRef = useRef({ isScrollable, viewportSize, wheelSpeedMultiplier, overscrollBehavior })
|
|
777
793
|
useLayoutEffect(() => {
|
|
778
794
|
// sizeRef と同じ理由で「最後に commit された値」だけを保持する (A-1。sizeRef のコメント参照)
|
|
779
|
-
wheelPolicyRef.current = { isScrollable, viewportSize, wheelSpeedMultiplier }
|
|
780
|
-
}, [isScrollable, viewportSize, wheelSpeedMultiplier])
|
|
795
|
+
wheelPolicyRef.current = { isScrollable, viewportSize, wheelSpeedMultiplier, overscrollBehavior }
|
|
796
|
+
}, [isScrollable, viewportSize, wheelSpeedMultiplier, overscrollBehavior])
|
|
781
797
|
|
|
782
798
|
/**
|
|
783
799
|
* Applies one wheel event with the pane's own rules and reports whether it was consumed.
|
|
@@ -797,7 +813,11 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
797
813
|
* 2. 横優勢 (shift+ホイールを含む) は `onWheelHorizontal` へ委譲。未指定なら消費せず祖先へ委ねる
|
|
798
814
|
* 3. スクロール不能なら消費しない
|
|
799
815
|
* 4. px 換算後の縦成分が 0 なら消費しない (適用できる量が無いのに祖先のスクロールを奪わない)
|
|
800
|
-
* 5.
|
|
816
|
+
* 5. 実効 delta の確定 (速度倍率を先に適用)
|
|
817
|
+
* 6. `overscrollBehavior` が "contain" でなければ端判定: その向きへもう動けなければ
|
|
818
|
+
* 消費せず祖先へ連鎖 (2.7.0)
|
|
819
|
+
* 7. 消費済みの印 + `preventDefault()` + 慣性停止
|
|
820
|
+
* 8. 相対スクロール
|
|
801
821
|
*
|
|
802
822
|
* @param event - The wheel event to apply / 適用するホイールイベント
|
|
803
823
|
* @returns True when this pane applied the event / このペインが適用した場合に true。
|
|
@@ -823,7 +843,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
823
843
|
// 保持済みハンドルは preventDefault し続け、ページのスクロールを殺す)。
|
|
824
844
|
// `VirtualScrollHandle.applyWheel` は毎回 ref を辿るため凍結しない。両者の
|
|
825
845
|
// 非対称は `WheelBridgeTarget` が両方を等価に扱う設計と噛み合わない。
|
|
826
|
-
const { isScrollable: currentIsScrollable, viewportSize: currentViewportSize, wheelSpeedMultiplier: currentWheelSpeedMultiplier } = wheelPolicyRef.current
|
|
846
|
+
const { isScrollable: currentIsScrollable, viewportSize: currentViewportSize, wheelSpeedMultiplier: currentWheelSpeedMultiplier, overscrollBehavior: currentOverscrollBehavior } = wheelPolicyRef.current
|
|
827
847
|
const axes = resolveWheelAxes(event, currentViewportSize)
|
|
828
848
|
|
|
829
849
|
// ❗ ctrl+wheel はブラウザのズーム (ピンチズーム/ctrl+shift+ホイール等) なので横取りせず常に素通しする。
|
|
@@ -861,15 +881,30 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
861
881
|
return false
|
|
862
882
|
}
|
|
863
883
|
|
|
864
|
-
markWheelConsumed(event)
|
|
865
|
-
event.preventDefault()
|
|
866
|
-
stopInertia()
|
|
867
|
-
|
|
868
884
|
let deltaY = axes.verticalDelta
|
|
869
885
|
if (currentWheelSpeedMultiplier !== 1) {
|
|
870
886
|
deltaY *= currentWheelSpeedMultiplier
|
|
871
887
|
}
|
|
872
888
|
|
|
889
|
+
// ❗ 端の連鎖 (A-2): その向きへ**もう動けない**ホイールは消費せず祖先へ委ねる
|
|
890
|
+
// (ネイティブの `overscroll-behavior: auto` 相当)。判定は倍率適用後の実効 delta の
|
|
891
|
+
// 向き × committed 位置で行う。端に**達する**ノッチはここを素通りして部分消費され
|
|
892
|
+
// (クランプが端で止める)、次のノッチからこの分岐で連鎖する — ネイティブと同じ段付き。
|
|
893
|
+
// `"contain"` は 2.6.0 以前のトラップ挙動 (モーダル内の一覧など背後を動かしたくない
|
|
894
|
+
// 消費側向け) を明示的に維持する。
|
|
895
|
+
if (currentOverscrollBehavior !== "contain") {
|
|
896
|
+
const currentPosition = scrollPositionRef.current
|
|
897
|
+
// contentSize は sizeRef (committed。wheelPolicyRef と同じ layout effect 同期) から読む
|
|
898
|
+
const maxScrollPosition = Math.max(sizeRef.current.contentSize - currentViewportSize, 0)
|
|
899
|
+
if ((deltaY > 0 && currentPosition >= maxScrollPosition) || (deltaY < 0 && currentPosition <= 0)) {
|
|
900
|
+
return false
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
markWheelConsumed(event)
|
|
905
|
+
event.preventDefault()
|
|
906
|
+
stopInertia()
|
|
907
|
+
|
|
873
908
|
// ホットパスのためログ引数はサンクで遅延評価し、抑制時は DOM 読み取り (scrollTop) も発生させない
|
|
874
909
|
Logger.debug("[ScrollPane] wheel event", () => ({ deltaY, scrollPosition: scrollPositionRef.current, wheelSpeedMultiplier: currentWheelSpeedMultiplier, deltaMode: event.deltaMode, scrollTop: contentAreaRef.current?.scrollTop }))
|
|
875
910
|
|
package/src/VirtualScroll.tsx
CHANGED
|
@@ -150,6 +150,11 @@ export type VirtualScrollBehaviorOptions = {
|
|
|
150
150
|
enableKeyboardNavigation?: boolean
|
|
151
151
|
wheelSpeedMultiplier?: number
|
|
152
152
|
inertiaOptions?: ScrollPaneProps["inertiaOptions"]
|
|
153
|
+
/**
|
|
154
|
+
* How wheel input behaves at the list's edges (default: "auto" = chain to ancestors).
|
|
155
|
+
* 一覧の端でホイールがどう振る舞うか (既定: "auto" = 祖先へ連鎖)。`ScrollPaneProps["overscrollBehavior"]` 参照。
|
|
156
|
+
*/
|
|
157
|
+
overscrollBehavior?: ScrollPaneProps["overscrollBehavior"]
|
|
153
158
|
clipItemHeight?: boolean
|
|
154
159
|
/**
|
|
155
160
|
* When `true`, the underlying Fenwick tree fully re-samples on every `getItemHeight` change
|
|
@@ -914,7 +919,7 @@ const VirtualScrollInner = <T,>(
|
|
|
914
919
|
) => {
|
|
915
920
|
const { width: scrollBarWidth, enableThumbDrag, enableTrackClick, enableArrowButtons, enableScrollToTopBottomButtons, renderThumbOverlay, tapScrollCircleOptions } = scrollBarOptions ?? {}
|
|
916
921
|
|
|
917
|
-
const { enablePointerDrag, pointerDragInputs, enableKeyboardNavigation = true, wheelSpeedMultiplier, inertiaOptions, clipItemHeight = false, resetOnGetItemHeightChange = false } = behaviorOptions ?? {}
|
|
922
|
+
const { enablePointerDrag, pointerDragInputs, enableKeyboardNavigation = true, wheelSpeedMultiplier, inertiaOptions, overscrollBehavior, clipItemHeight = false, resetOnGetItemHeightChange = false } = behaviorOptions ?? {}
|
|
918
923
|
|
|
919
924
|
// viewportSize 未指定のときは ScrollPane が自分の帯を計測し、その値をこのコールバックで通知する。
|
|
920
925
|
// 描画枚数 (computeRenderingRanges) とアライン計算 (scrollToIndex / ドリフト補正) はこの解決済み値を使う。
|
|
@@ -2365,6 +2370,7 @@ const VirtualScrollInner = <T,>(
|
|
|
2365
2370
|
background={background}
|
|
2366
2371
|
tapScrollCircleOptions={tapScrollCircleOptions}
|
|
2367
2372
|
inertiaOptions={inertiaOptions}
|
|
2373
|
+
overscrollBehavior={overscrollBehavior}
|
|
2368
2374
|
itemCount={itemCount}
|
|
2369
2375
|
scrollBarWidth={scrollBarWidth}
|
|
2370
2376
|
enableThumbDrag={enableThumbDrag}
|