@aiquants/virtualscroll 3.1.0 → 3.1.1
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 +18 -0
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1243 -1245
- package/dist/utils.d.cts +15 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ScrollBar.tsx +3 -30
- package/src/ScrollPane.tsx +16 -3
- package/src/utils.ts +42 -0
package/dist/utils.d.cts
CHANGED
|
@@ -12,3 +12,18 @@
|
|
|
12
12
|
* @returns The clamped value. / クランプされた値。
|
|
13
13
|
*/
|
|
14
14
|
export declare const minmax: (value: number, min: number, max: number) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Computes an element's CSS transform scale along one axis (visual px / layout px).
|
|
17
|
+
*
|
|
18
|
+
* 要素の指定軸方向 CSS transform スケール (視覚px / レイアウトpx) を算出。
|
|
19
|
+
* 祖先に transform: scale(...) があると client 座標 (視覚px) がレイアウトpx とズレるため、
|
|
20
|
+
* その比率でドラッグ量・クリック座標を補正する。分母には整数丸めされる offsetWidth/offsetHeight
|
|
21
|
+
* ではなく、transform の影響を受けず小数精度を持つ getComputedStyle のレイアウト寸法
|
|
22
|
+
* (content + padding + border) を優先して使い、transform なしなら厳密に 1 を返す。
|
|
23
|
+
* 取得不能時 (jsdom 等) は offset 寸法へフォールバックし、それも 0 なら 1 を返す。
|
|
24
|
+
*
|
|
25
|
+
* @param element The element to measure. / 計測対象要素。
|
|
26
|
+
* @param axis The axis to measure along ("x" = width, "y" = height). / 計測軸。
|
|
27
|
+
* @returns The visual/layout scale ratio (1 when unmeasurable). / 視覚/レイアウト比 (計測不能時 1)。
|
|
28
|
+
*/
|
|
29
|
+
export declare const getAxisScale: (element: HTMLElement, axis: "x" | "y") => number;
|
package/dist/utils.d.ts
CHANGED
|
@@ -12,4 +12,19 @@
|
|
|
12
12
|
* @returns The clamped value. / クランプされた値。
|
|
13
13
|
*/
|
|
14
14
|
export declare const minmax: (value: number, min: number, max: number) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Computes an element's CSS transform scale along one axis (visual px / layout px).
|
|
17
|
+
*
|
|
18
|
+
* 要素の指定軸方向 CSS transform スケール (視覚px / レイアウトpx) を算出。
|
|
19
|
+
* 祖先に transform: scale(...) があると client 座標 (視覚px) がレイアウトpx とズレるため、
|
|
20
|
+
* その比率でドラッグ量・クリック座標を補正する。分母には整数丸めされる offsetWidth/offsetHeight
|
|
21
|
+
* ではなく、transform の影響を受けず小数精度を持つ getComputedStyle のレイアウト寸法
|
|
22
|
+
* (content + padding + border) を優先して使い、transform なしなら厳密に 1 を返す。
|
|
23
|
+
* 取得不能時 (jsdom 等) は offset 寸法へフォールバックし、それも 0 なら 1 を返す。
|
|
24
|
+
*
|
|
25
|
+
* @param element The element to measure. / 計測対象要素。
|
|
26
|
+
* @param axis The axis to measure along ("x" = width, "y" = height). / 計測軸。
|
|
27
|
+
* @returns The visual/layout scale ratio (1 when unmeasurable). / 視覚/レイアウト比 (計測不能時 1)。
|
|
28
|
+
*/
|
|
29
|
+
export declare const getAxisScale: (element: HTMLElement, axis: "x" | "y") => number;
|
|
15
30
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,MAEhE,CAAA"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,MAEhE,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,WAAW,EAAE,MAAM,GAAG,GAAG,GAAG,KAAG,MA0BpE,CAAA"}
|
package/package.json
CHANGED
package/src/ScrollBar.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
|
|
|
8
8
|
import { twMerge } from "tailwind-merge"
|
|
9
9
|
import { Logger } from "./logger.ts"
|
|
10
10
|
import { TapScrollCircle, type TapScrollCircleDragState, type TapScrollCircleHandle, type TapScrollCircleRenderProps } from "./TapScrollCircle.tsx"
|
|
11
|
-
import { minmax } from "./utils.ts"
|
|
11
|
+
import { getAxisScale, minmax } from "./utils.ts"
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Configuration for scrollbar orientation (vertical or horizontal).
|
|
@@ -1055,35 +1055,8 @@ export const ScrollBar = ({
|
|
|
1055
1055
|
* (content + padding + border) を優先して使い、transform なしなら厳密に 1 を返す。
|
|
1056
1056
|
* 取得不能時 (jsdom 等) は offset 寸法へフォールバックし、それも 0 なら 1 を返す。
|
|
1057
1057
|
*/
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const rect = element.getBoundingClientRect()
|
|
1061
|
-
const visualSize = horizontal ? rect.width : rect.height
|
|
1062
|
-
// computed style の値 (px 文字列) を数値化する。解釈不能なら 0 とみなす。
|
|
1063
|
-
const parseSize = (value: string) => {
|
|
1064
|
-
const parsed = Number.parseFloat(value)
|
|
1065
|
-
return Number.isFinite(parsed) ? parsed : 0
|
|
1066
|
-
}
|
|
1067
|
-
const computed = window.getComputedStyle(element)
|
|
1068
|
-
const contentSize = parseSize(horizontal ? computed.width : computed.height)
|
|
1069
|
-
// rect (border-box) と同系になるよう content + padding + border を合成する。
|
|
1070
|
-
const computedLayoutSize =
|
|
1071
|
-
contentSize > 0
|
|
1072
|
-
? contentSize +
|
|
1073
|
-
parseSize(horizontal ? computed.paddingLeft : computed.paddingTop) +
|
|
1074
|
-
parseSize(horizontal ? computed.paddingRight : computed.paddingBottom) +
|
|
1075
|
-
parseSize(horizontal ? computed.borderLeftWidth : computed.borderTopWidth) +
|
|
1076
|
-
parseSize(horizontal ? computed.borderRightWidth : computed.borderBottomWidth)
|
|
1077
|
-
: 0
|
|
1078
|
-
// computed が取れない環境 (jsdom 等) では従来の offset 寸法へフォールバックする。
|
|
1079
|
-
const layoutSize = computedLayoutSize > 0 ? computedLayoutSize : horizontal ? element.offsetWidth : element.offsetHeight
|
|
1080
|
-
if (layoutSize <= 0 || visualSize <= 0) {
|
|
1081
|
-
return 1
|
|
1082
|
-
}
|
|
1083
|
-
return visualSize / layoutSize
|
|
1084
|
-
},
|
|
1085
|
-
[horizontal],
|
|
1086
|
-
)
|
|
1058
|
+
// 本体は共有ヘルパ getAxisScale (utils.ts — ScrollPane のポインタパンと同一 SSOT)
|
|
1059
|
+
const getMainAxisScale = useCallback((element: HTMLElement) => getAxisScale(element, horizontal ? "x" : "y"), [horizontal])
|
|
1087
1060
|
|
|
1088
1061
|
/**
|
|
1089
1062
|
* Pointer move handler for thumb dragging.
|
package/src/ScrollPane.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayo
|
|
|
2
2
|
import { twMerge } from "tailwind-merge"
|
|
3
3
|
import { Logger } from "./logger.ts"
|
|
4
4
|
import { ScrollBar, type ScrollBarTapCircleOptions, type ScrollBarThumbOverlayRenderProps, TAP_SCROLL_CANCEL_EVENT } from "./ScrollBar.tsx"
|
|
5
|
-
import { minmax } from "./utils.ts"
|
|
5
|
+
import { getAxisScale, minmax } from "./utils.ts"
|
|
6
6
|
import { resolveWheelAxes } from "./wheelAxes.ts"
|
|
7
7
|
import { markWheelConsumed, wasWheelConsumed } from "./wheelConsumption.ts"
|
|
8
8
|
|
|
@@ -231,6 +231,8 @@ type DragState = {
|
|
|
231
231
|
shouldCancelNextClick: boolean
|
|
232
232
|
clickResetTimer: number | null
|
|
233
233
|
velocitySamples: DragSample[]
|
|
234
|
+
/** press 時に採取した縦軸の視覚/レイアウト比 (祖先 transform scale — 補正の分母)。 */
|
|
235
|
+
scale: number
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
/**
|
|
@@ -989,6 +991,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
989
991
|
shouldCancelNextClick: false,
|
|
990
992
|
clickResetTimer: null,
|
|
991
993
|
velocitySamples: [],
|
|
994
|
+
scale: 1,
|
|
992
995
|
})
|
|
993
996
|
|
|
994
997
|
const enablePointerDragRef = useRef(enablePointerDrag)
|
|
@@ -1084,6 +1087,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1084
1087
|
state.isDragging = false
|
|
1085
1088
|
state.shouldCancelNextClick = shouldKeepClickSuppression
|
|
1086
1089
|
state.velocitySamples = []
|
|
1090
|
+
state.scale = 1
|
|
1087
1091
|
// 状態を確定させてから解放する (同期 lostpointercapture に引き継ぎを壊させない)
|
|
1088
1092
|
if (element?.hasPointerCapture(releasedPointerId)) {
|
|
1089
1093
|
element.releasePointerCapture(releasedPointerId)
|
|
@@ -1107,6 +1111,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1107
1111
|
state.startScroll = 0
|
|
1108
1112
|
state.isDragging = false
|
|
1109
1113
|
state.velocitySamples = []
|
|
1114
|
+
state.scale = 1
|
|
1110
1115
|
}
|
|
1111
1116
|
|
|
1112
1117
|
const clearClickTimer = () => {
|
|
@@ -1239,7 +1244,9 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1239
1244
|
|
|
1240
1245
|
pushVelocitySample(event.clientY)
|
|
1241
1246
|
|
|
1242
|
-
|
|
1247
|
+
// ÷scale: 視覚 px の指移動をレイアウト (論理) px へ (発動閾値 6px は指の物理
|
|
1248
|
+
// 移動量の判別なので視覚 px のまま — 意図的非対称)
|
|
1249
|
+
const deltaY = (event.clientY - state.startClientY) / (state.scale || 1)
|
|
1243
1250
|
const nextPosition = state.startScroll - deltaY
|
|
1244
1251
|
scrollToRef.current(nextPosition)
|
|
1245
1252
|
|
|
@@ -1309,7 +1316,8 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1309
1316
|
if (lastSample && firstSample && lastSample.time !== firstSample.time) {
|
|
1310
1317
|
const deltaClientY = lastSample.clientY - firstSample.clientY
|
|
1311
1318
|
const deltaTime = lastSample.time - firstSample.time
|
|
1312
|
-
|
|
1319
|
+
// ÷scale: フリック速度も視覚 px/ms のままだと慣性走行距離が z 倍に歪む
|
|
1320
|
+
inertiaVelocity = -(deltaClientY / deltaTime) / (state.scale || 1)
|
|
1313
1321
|
}
|
|
1314
1322
|
}
|
|
1315
1323
|
|
|
@@ -1405,6 +1413,11 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1405
1413
|
state.pointerType = event.pointerType
|
|
1406
1414
|
state.startClientY = event.clientY
|
|
1407
1415
|
state.startScroll = scrollPositionRef.current
|
|
1416
|
+
// 祖先 transform scale の押下時採取 (ScrollBar サム / トラックと同じ press キャッシュ
|
|
1417
|
+
// 規約): 視覚 px の指移動をレイアウト px へ換算しないと scale z 下でコンテンツが
|
|
1418
|
+
// 指速の z 倍で動く。move 毎の gBCR + getComputedStyle は 60-120Hz 熱経路の
|
|
1419
|
+
// 強制レイアウトになるため押下時 1 回に固定する
|
|
1420
|
+
state.scale = getAxisScale(element, "y")
|
|
1408
1421
|
state.isDragging = false
|
|
1409
1422
|
state.shouldCancelNextClick = false
|
|
1410
1423
|
state.velocitySamples = []
|
package/src/utils.ts
CHANGED
|
@@ -14,3 +14,45 @@
|
|
|
14
14
|
export const minmax = (value: number, min: number, max: number): number => {
|
|
15
15
|
return Math.max(min, Math.min(max, value))
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Computes an element's CSS transform scale along one axis (visual px / layout px).
|
|
20
|
+
*
|
|
21
|
+
* 要素の指定軸方向 CSS transform スケール (視覚px / レイアウトpx) を算出。
|
|
22
|
+
* 祖先に transform: scale(...) があると client 座標 (視覚px) がレイアウトpx とズレるため、
|
|
23
|
+
* その比率でドラッグ量・クリック座標を補正する。分母には整数丸めされる offsetWidth/offsetHeight
|
|
24
|
+
* ではなく、transform の影響を受けず小数精度を持つ getComputedStyle のレイアウト寸法
|
|
25
|
+
* (content + padding + border) を優先して使い、transform なしなら厳密に 1 を返す。
|
|
26
|
+
* 取得不能時 (jsdom 等) は offset 寸法へフォールバックし、それも 0 なら 1 を返す。
|
|
27
|
+
*
|
|
28
|
+
* @param element The element to measure. / 計測対象要素。
|
|
29
|
+
* @param axis The axis to measure along ("x" = width, "y" = height). / 計測軸。
|
|
30
|
+
* @returns The visual/layout scale ratio (1 when unmeasurable). / 視覚/レイアウト比 (計測不能時 1)。
|
|
31
|
+
*/
|
|
32
|
+
export const getAxisScale = (element: HTMLElement, axis: "x" | "y"): number => {
|
|
33
|
+
const isX = axis === "x"
|
|
34
|
+
const rect = element.getBoundingClientRect()
|
|
35
|
+
const visualSize = isX ? rect.width : rect.height
|
|
36
|
+
// computed style の値 (px 文字列) を数値化する。解釈不能なら 0 とみなす。
|
|
37
|
+
const parseSize = (value: string) => {
|
|
38
|
+
const parsed = Number.parseFloat(value)
|
|
39
|
+
return Number.isFinite(parsed) ? parsed : 0
|
|
40
|
+
}
|
|
41
|
+
const computed = window.getComputedStyle(element)
|
|
42
|
+
const contentSize = parseSize(isX ? computed.width : computed.height)
|
|
43
|
+
// rect (border-box) と同系になるよう content + padding + border を合成する。
|
|
44
|
+
const computedLayoutSize =
|
|
45
|
+
contentSize > 0
|
|
46
|
+
? contentSize +
|
|
47
|
+
parseSize(isX ? computed.paddingLeft : computed.paddingTop) +
|
|
48
|
+
parseSize(isX ? computed.paddingRight : computed.paddingBottom) +
|
|
49
|
+
parseSize(isX ? computed.borderLeftWidth : computed.borderTopWidth) +
|
|
50
|
+
parseSize(isX ? computed.borderRightWidth : computed.borderBottomWidth)
|
|
51
|
+
: 0
|
|
52
|
+
// computed が取れない環境 (jsdom 等) では従来の offset 寸法へフォールバックする。
|
|
53
|
+
const layoutSize = computedLayoutSize > 0 ? computedLayoutSize : isX ? element.offsetWidth : element.offsetHeight
|
|
54
|
+
if (layoutSize <= 0 || visualSize <= 0) {
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
return visualSize / layoutSize
|
|
58
|
+
}
|