@aiquants/virtualscroll 3.11.1 → 3.11.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 +44 -0
- package/README.md +6 -4
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/TapScrollCircle.d.ts.map +1 -1
- package/dist/VirtualGrid.d.ts.map +1 -1
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1619 -1465
- package/dist/residualQuantizer.d.ts.map +1 -1
- package/dist/useGridTapScroll.d.ts.map +1 -1
- package/dist/useWheelBridge.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/ScrollBar.tsx +61 -34
- package/src/ScrollPane.tsx +16 -0
- package/src/TapScrollCircle.tsx +8 -0
- package/src/VirtualGrid.tsx +70 -0
- package/src/VirtualScroll.tsx +47 -17
- package/src/residualQuantizer.ts +14 -0
- package/src/useGridTapScroll.ts +35 -9
- package/src/useWheelBridge.ts +14 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"residualQuantizer.d.ts","sourceRoot":"","sources":["../src/residualQuantizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACnC;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACnC,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/B;;;;;OAKG;IACH,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,wBAAwB,KAAG,
|
|
1
|
+
{"version":3,"file":"residualQuantizer.d.ts","sourceRoot":"","sources":["../src/residualQuantizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACnC;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACnC,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/B;;;;;OAKG;IACH,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,wBAAwB,KAAG,iBAiD3E,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGridTapScroll.d.ts","sourceRoot":"","sources":["../src/useGridTapScroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAA4B,KAAK,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAEvG,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAG5F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAalD,4MAA4M;AAC5M,MAAM,MAAM,sBAAsB,GAAG;IACjC,uIAAuI;IACvI,OAAO,EAAE,OAAO,CAAA;IAChB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAA;IACnB,gIAAgI;IAChI,YAAY,EAAE,wBAAwB,CAAA;IACtC,6IAA6I;IAC7I,YAAY,EAAE,wBAAwB,CAAA;IACtC,sHAAsH;IACtH,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAA;IAC1D,gDAAgD;IAChD,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,mJAAmJ;IACnJ,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,oVAAoV;IACpV,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,8YAA8Y;IAC9Y,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,2HAA2H;IAC3H,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,mLAAmL;IACnL,mBAAmB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;IACzC,qKAAqK;IACrK,gBAAgB,EAAE,OAAO,CAAA;IACzB,4JAA4J;IAC5J,gBAAgB,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAAG;IACjC,4FAA4F;IAC5F,WAAW,EAAE,OAAO,CAAA;IACpB,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACpE,4HAA4H;IAC5H,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;CACpE,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,sBAAsB,KAAG,
|
|
1
|
+
{"version":3,"file":"useGridTapScroll.d.ts","sourceRoot":"","sources":["../src/useGridTapScroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAA4B,KAAK,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAEvG,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAG5F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAalD,4MAA4M;AAC5M,MAAM,MAAM,sBAAsB,GAAG;IACjC,uIAAuI;IACvI,OAAO,EAAE,OAAO,CAAA;IAChB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAA;IACnB,gIAAgI;IAChI,YAAY,EAAE,wBAAwB,CAAA;IACtC,6IAA6I;IAC7I,YAAY,EAAE,wBAAwB,CAAA;IACtC,sHAAsH;IACtH,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAA;IAC1D,gDAAgD;IAChD,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,mJAAmJ;IACnJ,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,oVAAoV;IACpV,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,8YAA8Y;IAC9Y,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,2HAA2H;IAC3H,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,mLAAmL;IACnL,mBAAmB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;IACzC,qKAAqK;IACrK,gBAAgB,EAAE,OAAO,CAAA;IACzB,4JAA4J;IAC5J,gBAAgB,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAAG;IACjC,4FAA4F;IAC5F,WAAW,EAAE,OAAO,CAAA;IACpB,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACpE,4HAA4H;IAC5H,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;CACpE,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,sBAAsB,KAAG,sBAiSjE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWheelBridge.d.ts","sourceRoot":"","sources":["../src/useWheelBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAwC,MAAM,OAAO,CAAA;AAE9F;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAA;CAC7C,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAAE,UAAU,kBAAkB,KAAG,WAAW,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"useWheelBridge.d.ts","sourceRoot":"","sources":["../src/useWheelBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAwC,MAAM,OAAO,CAAA;AAE9F;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAA;CAC7C,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAAE,UAAU,kBAAkB,KAAG,WAAW,CAAC,WAAW,CA6EjI,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/virtualscroll",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.2",
|
|
4
4
|
"description": "High-performance virtual scrolling component for React with variable item heights",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"src",
|
|
30
30
|
"!src/**/*.spec.ts",
|
|
31
31
|
"!src/**/*.spec.tsx",
|
|
32
|
+
"!src/test-helpers/**",
|
|
32
33
|
"README.md",
|
|
33
34
|
"CHANGELOG.md",
|
|
34
35
|
"LICENSE"
|
|
@@ -103,6 +104,8 @@
|
|
|
103
104
|
"check:coverage": "node scripts/ratchet-coverage.mjs",
|
|
104
105
|
"coverage:ratchet": "node scripts/ratchet-coverage.mjs --write",
|
|
105
106
|
"coverage:adopt": "node scripts/ratchet-coverage.mjs --adopt",
|
|
107
|
+
"check:docstrings": "node scripts/ratchet-docstrings.mjs",
|
|
108
|
+
"docstrings:ratchet": "node scripts/ratchet-docstrings.mjs --write",
|
|
106
109
|
"lint": "biome check src tests",
|
|
107
110
|
"lint:fix": "biome check --write src tests",
|
|
108
111
|
"format": "biome format --write src tests",
|
package/src/ScrollBar.tsx
CHANGED
|
@@ -379,7 +379,15 @@ const createOrientationConfig = (horizontal: boolean): OrientationConfig => {
|
|
|
379
379
|
mainSizeKey: "width",
|
|
380
380
|
crossSizeKey: "height",
|
|
381
381
|
thumbTranslateFunction: "translateX",
|
|
382
|
+
/**
|
|
383
|
+
* Picks the component of a pointer drag along the horizontal bar.
|
|
384
|
+
* ポインタのドラッグの、横のバーに沿う成分を選ぶ処理。
|
|
385
|
+
*/
|
|
382
386
|
selectDelta: (deltaX: number, _deltaY: number) => deltaX,
|
|
387
|
+
/**
|
|
388
|
+
* Reads a pointer's coordinate along the horizontal bar.
|
|
389
|
+
* ポインタの、横のバーに沿う座標を読む処理。
|
|
390
|
+
*/
|
|
383
391
|
getPointerCoordinate: ({ clientX }) => clientX,
|
|
384
392
|
arrowIcons: ["◀", "▶"],
|
|
385
393
|
directionClass: "aqvs-scrollbar-horizontal",
|
|
@@ -391,7 +399,15 @@ const createOrientationConfig = (horizontal: boolean): OrientationConfig => {
|
|
|
391
399
|
mainSizeKey: "height",
|
|
392
400
|
crossSizeKey: "width",
|
|
393
401
|
thumbTranslateFunction: "translateY",
|
|
402
|
+
/**
|
|
403
|
+
* Picks the component of a pointer drag along the vertical bar.
|
|
404
|
+
* ポインタのドラッグの、縦のバーに沿う成分を選ぶ処理。
|
|
405
|
+
*/
|
|
394
406
|
selectDelta: (_deltaX: number, deltaY: number) => deltaY,
|
|
407
|
+
/**
|
|
408
|
+
* Reads a pointer's coordinate along the vertical bar.
|
|
409
|
+
* ポインタの、縦のバーに沿う座標を読む処理。
|
|
410
|
+
*/
|
|
395
411
|
getPointerCoordinate: ({ clientY }) => clientY,
|
|
396
412
|
arrowIcons: ["▲", "▼"],
|
|
397
413
|
directionClass: "aqvs-scrollbar-vertical",
|
|
@@ -476,44 +492,55 @@ const useArrowAutoRepeat = ({ canUseArrowButtons, enableArrowButtons, keyboardOp
|
|
|
476
492
|
}, [clearArrowTimers])
|
|
477
493
|
|
|
478
494
|
const handleArrowPointerDown = useCallback(
|
|
479
|
-
(direction: 1 | -1) =>
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
495
|
+
(direction: 1 | -1) =>
|
|
496
|
+
/**
|
|
497
|
+
* Handles a press on an arrow: one step at once, then a repeat while the press is held, ended by the release anywhere,
|
|
498
|
+
* a cancel or the window losing focus.
|
|
499
|
+
* 矢印の押下を扱う処理。すぐに 1 段進め、押している間は繰り返し、どこでの解放・取り消し・ウィンドウのフォーカスの喪失で止める。
|
|
500
|
+
*/
|
|
501
|
+
(event: React.PointerEvent<HTMLButtonElement>) => {
|
|
502
|
+
if (!canUseArrowButtons) {
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
if (event.cancelable) {
|
|
506
|
+
event.preventDefault()
|
|
507
|
+
}
|
|
508
|
+
event.stopPropagation()
|
|
509
|
+
resetTapScroll()
|
|
510
|
+
clearArrowTimers()
|
|
511
|
+
scrollByStep(direction)
|
|
512
|
+
arrowHoldTimeoutRef.current = window.setTimeout(() => {
|
|
513
|
+
arrowHoldIntervalRef.current = window.setInterval(() => {
|
|
514
|
+
scrollByStep(direction)
|
|
515
|
+
}, ARROW_HOLD_INTERVAL)
|
|
516
|
+
}, ARROW_HOLD_DELAY)
|
|
517
|
+
// ボタンが disabled 化 / ウィンドウ blur で pointerup を取りこぼしてもリピートを確実に止める保険。
|
|
518
|
+
const release = () => clearArrowTimers()
|
|
519
|
+
globalReleaseRef.current = release
|
|
520
|
+
window.addEventListener("pointerup", release)
|
|
521
|
+
window.addEventListener("pointercancel", release)
|
|
522
|
+
window.addEventListener("blur", release)
|
|
523
|
+
},
|
|
502
524
|
[canUseArrowButtons, clearArrowTimers, resetTapScroll, scrollByStep],
|
|
503
525
|
)
|
|
504
526
|
|
|
505
527
|
const handleArrowKeyDown = useCallback(
|
|
506
|
-
(direction: 1 | -1) =>
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
528
|
+
(direction: 1 | -1) =>
|
|
529
|
+
/**
|
|
530
|
+
* Handles Enter and Space on a focused arrow as one step, unless the bar is pointer-only or its arrows are disabled.
|
|
531
|
+
* フォーカスした矢印での Enter と Space を 1 段として扱う処理 (バーがポインタ専用か矢印が無効なら何もしない)。
|
|
532
|
+
*/
|
|
533
|
+
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
534
|
+
// ポインタ専用のバーは支援技術から隠れていて、キーボードのスクロールはホストが持つ。スクリプトがフォーカスした矢印でも
|
|
535
|
+
// キーを受けると、隠れた部品がホストのキー操作と重なって一覧を動かす
|
|
536
|
+
if (!(enableArrowButtons && keyboardOperable)) {
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
540
|
+
event.preventDefault()
|
|
541
|
+
scrollByStep(direction)
|
|
542
|
+
}
|
|
543
|
+
},
|
|
517
544
|
[enableArrowButtons, keyboardOperable, scrollByStep],
|
|
518
545
|
)
|
|
519
546
|
|
package/src/ScrollPane.tsx
CHANGED
|
@@ -1064,12 +1064,28 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1064
1064
|
useImperativeHandle(
|
|
1065
1065
|
ref,
|
|
1066
1066
|
() => ({
|
|
1067
|
+
/**
|
|
1068
|
+
* Stops any inertia, then jumps to a pane position (clamped); returns the applied position.
|
|
1069
|
+
* 慣性を止めてからペインの位置へ跳ぶ (クランプする) 処理。当てた位置を返す。
|
|
1070
|
+
*/
|
|
1067
1071
|
scrollTo: (pos, dimsOverride) => {
|
|
1068
1072
|
stopInertia()
|
|
1069
1073
|
return scrollTo(pos, dimsOverride)
|
|
1070
1074
|
},
|
|
1075
|
+
/**
|
|
1076
|
+
* Reads the pane position (synchronously fresh, from the position ref).
|
|
1077
|
+
* ペインの位置を読む処理 (位置の ref から読むので同期で最新)。
|
|
1078
|
+
*/
|
|
1071
1079
|
getScrollPosition: () => scrollPositionRef.current,
|
|
1080
|
+
/**
|
|
1081
|
+
* Reads the content size the pane was given.
|
|
1082
|
+
* ペインが受け取った中身の寸法を読む処理。
|
|
1083
|
+
*/
|
|
1072
1084
|
getContentSize: () => contentSize,
|
|
1085
|
+
/**
|
|
1086
|
+
* Reads the viewport size the pane uses (given or measured).
|
|
1087
|
+
* ペインが使う表示域の寸法 (与えられたか測ったもの) を読む処理。
|
|
1088
|
+
*/
|
|
1073
1089
|
getViewportSize: () => viewportSize,
|
|
1074
1090
|
applyWheel,
|
|
1075
1091
|
}),
|
package/src/TapScrollCircle.tsx
CHANGED
|
@@ -360,9 +360,17 @@ export const TapScrollCircle = memo(
|
|
|
360
360
|
useImperativeHandle(
|
|
361
361
|
ref,
|
|
362
362
|
() => ({
|
|
363
|
+
/**
|
|
364
|
+
* Returns the circle to its resting state and reports it through `onDragChange`.
|
|
365
|
+
* サークルを静止の状態へ戻し、`onDragChange` で知らせる処理。
|
|
366
|
+
*/
|
|
363
367
|
reset: () => {
|
|
364
368
|
resetState(true)
|
|
365
369
|
},
|
|
370
|
+
/**
|
|
371
|
+
* Reads the circle's root element (null while it is not mounted).
|
|
372
|
+
* サークルのルートの要素を読む処理 (マウントしていない間は null)。
|
|
373
|
+
*/
|
|
366
374
|
getElement: () => rootRef.current,
|
|
367
375
|
}),
|
|
368
376
|
[resetState],
|
package/src/VirtualGrid.tsx
CHANGED
|
@@ -1038,6 +1038,10 @@ const VirtualGridInner = <T,>(
|
|
|
1038
1038
|
(root: HTMLDivElement) => {
|
|
1039
1039
|
rootRef.current = root
|
|
1040
1040
|
if (hasExplicitViewport) {
|
|
1041
|
+
/**
|
|
1042
|
+
* Detaches the root (the given viewport needs no observer).
|
|
1043
|
+
* ルートを外す処理 (与えられたビューポートには監視が要らない)。
|
|
1044
|
+
*/
|
|
1041
1045
|
return () => {
|
|
1042
1046
|
rootRef.current = null
|
|
1043
1047
|
}
|
|
@@ -1059,6 +1063,10 @@ const VirtualGridInner = <T,>(
|
|
|
1059
1063
|
measure()
|
|
1060
1064
|
const observer = new ResizeObserver(measure)
|
|
1061
1065
|
observer.observe(root)
|
|
1066
|
+
/**
|
|
1067
|
+
* Detaches the root and stops the observer.
|
|
1068
|
+
* ルートを外し、監視を止める処理。
|
|
1069
|
+
*/
|
|
1062
1070
|
return () => {
|
|
1063
1071
|
observer.disconnect()
|
|
1064
1072
|
rootRef.current = null
|
|
@@ -1806,6 +1814,12 @@ const VirtualGridInner = <T,>(
|
|
|
1806
1814
|
useImperativeHandle(
|
|
1807
1815
|
ref,
|
|
1808
1816
|
(): VirtualGridHandle => ({
|
|
1817
|
+
/**
|
|
1818
|
+
* Applies `y` through the embedded row list's `scrollTo` (it floors and pins a row anchor) and `x` through `applyHx`,
|
|
1819
|
+
* clearing any column anchor; returns the applied position.
|
|
1820
|
+
* `y` は埋め込みの行の一覧の `scrollTo` (切り捨てて行のアンカーを張る) で、`x` は列のアンカーを外して `applyHx` で当て、当てた位置を
|
|
1821
|
+
* 返す処理。
|
|
1822
|
+
*/
|
|
1809
1823
|
scrollTo: (pos) => {
|
|
1810
1824
|
const inner = scrollHandleRef.current
|
|
1811
1825
|
let y = vyRef.current
|
|
@@ -1822,6 +1836,11 @@ const VirtualGridInner = <T,>(
|
|
|
1822
1836
|
}
|
|
1823
1837
|
return { x, y }
|
|
1824
1838
|
},
|
|
1839
|
+
/**
|
|
1840
|
+
* Applies a non-zero `y` delta through `applyVy` and a non-zero `x` delta through `applyHx` (clearing any column anchor);
|
|
1841
|
+
* returns the applied position.
|
|
1842
|
+
* 0 でない `y` の差分は `applyVy` で、0 でない `x` の差分は列のアンカーを外して `applyHx` で当て、当てた位置を返す処理。
|
|
1843
|
+
*/
|
|
1825
1844
|
scrollBy: (delta) => {
|
|
1826
1845
|
// 縦の差分はタップの駆動と同じ継ぎ目 (applyVy) で当て、適用と鏡像の同期を 2 通りに書かない
|
|
1827
1846
|
const y = delta.y !== undefined && delta.y !== 0 ? applyVy(delta.y) : vyRef.current
|
|
@@ -1832,6 +1851,12 @@ const VirtualGridInner = <T,>(
|
|
|
1832
1851
|
}
|
|
1833
1852
|
return { x, y }
|
|
1834
1853
|
},
|
|
1854
|
+
/**
|
|
1855
|
+
* Reveals a cell: a scroll row through the embedded row list's `scrollToIndex` (translated by −R), a scroll column by
|
|
1856
|
+
* arming a column anchor and applying its position; a frozen or trailing band row or column leaves its axis as it is.
|
|
1857
|
+
* セルを見せる処理。スクロール行は −R へ訳して埋め込みの行の一覧の `scrollToIndex` で、スクロール列は列のアンカーを張ってその位置を
|
|
1858
|
+
* 当てる。先頭・末尾の帯の行や列を狙った軸はそのまま。
|
|
1859
|
+
*/
|
|
1835
1860
|
scrollToCell: (row, col, options) => {
|
|
1836
1861
|
const inner = scrollHandleRef.current
|
|
1837
1862
|
if (inner !== null && rowCount > 0) {
|
|
@@ -1856,7 +1881,18 @@ const VirtualGridInner = <T,>(
|
|
|
1856
1881
|
}
|
|
1857
1882
|
}
|
|
1858
1883
|
},
|
|
1884
|
+
/**
|
|
1885
|
+
* Reads the position: `x` from the horizontal cell, `y` through `getVy` (the embedded handle, or the last position once the
|
|
1886
|
+
* grid has unmounted).
|
|
1887
|
+
* 位置を読む処理。`x` は横の位置のセルから、`y` は `getVy` (埋め込みのハンドル。グリッドのアンマウントの後は最後の位置) から。
|
|
1888
|
+
*/
|
|
1859
1889
|
getScrollPosition: () => ({ x: hxRef.current, y: getVy() }),
|
|
1890
|
+
/**
|
|
1891
|
+
* Captures the `{ row, col, offsetX, offsetY }` anchor of the first visible cell of the scroll band, on the same boundary
|
|
1892
|
+
* rule as the row side; null when either count is 0 or there is no scroll row.
|
|
1893
|
+
* スクロール帯の最初に見えるセルの `{ row, col, offsetX, offsetY }` のアンカーを、行の側と同じ境界の規則で捕える処理。どちらかの数が 0、
|
|
1894
|
+
* またはスクロール行が無ければ null。
|
|
1895
|
+
*/
|
|
1860
1896
|
getScrollAnchor: () => {
|
|
1861
1897
|
if (rowCount === 0 || colCount === 0) {
|
|
1862
1898
|
return null
|
|
@@ -1883,11 +1919,26 @@ const VirtualGridInner = <T,>(
|
|
|
1883
1919
|
// 行はシフト空間 → フル行空間へ +R 翻訳 (行凍結設計 §3 — 復元側は −R で往復恒等)
|
|
1884
1920
|
return { row: rowAnchor.index + effectiveFrozenRows, col: found.index, offsetX: Math.max(0, bandStart - (column.cumulative - column.currentValue)), offsetY: rowAnchor.offsetPx }
|
|
1885
1921
|
},
|
|
1922
|
+
/**
|
|
1923
|
+
* Reads the cell viewport (the measured or given box without the bars).
|
|
1924
|
+
* セルのビューポート (測ったか与えられた箱からバーを除いたもの) を読む処理。
|
|
1925
|
+
*/
|
|
1886
1926
|
getViewportSize: () => ({ width: viewportRef.current.width, height: viewportRef.current.height }),
|
|
1927
|
+
/**
|
|
1928
|
+
* Reads the content size: the total column width, and the band heights plus the embedded row list's content size
|
|
1929
|
+
* (insets included; 0 once the grid has unmounted).
|
|
1930
|
+
* 中身の寸法を読む処理。列の総幅と、帯の高さに埋め込みの行の一覧の中身の高さ (インセット込み。グリッドのアンマウントの後は 0) を足したもの。
|
|
1931
|
+
*/
|
|
1887
1932
|
// 縦はインセット込みの行側 getContentSize を単一情報源にする (range.totalHeight は木総高のみ)。
|
|
1888
1933
|
// 3.5.0: height = H_F + 埋め込み + H_T
|
|
1889
1934
|
getContentSize: () => ({ width: totalWidthRef.current, height: scrollHandleRef.current === null ? 0 : frozenHeightRef.current + trailingHeightRef.current + Math.max(0, scrollHandleRef.current.getContentSize()) }),
|
|
1890
1935
|
getRange: buildRange,
|
|
1936
|
+
/**
|
|
1937
|
+
* Takes a row's new height (validated first): a leading or trailing band row re-reads its accessor through its epoch, a
|
|
1938
|
+
* scroll row goes to the embedded row list's `updateItemSize` (translated by −R).
|
|
1939
|
+
* 行の新しい高さを受け取る処理 (先に検証)。先頭・末尾の帯の行は epoch でアクセサを読み直させ、スクロール行は −R へ訳して埋め込みの
|
|
1940
|
+
* 行の一覧の `updateItemSize` へ渡す。
|
|
1941
|
+
*/
|
|
1891
1942
|
updateRowSize: (row, px) => {
|
|
1892
1943
|
validateTrackSize(px, "row", row)
|
|
1893
1944
|
// 凍結行はグリッド所有バンド — epoch 繰上げでアクセサ再読 (帯に第 2 の格納庫は
|
|
@@ -1901,12 +1952,20 @@ const VirtualGridInner = <T,>(
|
|
|
1901
1952
|
scrollHandleRef.current?.updateItemSize(Math.trunc(row) - effectiveFrozenRows, px)
|
|
1902
1953
|
}
|
|
1903
1954
|
},
|
|
1955
|
+
/**
|
|
1956
|
+
* Takes a column's new width (validated first) into the width tree and mirrors the new total width at once.
|
|
1957
|
+
* 列の新しい幅を (先に検証して) 幅木へ入れ、新しい総幅をすぐに写す処理。
|
|
1958
|
+
*/
|
|
1904
1959
|
updateColSize: (col, px) => {
|
|
1905
1960
|
validateTrackSize(px, "col", col)
|
|
1906
1961
|
// 総幅は ref へも即時反映 (同 tick の scrollBy/scrollTo が旧上限でクランプしないため)。幅が変われば木の版数が進み、
|
|
1907
1962
|
// 窓キー不変でもセル配置を失効させる (バッチループは React 自動バッチングで 1 コミット)。同じ幅は何も失効させない
|
|
1908
1963
|
commitTotalWidth(changeColTree(() => colTree.update(minmax(Math.trunc(col), 0, Math.max(0, colCount - 1)), px)))
|
|
1909
1964
|
},
|
|
1965
|
+
/**
|
|
1966
|
+
* Reads the effective frozen bands of both axes and both ends (the visible trailing sizes through `trailingVisibleSize`).
|
|
1967
|
+
* 両軸・両端の実効の凍結帯を読む処理 (見えている末尾の寸法は `trailingVisibleSize` で求める)。
|
|
1968
|
+
*/
|
|
1910
1969
|
getFrozenSize: () => ({
|
|
1911
1970
|
cols: effectiveFrozenCols,
|
|
1912
1971
|
width: frozenWidthRef.current,
|
|
@@ -1920,7 +1979,18 @@ const VirtualGridInner = <T,>(
|
|
|
1920
1979
|
trailingVisibleWidth: trailingVisibleSize(trailingWidthRef.current, frozenWidthRef.current, viewportRef.current.width),
|
|
1921
1980
|
trailingVisibleHeight: trailingVisibleSize(trailingHeightRef.current, frozenHeightRef.current, viewportRef.current.height),
|
|
1922
1981
|
}),
|
|
1982
|
+
/**
|
|
1983
|
+
* Applies one wheel event through the embedded row list (its horizontal part reaches the grid's own axis through the
|
|
1984
|
+
* row list's `onWheelHorizontal`); returns whether the event was consumed, false once the grid has unmounted.
|
|
1985
|
+
* ホイールのイベント 1 つを埋め込みの行の一覧で当てる処理 (横の成分は行の一覧の `onWheelHorizontal` を通ってグリッド自身の軸へ届く)。
|
|
1986
|
+
* 消費したかを返し、グリッドのアンマウントの後は false。
|
|
1987
|
+
*/
|
|
1923
1988
|
applyWheel: (event) => scrollHandleRef.current?.applyWheel(event) ?? false,
|
|
1989
|
+
/**
|
|
1990
|
+
* Focuses a row: a leading or trailing band row through its band DOM, a scroll row through the embedded row list's
|
|
1991
|
+
* `focusItemAtIndex` (translated by −R).
|
|
1992
|
+
* 行へフォーカスする処理。先頭・末尾の帯の行は帯の DOM へ直接、スクロール行は −R へ訳して埋め込みの行の一覧の `focusItemAtIndex` で。
|
|
1993
|
+
*/
|
|
1924
1994
|
focusRowAtIndex: (row, options) => {
|
|
1925
1995
|
// 凍結行はバンド行 DOM へ直接フォーカス、末尾帯行も帯行 DOM へ直接フォーカス
|
|
1926
1996
|
// (3.5.0 — 3 分岐。T = 0 は分岐不成立で従来挙動と恒等)、スクロール行は −R 翻訳して委譲
|
package/src/VirtualScroll.tsx
CHANGED
|
@@ -1124,10 +1124,9 @@ export const computeRenderingRangesHuge = (effectiveScrollPosition: number, view
|
|
|
1124
1124
|
if (!Number.isSafeInteger(indexNumber)) {
|
|
1125
1125
|
break
|
|
1126
1126
|
}
|
|
1127
|
+
// 後方の行は先頭の可視行より上にあり、木の値は負にならないので、その累積は先頭の可視行の上端 (有限の位置以下) を
|
|
1128
|
+
// 越えない。前方と違い、累積が溢れた行へは後方の走査が届かない
|
|
1127
1129
|
const { cumulative: runBottom } = fenwickTree.prefixSum(indexNumber, FENWICK_LOOKUP_ONLY)
|
|
1128
|
-
if (!Number.isFinite(runBottom)) {
|
|
1129
|
-
break
|
|
1130
|
-
}
|
|
1131
1130
|
const { index: jumpIndex } = fenwickTree.findIndexAtOrAfter(runBottom - 0.5, FENWICK_LOOKUP_ONLY)
|
|
1132
1131
|
const jumpBig = jumpIndex === -1 ? -1n : toSafeBigInt(jumpIndex)
|
|
1133
1132
|
if (jumpBig >= 0n && jumpBig < backwardStart) {
|
|
@@ -1244,9 +1243,10 @@ export const computeRenderingRanges = (scrollPosition: number, viewportSize: num
|
|
|
1244
1243
|
if (itemHeight === 0) {
|
|
1245
1244
|
backwardZeroRun++
|
|
1246
1245
|
if (backwardZeroRun > ZERO_HEIGHT_RUN_LIMIT) {
|
|
1247
|
-
// 連続 0 行の直前にある非 0 行へ後方ジャンプ (木上の累積位置 -0.5 で探索)
|
|
1246
|
+
// 連続 0 行の直前にある非 0 行へ後方ジャンプ (木上の累積位置 -0.5 で探索)。後方の行は先頭の可視行より上にあり、
|
|
1247
|
+
// 木の値は負にならないので、その累積は先頭の可視行の上端 (有限の位置以下) を越えない
|
|
1248
1248
|
const { cumulative: runBottom } = fenwickTree.prefixSum(backwardCursor, FENWICK_LOOKUP_ONLY)
|
|
1249
|
-
const { index: jumpIndex } =
|
|
1249
|
+
const { index: jumpIndex } = fenwickTree.findIndexAtOrAfter(runBottom - 0.5, FENWICK_LOOKUP_ONLY)
|
|
1250
1250
|
if (jumpIndex !== -1 && jumpIndex < backwardCursor) {
|
|
1251
1251
|
// 0 行の連続を飛び越えて直前の非 0 行から走査を続行する
|
|
1252
1252
|
backwardCursor = jumpIndex
|
|
@@ -1301,8 +1301,8 @@ type OnRangeChangeCallback = (range: VirtualScrollRange) => void
|
|
|
1301
1301
|
* @param throttleMs Minimum time between invocations in ms / 実行間の最小時間(ミリ秒)
|
|
1302
1302
|
* @param invoke Wrapper function to execute the callback (e.g. for argument unwrapping) / コールバックを実行するラッパー関数
|
|
1303
1303
|
*/
|
|
1304
|
-
const useThrottledInvoker = <Callback, Payload>(callbackRef: React.MutableRefObject<Callback | undefined>, throttleMs: number
|
|
1305
|
-
const throttle = Math.max(0, throttleMs
|
|
1304
|
+
const useThrottledInvoker = <Callback, Payload>(callbackRef: React.MutableRefObject<Callback | undefined>, throttleMs: number, invoke: (callback: Callback, payload: Payload) => void) => {
|
|
1305
|
+
const throttle = Math.max(0, throttleMs)
|
|
1306
1306
|
// last: 最終実行時刻, id: RAF ID, arg: 待機中の引数
|
|
1307
1307
|
const state = useRef({ last: 0, id: null as number | null, arg: null as Payload | null }).current
|
|
1308
1308
|
|
|
@@ -1329,6 +1329,12 @@ const useThrottledInvoker = <Callback, Payload>(callbackRef: React.MutableRefObj
|
|
|
1329
1329
|
[state, callbackRef, invoke],
|
|
1330
1330
|
)
|
|
1331
1331
|
|
|
1332
|
+
/**
|
|
1333
|
+
* Schedules one invocation with the latest payload: at most once per animation frame, and not before `throttleMs` has passed
|
|
1334
|
+
* since the previous one; a payload that arrives while one is pending replaces it.
|
|
1335
|
+
* 最新のペイロードでの 1 回の実行を予約する処理。アニメーションのフレームごとに多くて 1 回で、前回から `throttleMs` が経つまでは実行しない。
|
|
1336
|
+
* 予約中に届いたペイロードは前のものを置き換える。
|
|
1337
|
+
*/
|
|
1332
1338
|
return useCallback(
|
|
1333
1339
|
(payload: Payload) => {
|
|
1334
1340
|
state.arg = payload
|
|
@@ -1575,19 +1581,19 @@ const VirtualScrollInner = <T,>(
|
|
|
1575
1581
|
const safeIndexFrom = minmax(safeIndex - overscanCount * 2, 0, itemCount - 1)
|
|
1576
1582
|
const safeIndexTo = minmax(safeIndex + overscanCount * 2, 0, itemCount - 1)
|
|
1577
1583
|
const options = safeIndex > 0 || anchorOffsetPx > 0 ? { materializeOption: { materialize: true, ranges: [{ from: safeIndexFrom, to: safeIndexTo }] } } : undefined
|
|
1578
|
-
const { cumulative,
|
|
1584
|
+
const { cumulative, currentValue } = fenwickTree.prefixSum(safeIndex, options)
|
|
1579
1585
|
const logicalOffset = Math.max(cumulative - currentValue + anchorOffsetPx, 0)
|
|
1580
1586
|
position = toPanePositionWithInset(logicalOffset, resolvedInsets.top)
|
|
1581
|
-
total =
|
|
1587
|
+
total = fenwickTree.getTotal()
|
|
1582
1588
|
} else if (typeof initialScrollIndex === "number") {
|
|
1583
1589
|
const safeIndex = minmax(initialScrollIndex, 0, itemCount - 1)
|
|
1584
1590
|
const safeIndexFrom = minmax(safeIndex - overscanCount * 2, 0, itemCount - 1)
|
|
1585
1591
|
const safeIndexTo = minmax(safeIndex + overscanCount * 2, 0, itemCount - 1)
|
|
1586
1592
|
const options = initialScrollIndex > 0 ? { materializeOption: { materialize: true, ranges: [{ from: safeIndexFrom, to: safeIndexTo }] } } : undefined
|
|
1587
|
-
const { cumulative,
|
|
1593
|
+
const { cumulative, currentValue } = fenwickTree.prefixSum(initialScrollIndex, options)
|
|
1588
1594
|
const logicalOffset = Math.max(cumulative - currentValue, 0)
|
|
1589
1595
|
position = toPanePositionWithInset(logicalOffset, resolvedInsets.top)
|
|
1590
|
-
total =
|
|
1596
|
+
total = fenwickTree.getTotal()
|
|
1591
1597
|
} else if (typeof initialScrollOffset === "number") {
|
|
1592
1598
|
position = toPanePositionWithInset(Math.max(initialScrollOffset, 0), resolvedInsets.top)
|
|
1593
1599
|
total = fenwickTree.getTotal()
|
|
@@ -3031,13 +3037,12 @@ const VirtualScrollInner = <T,>(
|
|
|
3031
3037
|
}
|
|
3032
3038
|
|
|
3033
3039
|
// 照合した行の上端は描画の中で補ってあるが、木から導いた値 (getRange の総高さなど) は古い木のまま。総和が変わらない
|
|
3034
|
-
//
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
return
|
|
3038
|
-
}
|
|
3040
|
+
// 変化でも、次の確定で木から導き直させる。ここから先は同期で、上の確認の後にアンマウントは挟まらない
|
|
3041
|
+
changeTree(() => fenwickTree.updates(toUpdateHeights))
|
|
3042
|
+
const total = fenwickTree.getTotal()
|
|
3039
3043
|
writeContentSize(total)
|
|
3040
3044
|
Logger.debug("[VirtualScroll] Updated heights for items", toUpdateHeights, "New total height:", total)
|
|
3045
|
+
// 並行の描画では、初回の描画のマイクロタスクがその確定 (ペインの取り付け) より先に走ることがある
|
|
3041
3046
|
const panePosition = scrollPaneRef.current?.getScrollPosition() ?? latestScrollPositionRef.current
|
|
3042
3047
|
|
|
3043
3048
|
if (shiftAmount !== 0) {
|
|
@@ -3101,7 +3106,7 @@ const VirtualScrollInner = <T,>(
|
|
|
3101
3106
|
*/
|
|
3102
3107
|
const renderVisibleItems = useCallback(
|
|
3103
3108
|
(currentScrollPosition: number) => {
|
|
3104
|
-
const shouldUseThrottledPosition =
|
|
3109
|
+
const shouldUseThrottledPosition = callbackThrottleMs > 0
|
|
3105
3110
|
const diff = Math.abs(currentScrollPosition - scrollPosition)
|
|
3106
3111
|
const rawEffectiveScrollPosition = shouldUseThrottledPosition && diff > 0.5 ? scrollPosition : currentScrollPosition
|
|
3107
3112
|
const effectiveScrollPosition = toLogicalPositionWithInset(rawEffectiveScrollPosition, resolvedInsets.top)
|
|
@@ -3235,21 +3240,46 @@ const VirtualScrollInner = <T,>(
|
|
|
3235
3240
|
useImperativeHandle(
|
|
3236
3241
|
ref,
|
|
3237
3242
|
() => ({
|
|
3243
|
+
/**
|
|
3244
|
+
* Reads the logical scroll position (the pane's position less the top inset), or the `-1` sentinel while the pane is
|
|
3245
|
+
* unconnected.
|
|
3246
|
+
* 論理スクロール位置 (ペインの位置から上のインセットを引いたもの) を読む処理。ペインが未接続の間は番兵の `-1`。
|
|
3247
|
+
*/
|
|
3238
3248
|
getScrollPosition: () => {
|
|
3239
3249
|
// ❗ 論理座標で返す (2.0.0)。ペイン座標を返す実装へ戻さないこと: 消費者の換算コードが
|
|
3240
3250
|
// 座標混在バグの温床になる (この統一が本質修正)。inset は走行中も最新を ref から読む
|
|
3241
3251
|
const panePosition = scrollPaneRef.current?.getScrollPosition()
|
|
3242
3252
|
return typeof panePosition === "number" ? toLogicalPositionWithInset(panePosition, resolvedInsetsTopRef.current) : -1
|
|
3243
3253
|
},
|
|
3254
|
+
/**
|
|
3255
|
+
* Reads the pane's content size (insets included), or the `-1` sentinel while the pane is unconnected.
|
|
3256
|
+
* ペインの中身の寸法 (インセット込み) を読む処理。ペインが未接続の間は番兵の `-1`。
|
|
3257
|
+
*/
|
|
3244
3258
|
getContentSize: () => scrollPaneRef.current?.getContentSize() ?? -1,
|
|
3259
|
+
/**
|
|
3260
|
+
* Reads the pane's viewport size, or the `-1` sentinel while the pane is unconnected.
|
|
3261
|
+
* ペインの表示域の寸法を読む処理。ペインが未接続の間は番兵の `-1`。
|
|
3262
|
+
*/
|
|
3245
3263
|
getViewportSize: () => scrollPaneRef.current?.getViewportSize() ?? -1,
|
|
3246
3264
|
scrollTo: scrollToHandle,
|
|
3247
3265
|
scrollBy,
|
|
3248
3266
|
applyWheel,
|
|
3249
3267
|
scrollToIndex,
|
|
3268
|
+
/**
|
|
3269
|
+
* Reads the total of the row-height tree (insets excluded).
|
|
3270
|
+
* 行の高さの木の合計 (インセットを除く) を読む処理。
|
|
3271
|
+
*/
|
|
3250
3272
|
getFenwickTreeTotalHeight: () => fenwickTree.getTotal(),
|
|
3273
|
+
/**
|
|
3274
|
+
* Reads the number of rows the row-height tree holds.
|
|
3275
|
+
* 行の高さの木が持つ行の数を読む処理。
|
|
3276
|
+
*/
|
|
3251
3277
|
getFenwickSize: () => fenwickTree.getSize(),
|
|
3252
3278
|
focusItemAtIndex,
|
|
3279
|
+
/**
|
|
3280
|
+
* Reads the range the last commit published (one render behind).
|
|
3281
|
+
* 最後の確定が公開した範囲 (描画 1 回ぶん遅れる) を読む処理。
|
|
3282
|
+
*/
|
|
3253
3283
|
getRange: () => currentRangeRef.current,
|
|
3254
3284
|
getScrollAnchor,
|
|
3255
3285
|
updateItemSize,
|
package/src/residualQuantizer.ts
CHANGED
|
@@ -105,6 +105,12 @@ export const createResidualQuantizer = (options: ResidualQuantizerOptions): Resi
|
|
|
105
105
|
let residue = 0
|
|
106
106
|
|
|
107
107
|
return {
|
|
108
|
+
/**
|
|
109
|
+
* Adds a delta to the residue and emits the whole quanta it holds (a non-finite delta, or one beyond
|
|
110
|
+
* `Number.MAX_SAFE_INTEGER`, emits 0 and leaves the residue as it is).
|
|
111
|
+
* 差分を残差へ足し、残差が持つ量子の整数倍を放出する処理 (非有限の差分と `Number.MAX_SAFE_INTEGER` を越える差分は 0 を放出し、
|
|
112
|
+
* 残差を変えない)。
|
|
113
|
+
*/
|
|
108
114
|
push: (delta: number): number => {
|
|
109
115
|
// 非有限デルタは残差を汚さず 0 を返す (蓄積器に NaN が入ると以後全放出が死ぬ)。
|
|
110
116
|
// ❗ |delta| > Number.MAX_SAFE_INTEGER も同じ契約で見送る: 2^53 超では
|
|
@@ -125,7 +131,15 @@ export const createResidualQuantizer = (options: ResidualQuantizerOptions): Resi
|
|
|
125
131
|
residue -= emitted
|
|
126
132
|
return emitted
|
|
127
133
|
},
|
|
134
|
+
/**
|
|
135
|
+
* Reads the residue not yet emitted.
|
|
136
|
+
* まだ放出していない残差を読む処理。
|
|
137
|
+
*/
|
|
128
138
|
peekResidue: () => residue,
|
|
139
|
+
/**
|
|
140
|
+
* Drops the residue.
|
|
141
|
+
* 残差を捨てる処理。
|
|
142
|
+
*/
|
|
129
143
|
reset: () => {
|
|
130
144
|
residue = 0
|
|
131
145
|
},
|
package/src/useGridTapScroll.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* immobile 停止則・延長成長の再武装・fail-closed のキャンセル 3 系。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useCallback, useEffect, useRef, useState } from "react"
|
|
11
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"
|
|
12
12
|
import { computeTapScrollVelocity, type TapScrollAxisSpeedParams } from "./computeTapScrollVelocity.ts"
|
|
13
13
|
import { TAP_SCROLL_CANCEL_EVENT, TAP_SCROLL_MAX_FRAME_DELTA_SECONDS } from "./ScrollBar.tsx"
|
|
14
14
|
import type { TapScrollCircleDragState, TapScrollCircleHandle } from "./TapScrollCircle.tsx"
|
|
@@ -113,6 +113,8 @@ export const useGridTapScroll = (params: UseGridTapScrollParams): UseGridTapScro
|
|
|
113
113
|
const tapCircleHandleRef = useRef<TapScrollCircleHandle | null>(null)
|
|
114
114
|
const [isTapActive, setIsTapActive] = useState(false)
|
|
115
115
|
const frameRef = useRef<number | null>(null)
|
|
116
|
+
// 生きているのは、フックの部品を置いた確定から取り除く確定まで (レイアウトの副作用の本体と後始末が書く)
|
|
117
|
+
const isLiveRef = useRef(false)
|
|
116
118
|
const lastTimestampRef = useRef<number | null>(null)
|
|
117
119
|
const xDriveRef = useRef<AxisDriveState>({ residual: 0, direction: 0 })
|
|
118
120
|
const yDriveRef = useRef<AxisDriveState>({ residual: 0, direction: 0 })
|
|
@@ -181,14 +183,20 @@ export const useGridTapScroll = (params: UseGridTapScrollParams): UseGridTapScro
|
|
|
181
183
|
}, [])
|
|
182
184
|
|
|
183
185
|
/**
|
|
184
|
-
* One integration frame of the two-axis loop.
|
|
185
|
-
*
|
|
186
|
+
* One integration frame of the two-axis loop. A frame that runs after the commit that unmounts the hook's component (one
|
|
187
|
+
* the browser had already scheduled, or one a drag report started after that commit) moves nothing and ends the loop.
|
|
188
|
+
* 2 軸ループの 1 積分フレーム。フックの部品をアンマウントする確定の後に走るフレーム (ブラウザが予約済みだったもの、またはその確定の
|
|
189
|
+
* 後のドラッグの知らせが始めたもの) は何も動かさず、ループを終える。
|
|
186
190
|
*/
|
|
187
191
|
const step = useCallback(
|
|
188
192
|
(timestamp: number) => {
|
|
189
193
|
// この rAF ループ意味論は ScrollBar.tsx の tap ループ (stepAutoScroll) と双子 — 片方を直したらもう片方も直すこと
|
|
190
194
|
const current = paramsRef.current
|
|
191
195
|
const state = tapDragStateRef.current
|
|
196
|
+
if (!isLiveRef.current) {
|
|
197
|
+
stopLoop()
|
|
198
|
+
return
|
|
199
|
+
}
|
|
192
200
|
if (!state.active || state.direction === 0) {
|
|
193
201
|
stopLoop()
|
|
194
202
|
return
|
|
@@ -348,20 +356,38 @@ export const useGridTapScroll = (params: UseGridTapScrollParams): UseGridTapScro
|
|
|
348
356
|
}, [enabled, resetTapScroll])
|
|
349
357
|
|
|
350
358
|
/**
|
|
351
|
-
* Cancel path (c): `enabled` → false resets
|
|
352
|
-
* キャンセル系 (c)
|
|
359
|
+
* Cancel path (c), first half: `enabled` → false resets.
|
|
360
|
+
* キャンセル系 (c) の前半: `enabled` → false でリセット。
|
|
353
361
|
*
|
|
354
|
-
* 目的:
|
|
355
|
-
* 依存関係: [enabled, resetTapScroll]
|
|
356
|
-
* クリーンアップ:
|
|
362
|
+
* 目的: 無効化時に rAF を残さない。
|
|
363
|
+
* 依存関係: [enabled, resetTapScroll]
|
|
364
|
+
* クリーンアップ: 不要 (アンマウントの停止は後半が担う)。
|
|
357
365
|
*/
|
|
358
366
|
useEffect(() => {
|
|
359
367
|
if (!enabled) {
|
|
360
368
|
resetTapScroll()
|
|
361
369
|
}
|
|
362
370
|
}, [enabled, resetTapScroll])
|
|
363
|
-
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Cancel path (c), second half: the liveness of the hook's component, and the stop at unmount. The loop stops in the commit
|
|
374
|
+
* that unmounts the component (a layout-effect cleanup), not in the passive cleanups after it: when the unmount is not
|
|
375
|
+
* synchronous, the browser can run a scheduled frame between that commit and the passive cleanups, and that frame would
|
|
376
|
+
* move the grid and call the host's `onScroll` after the grid is gone. A frame that still runs after the commit finds the
|
|
377
|
+
* hook not live (`step`).
|
|
378
|
+
* キャンセル系 (c) の後半: フックの部品が生きているかと、アンマウントでの停止。ループはその後の受け身の後始末ではなく、部品を
|
|
379
|
+
* アンマウントする確定 (レイアウトの副作用の後始末) で止める。アンマウントが同期でないとき、ブラウザはその確定と受け身の後始末の間に
|
|
380
|
+
* 予約済みのフレームを走らせられ、そのフレームはグリッドが消えた後にグリッドを動かしてホストの `onScroll` を呼んでしまう。確定の後に
|
|
381
|
+
* なお走るフレームは、フックが生きていないことを見て何もしない (`step`)。
|
|
382
|
+
*
|
|
383
|
+
* 目的: アンマウントの確定の後に位置を動かさない。
|
|
384
|
+
* 依存関係: [stopLoop] (同一性は不変なので、マウントで 1 回・アンマウントで 1 回)
|
|
385
|
+
* クリーンアップ: 生きていない印とループの停止。
|
|
386
|
+
*/
|
|
387
|
+
useLayoutEffect(() => {
|
|
388
|
+
isLiveRef.current = true
|
|
364
389
|
return () => {
|
|
390
|
+
isLiveRef.current = false
|
|
365
391
|
stopLoop()
|
|
366
392
|
}
|
|
367
393
|
}, [stopLoop])
|