@aiquants/virtualscroll 3.11.0 → 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 +87 -0
- package/README.md +12 -7
- 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.cts +18 -10
- package/dist/VirtualGrid.d.ts +18 -10
- 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 +2408 -2251
- package/dist/residualQuantizer.d.ts.map +1 -1
- package/dist/useFenwickMapTree.d.cts +48 -0
- package/dist/useFenwickMapTree.d.ts +48 -0
- package/dist/useFenwickMapTree.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 +96 -37
- package/src/ScrollPane.tsx +74 -15
- package/src/TapScrollCircle.tsx +8 -0
- package/src/VirtualGrid.tsx +191 -94
- package/src/VirtualScroll.tsx +65 -73
- package/src/residualQuantizer.ts +14 -0
- package/src/useFenwickMapTree.ts +82 -1
- package/src/useGridTapScroll.ts +35 -9
- package/src/useLruCache.ts +4 -4
- package/src/useWheelBridge.ts +14 -0
package/src/VirtualGrid.tsx
CHANGED
|
@@ -41,7 +41,7 @@ import { Logger } from "./logger.ts"
|
|
|
41
41
|
import { resolveTapScrollCircleOptions, ScrollBar, type ScrollBarTapCircleOptions } from "./ScrollBar.tsx"
|
|
42
42
|
import type { ScrollPaneProps } from "./ScrollPane.tsx"
|
|
43
43
|
import { TapScrollCircle } from "./TapScrollCircle.tsx"
|
|
44
|
-
import { useFenwickMapTree } from "./useFenwickMapTree.ts"
|
|
44
|
+
import { FENWICK_LOOKUP_ONLY, useFenwickMapTree, useFenwickTreeRevision } from "./useFenwickMapTree.ts"
|
|
45
45
|
import { GRID_TAP_CIRCLE_DEFAULT_OFFSET, useGridTapScroll } from "./useGridTapScroll.ts"
|
|
46
46
|
import { minmax } from "./utils.ts"
|
|
47
47
|
import { ANCHOR_REBASE_DISTANCE, computeRenderingRanges, MAX_RENDERED_ITEMS, VirtualScroll, type VirtualScrollBehaviorOptions, type VirtualScrollHandle, type VirtualScrollRange, type VirtualScrollScrollBarOptions, ZERO_HEIGHT_RUN_LIMIT } from "./VirtualScroll.tsx"
|
|
@@ -144,7 +144,7 @@ export const MAX_FROZEN_TRAILING_ROWS = 128
|
|
|
144
144
|
* Maximum number of CONSECUTIVE NON-CONVERGENT self-heal flushes. A stable accessor converges in
|
|
145
145
|
* ONE flush (tree == accessor afterwards), so the next collect pass is clean and RESETS this
|
|
146
146
|
* counter — sequential legitimate width changes never accumulate. A truly oscillating getColWidth
|
|
147
|
-
* (a contract violation) never produces a clean pass, so the heal→
|
|
147
|
+
* (a contract violation) never produces a clean pass, so the heal→revision→re-collect microtask
|
|
148
148
|
* cycle would spin forever — past this limit healing is suspended with a single warning (bounded
|
|
149
149
|
* degradation instead of a livelock). ❗ The state is deliberately window-agnostic ({count,
|
|
150
150
|
* warned} — no window key): a key-scoped reset would be evaded forever by an oscillation whose
|
|
@@ -155,7 +155,7 @@ export const MAX_FROZEN_TRAILING_ROWS = 128
|
|
|
155
155
|
* 「**連続する非収束**」self-heal フラッシュ数の上限。安定したアクセサは 1 回で収束する
|
|
156
156
|
* (治癒後は 木 == アクセサ) ため次の走査は乖離ゼロとなり本カウンタは**解消**される — 逐次の
|
|
157
157
|
* 正当な幅変更は蓄積しない。真に振動する getColWidth (契約違反) は収束パスを一度も作れず、
|
|
158
|
-
*
|
|
158
|
+
* 治癒→版数→再走査のマイクロタスク循環が無限旋回するため、超過時は治癒を停止し 1 回だけ
|
|
159
159
|
* 警告する (livelock でなく有界な縮退)。❗ 状態は意図的に窓非依存 ({count, warned} — 窓キーを
|
|
160
160
|
* 持たない): キー基準の解消は、振幅が窓端を動かす振動が治癒のたびにキーを交互させて永久回避する
|
|
161
161
|
* (実測反証済み)。停止後の回復は収束パス解消が担う (**どの窓でも**収束 1 回で治癒が再武装する)。
|
|
@@ -437,7 +437,13 @@ export type VirtualGridProps<T> = {
|
|
|
437
437
|
testId?: string
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
/**
|
|
440
|
+
/**
|
|
441
|
+
* Imperative handle (logical coordinates everywhere — row-parity incl. return values). React attaches it after the embedded
|
|
442
|
+
* row list, so it never reads as unconnected; a handle the host keeps after the grid unmounts reports the last position and
|
|
443
|
+
* never throws.
|
|
444
|
+
* 命令ハンドル (全て論理座標 — 返り値契約まで行側と対称)。React は埋め込みの行の一覧の後にこれを取り付けるので、未接続の値を返す
|
|
445
|
+
* ことはない。グリッドのアンマウントの後もホストが持ち続けたハンドルは最後の位置を返し、投げない。
|
|
446
|
+
*/
|
|
441
447
|
export type VirtualGridHandle = {
|
|
442
448
|
/**
|
|
443
449
|
* Jump API — do NOT use for continuous input (use scrollBy). Vertical delegates to the row
|
|
@@ -470,7 +476,7 @@ export type VirtualGridHandle = {
|
|
|
470
476
|
* 隠れた末尾セルはどのスクロール位置でも可視化できない — no-op はそこでも正確。
|
|
471
477
|
*/
|
|
472
478
|
scrollToCell(row: number, col: number, options?: { alignY?: "top" | "bottom" | "center"; alignX?: "start" | "end" | "center"; offsetX?: number; offsetY?: number }): void
|
|
473
|
-
/** Synchronous-fresh position read (internal refs — safe mid-event;
|
|
479
|
+
/** Synchronous-fresh position read (internal refs — safe mid-event; after the grid unmounts, the last position). / 同期・最新の位置読み (内部 ref — イベント中も安全。グリッドのアンマウントの後は最後の位置)。 */
|
|
474
480
|
getScrollPosition(): { x: number; y: number }
|
|
475
481
|
/** {index, offset} anchor capture for exact restore; null when either count is 0, or the embedded scroll rows are absent (all-frozen OR a degenerate band whose H_F fills the viewport — both force itemCount 0, so there is no scroll row to anchor). / 厳密復元用アンカー捕獲。どちらかの count が 0、またはスクロール行が不在 (全行凍結 / H_F がビューポートを埋める縮退帯 — いずれも itemCount 0) なら null。 */
|
|
476
482
|
getScrollAnchor(): { row: number; col: number; offsetX: number; offsetY: number } | null
|
|
@@ -480,14 +486,16 @@ export type VirtualGridHandle = {
|
|
|
480
486
|
/** Pair-call seam: getRowHeight stays the truth. 3-way split — leading band rows bump the frozen-row epoch, trailing band rows bump the trailing-row epoch (accessor re-read; the bands hold no tree), scroll rows delegate to the row updateItemSize at −R. / 対呼び出しシーム (getRowHeight が正)。3 分岐 — 先頭帯行は凍結行 epoch、末尾帯行は末尾行 epoch の繰上げ (アクセサ再読 — 帯に木は無い)、スクロール行は −R で行 updateItemSize へ委譲。 */
|
|
481
487
|
updateRowSize(row: number, px: number): void
|
|
482
488
|
/**
|
|
483
|
-
* Column twin: validates (MAX_TRACK_SIZE fail-fast)
|
|
484
|
-
* width
|
|
485
|
-
* offsetting batch resizes)
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
489
|
+
* Column twin: validates (MAX_TRACK_SIZE fail-fast) and updates the width tree. A width that
|
|
490
|
+
* changes advances the width tree's revision, so committed cells REPAINT even when the window
|
|
491
|
+
* key is unchanged (small grids, offsetting batch resizes); the width the column already has
|
|
492
|
+
* changes nothing and re-renders nothing. Multi-call batch loops coalesce into one recompute via
|
|
493
|
+
* React's automatic state batching (one commit per synchronous batch — the design's
|
|
494
|
+
* rAF-coalescing intent realized at commit granularity).
|
|
495
|
+
* 列双子: 検証 (fail-fast) → 幅木の更新。幅が変われば幅木の版数が進み、窓キー不変でもコミット済み
|
|
496
|
+
* セルを再描画する (小さなグリッド・相殺バッチリサイズ)。列が既に持つ幅は何も変えず、何も
|
|
497
|
+
* 再描画しない。連続呼び出しは React の自動バッチングで 1 コミットに合流 (設計の rAF 合流意図の
|
|
498
|
+
* コミット粒度での実現)。
|
|
491
499
|
*/
|
|
492
500
|
updateColSize(col: number, px: number): void
|
|
493
501
|
/**
|
|
@@ -573,7 +581,7 @@ export const computeColumnPlacement = (
|
|
|
573
581
|
// アンカー再基準化 (行 memo と同一ロジック): 描画窓先頭の絶対 X が現アンカーから距離超過の
|
|
574
582
|
// ときのみ付替え。付替え先は任意座標 (グリッド量子化ではなくトリガー距離 — 付録 A.2)
|
|
575
583
|
const safeStart = minmax(ranges.renderingStartIndex, 0, colCount - 1)
|
|
576
|
-
const { cumulative, currentValue } = colTree.prefixSum(safeStart,
|
|
584
|
+
const { cumulative, currentValue } = colTree.prefixSum(safeStart, FENWICK_LOOKUP_ONLY)
|
|
577
585
|
const startPosition = cumulative - currentValue
|
|
578
586
|
const colAnchor = Number.isFinite(startPosition) && Math.abs(startPosition - prevAnchor) > ANCHOR_REBASE_DISTANCE ? startPosition : prevAnchor
|
|
579
587
|
return {
|
|
@@ -606,7 +614,7 @@ const collectVisibleColumns = (startCol: number, endCol: number, getColWidth: (c
|
|
|
606
614
|
const columns: PlacedColumn[] = []
|
|
607
615
|
const widthUpdates: Array<{ index: number; value: number }> = []
|
|
608
616
|
// 呼び出し元は空窓 (start > end) を先に除くので、ここへ来る窓は 1 列以上
|
|
609
|
-
const startPrefix = colTree.prefixSum(startCol,
|
|
617
|
+
const startPrefix = colTree.prefixSum(startCol, FENWICK_LOOKUP_ONLY)
|
|
610
618
|
// 窓先頭の絶対 left から幅を積む O(k) 走査 (列ごとの prefixSum O(k·log n) を回避 — 行 memo と同型)
|
|
611
619
|
let runningLeft = startPrefix.cumulative - startPrefix.currentValue
|
|
612
620
|
let zeroRun = 0
|
|
@@ -634,11 +642,11 @@ const collectVisibleColumns = (startCol: number, endCol: number, getColWidth: (c
|
|
|
634
642
|
if (zeroRun > ZERO_HEIGHT_RUN_LIMIT) {
|
|
635
643
|
// 幅 0 ランの Fenwick ジャンプ (行側と共有定数・同一比較 `>` — 複製禁止): 次の非 0 列へ。
|
|
636
644
|
// +0.5 は右開区間バンプ — 木側の値で跳ぶため self-heal 前の乖離があっても有界性は保たれる
|
|
637
|
-
const jump = colTree.findIndexAtOrAfter(runningLeft + 0.5,
|
|
645
|
+
const jump = colTree.findIndexAtOrAfter(runningLeft + 0.5, FENWICK_LOOKUP_ONLY)
|
|
638
646
|
if (jump.index === -1 || jump.index <= i || !Number.isSafeInteger(jump.index)) {
|
|
639
647
|
break
|
|
640
648
|
}
|
|
641
|
-
const jumpPrefix = colTree.prefixSum(jump.index,
|
|
649
|
+
const jumpPrefix = colTree.prefixSum(jump.index, FENWICK_LOOKUP_ONLY)
|
|
642
650
|
runningLeft = jumpPrefix.cumulative - jumpPrefix.currentValue
|
|
643
651
|
i = jump.index
|
|
644
652
|
zeroRun = 0
|
|
@@ -847,10 +855,10 @@ const VirtualGridInner = <T,>(
|
|
|
847
855
|
}, [resetOnGetColWidthChange, defaultColWidth])
|
|
848
856
|
const colTree = useFenwickMapTree(colCount, validatedGetColWidth, colTreeOptions)
|
|
849
857
|
|
|
850
|
-
// ----
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
const
|
|
858
|
+
// ---- 列幅木の版数: 木は同一性を保ったまま描画の外で変わる (updateColSize・幅 self-heal) ので、変更はどれも changeColTree を
|
|
859
|
+
// 通し、木を読む memo と列窓の再計算は colTreeRevision に依存させる。総幅は相殺バッチリサイズ (総和不変) で変わらないため
|
|
860
|
+
// 合図にならず、同じ幅の書き込みは木の版を動かさないので再計算も確定も足さない (行側の VirtualScroll と同じ仕組み) ----
|
|
861
|
+
const { revision: colTreeRevision, change: changeColTree } = useFenwickTreeRevision(colTree)
|
|
854
862
|
|
|
855
863
|
// ---- 凍結帯 (設計 §8) — 形状検証は fail-fast、colCount 超過だけは動的クランプ (シート
|
|
856
864
|
// 切替の過渡で colCount が一時 0 になる正当構成を throw で殺さないための文書化契約) ----
|
|
@@ -971,7 +979,6 @@ const VirtualGridInner = <T,>(
|
|
|
971
979
|
// ---- 論理位置 (float64 ref — DOM へは書かない) と鮮度序列 ----
|
|
972
980
|
const hxRef = useRef(0)
|
|
973
981
|
const vyRef = useRef(0)
|
|
974
|
-
const attachedRef = useRef(false)
|
|
975
982
|
|
|
976
983
|
// ---- コミット済み列窓 + アンカー (M10 整合の単位) ----
|
|
977
984
|
const [columnWindow, setColumnWindow] = useState<ColumnWindowState>(INITIAL_COLUMN_WINDOW)
|
|
@@ -1004,36 +1011,69 @@ const VirtualGridInner = <T,>(
|
|
|
1004
1011
|
// ---- ビューポート実測 (RO — 縦バー幅ぶんを列窓から差し引く) ----
|
|
1005
1012
|
const scrollBarWidth = scrollBarOptions?.width ?? 12
|
|
1006
1013
|
const explicitViewport = viewportSize === undefined ? null : { width: Math.max(0, Math.round(viewportSize.width) - scrollBarWidth), height: Math.max(0, Math.round(viewportSize.height) - scrollBarWidth) }
|
|
1007
|
-
|
|
1008
|
-
|
|
1014
|
+
// ルートの箱はバー幅を引く前の寸法で持ち、引くのは描画の側に置く。バー幅が変わっても測り直さずに済み、計測はルートが
|
|
1015
|
+
// 付いている間ずっと同じ 1 つで足りる
|
|
1016
|
+
const [measuredRootSize, setMeasuredRootSize] = useState({ width: 0, height: 0 })
|
|
1017
|
+
const viewport = explicitViewport ?? { width: Math.max(0, measuredRootSize.width - scrollBarWidth), height: Math.max(0, measuredRootSize.height - scrollBarWidth) }
|
|
1009
1018
|
const viewportRef = useRef(viewport)
|
|
1010
1019
|
viewportRef.current = viewport
|
|
1011
1020
|
/** Degenerate band: H_F + H_T fills the measured viewport — scroll rows go canonical-EMPTY (§3.3 縮退帯の縦対称; 3.5.0 で −H_T を一般化). / 縮退帯: H_F + H_T が実測ビューポートを食い尽くす — スクロール行は正準空窓 (§3.3 の縦対称。3.5.0 で −H_T を一般化)。 */
|
|
1012
1021
|
const degenerateBand = viewport.height > 0 && viewport.height - frozenHeight - trailingHeight <= 0
|
|
1013
1022
|
degenerateBandRef.current = degenerateBand
|
|
1014
1023
|
const hasExplicitViewport = explicitViewport !== null
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1024
|
+
/**
|
|
1025
|
+
* Attaches the grid root: keeps `rootRef` on it and, while the viewport is self-measured, keeps `measuredRootSize` at
|
|
1026
|
+
* the root's layout box with one ResizeObserver for as long as the root stays attached. A ref callback with a cleanup
|
|
1027
|
+
* (React 19) receives the element itself, so the measurement never runs without one; the cleanup detaches the root and
|
|
1028
|
+
* stops the observer. Its identity changes only when the viewport mode changes.
|
|
1029
|
+
*
|
|
1030
|
+
* グリッドのルートを取り付ける処理。`rootRef` をルートに保ち、ビューポートを自分で測る間は、ルートが付いている限り 1 つの
|
|
1031
|
+
* ResizeObserver で `measuredRootSize` をルートのレイアウトの箱に保つ。後始末を返す ref コールバック (React 19) は要素そのものを
|
|
1032
|
+
* 受け取るので、要素の無いまま測ることはない。後始末はルートを外して監視を止める。同一性はビューポートの方式が変わるときだけ変わる。
|
|
1033
|
+
*
|
|
1034
|
+
* @param root - The grid root element / グリッドのルート要素
|
|
1035
|
+
* @returns The cleanup that detaches the root / ルートを外す後始末
|
|
1036
|
+
*/
|
|
1037
|
+
const attachRoot = useCallback(
|
|
1038
|
+
(root: HTMLDivElement) => {
|
|
1039
|
+
rootRef.current = root
|
|
1040
|
+
if (hasExplicitViewport) {
|
|
1041
|
+
/**
|
|
1042
|
+
* Detaches the root (the given viewport needs no observer).
|
|
1043
|
+
* ルートを外す処理 (与えられたビューポートには監視が要らない)。
|
|
1044
|
+
*/
|
|
1045
|
+
return () => {
|
|
1046
|
+
rootRef.current = null
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Reads the root's layout box into `measuredRootSize` (an unchanged box keeps the state as it is).
|
|
1051
|
+
*
|
|
1052
|
+
* ルートのレイアウトの箱を `measuredRootSize` へ読み込む処理 (箱が変わらなければ状態をそのまま保つ)。
|
|
1053
|
+
*/
|
|
1054
|
+
const measure = () => {
|
|
1055
|
+
// ❗ 論理 (レイアウト) px で測る — getBoundingClientRect は祖先 transform scale 下で
|
|
1056
|
+
// 視覚 px (論理 × z) を返し、ズームホストで窓寸・クランプ・列窓の全幾何が z 倍へ歪む。
|
|
1057
|
+
// clientWidth / clientHeight は transform 非影響のレイアウト px (行側 ScrollPane の
|
|
1058
|
+
// 自己計測と同じ座標系)
|
|
1059
|
+
const width = Math.round(root.clientWidth)
|
|
1060
|
+
const height = Math.round(root.clientHeight)
|
|
1061
|
+
setMeasuredRootSize((previous) => (previous.width === width && previous.height === height ? previous : { width, height }))
|
|
1062
|
+
}
|
|
1063
|
+
measure()
|
|
1064
|
+
const observer = new ResizeObserver(measure)
|
|
1065
|
+
observer.observe(root)
|
|
1066
|
+
/**
|
|
1067
|
+
* Detaches the root and stops the observer.
|
|
1068
|
+
* ルートを外し、監視を止める処理。
|
|
1069
|
+
*/
|
|
1070
|
+
return () => {
|
|
1071
|
+
observer.disconnect()
|
|
1072
|
+
rootRef.current = null
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
[hasExplicitViewport],
|
|
1076
|
+
)
|
|
1037
1077
|
|
|
1038
1078
|
// ---- 残差 var の書き手 (1 点)。コミット済みアンカー基準 — スクロール中の中間フレームでも
|
|
1039
1079
|
// セル left (コミット済みアンカー) と常に同一基準で整合する ----
|
|
@@ -1233,7 +1273,7 @@ const VirtualGridInner = <T,>(
|
|
|
1233
1273
|
// colCount 縮小で範囲外化したアンカーは最終列へ明示クランプ (fail-safe — 木側の暗黙
|
|
1234
1274
|
// クランプへ依存しない。挙動は §7 の既知の制約として記録)
|
|
1235
1275
|
const safeCol = minmax(anchor.col, 0, Math.max(0, colCount - 1))
|
|
1236
|
-
const prefix = colTree.prefixSum(safeCol,
|
|
1276
|
+
const prefix = colTree.prefixSum(safeCol, FENWICK_LOOKUP_ONLY)
|
|
1237
1277
|
const colLeft = prefix.cumulative - prefix.currentValue
|
|
1238
1278
|
const colWidth = prefix.currentValue
|
|
1239
1279
|
// 凍結帯 (§8): 整列先はスクロール帯 [W_F, viewport − W_T) — 列座標から W_F を引き、
|
|
@@ -1247,7 +1287,7 @@ const VirtualGridInner = <T,>(
|
|
|
1247
1287
|
)
|
|
1248
1288
|
|
|
1249
1289
|
// ---- 幅 self-heal (窓内の getColWidth と木の乖離をバッチ適用 — 行 memo の toUpdateHeights 双子)。
|
|
1250
|
-
//
|
|
1290
|
+
// 適用は木の版数を進め、治癒前の stale な左座標で確定したセル配置を再計算させる ----
|
|
1251
1291
|
const pendingWidthUpdatesRef = useRef<Array<{ index: number; value: number }> | null>(null)
|
|
1252
1292
|
const flushWidthUpdates = useCallback(() => {
|
|
1253
1293
|
const updates = pendingWidthUpdatesRef.current
|
|
@@ -1255,17 +1295,16 @@ const VirtualGridInner = <T,>(
|
|
|
1255
1295
|
if (updates === null || updates.length === 0) {
|
|
1256
1296
|
return
|
|
1257
1297
|
}
|
|
1258
|
-
commitTotalWidth(colTree.updates(updates))
|
|
1259
|
-
|
|
1260
|
-
}, [colTree, commitTotalWidth])
|
|
1298
|
+
commitTotalWidth(changeColTree(() => colTree.updates(updates)))
|
|
1299
|
+
}, [colTree, changeColTree, commitTotalWidth])
|
|
1261
1300
|
|
|
1262
1301
|
// ---- 凍結帯の列挙 (設計 §8 — 窓化されない静的帯)。幅 self-heal は共有バッチへ合流し、
|
|
1263
1302
|
// 暴走ガードは帯専用カウンタ (スクロール窓側と独立 — 凍結専用振動アクセサが窓側の
|
|
1264
1303
|
// 収束リセットで永久にガードを回避する穴を作らない) ----
|
|
1265
1304
|
const frozenHealBurstRef = useRef({ count: 0, warned: false })
|
|
1266
1305
|
const { frozenColumns, frozenWidth } = useMemo(() => {
|
|
1267
|
-
//
|
|
1268
|
-
void
|
|
1306
|
+
// 列幅木の版数は木の中身の反応辺 (スクロール窓 memo と同じ規律)
|
|
1307
|
+
void colTreeRevision
|
|
1269
1308
|
if (effectiveFrozenCols === 0) {
|
|
1270
1309
|
// 凍結解除は帯ガードのエピソードも閉じる — 次の凍結エピソードが停止状態を継承して
|
|
1271
1310
|
// 無言で self-heal 不能になる越境リークの遮断 (R3 実証)
|
|
@@ -1295,7 +1334,7 @@ const VirtualGridInner = <T,>(
|
|
|
1295
1334
|
frozenHealBurstRef.current.warned = false
|
|
1296
1335
|
}
|
|
1297
1336
|
return { frozenColumns: columns, frozenWidth: width }
|
|
1298
|
-
}, [effectiveFrozenCols,
|
|
1337
|
+
}, [effectiveFrozenCols, colTreeRevision, validatedGetColWidth, colTree, flushWidthUpdates])
|
|
1299
1338
|
frozenWidthRef.current = frozenWidth
|
|
1300
1339
|
// 凍結幅 var の書き手 (1 点): クリップ左端 (.aqvs-grid-row-scroll の left) と残差の −W_F 項
|
|
1301
1340
|
// (.aqvs-grid-row-scroll-inner の calc) の両消費者が読む
|
|
@@ -1314,8 +1353,8 @@ const VirtualGridInner = <T,>(
|
|
|
1314
1353
|
// 閉じる — 先頭帯の「越境リーク遮断」と同文) ----
|
|
1315
1354
|
const trailingHealBurstRef = useRef({ count: 0, warned: false })
|
|
1316
1355
|
const { trailingColumns, trailingWidth } = useMemo(() => {
|
|
1317
|
-
//
|
|
1318
|
-
void
|
|
1356
|
+
// 列幅木の版数は木の中身の反応辺 (先頭帯 memo と同じ規律)
|
|
1357
|
+
void colTreeRevision
|
|
1319
1358
|
if (effectiveTrailingCols === 0) {
|
|
1320
1359
|
// 末尾凍結解除は帯ガードのエピソードも閉じる (先頭帯の R3 実証と同文の遮断)
|
|
1321
1360
|
trailingHealBurstRef.current.count = 0
|
|
@@ -1344,7 +1383,7 @@ const VirtualGridInner = <T,>(
|
|
|
1344
1383
|
trailingHealBurstRef.current.warned = false
|
|
1345
1384
|
}
|
|
1346
1385
|
return { trailingColumns: columns, trailingWidth: width }
|
|
1347
|
-
}, [effectiveTrailingCols, colCount,
|
|
1386
|
+
}, [effectiveTrailingCols, colCount, colTreeRevision, validatedGetColWidth, colTree, flushWidthUpdates])
|
|
1348
1387
|
trailingWidthRef.current = trailingWidth
|
|
1349
1388
|
// 末尾幅 var の書き手 (3.5.0 プラン §6.1-8 — **単一書き手規律**: この effect だけが書く)。
|
|
1350
1389
|
// 消費者はクリップ右端 (.aqvs-grid-row-scroll の right)・末尾クリップの max() 左端と inner 幅。
|
|
@@ -1408,24 +1447,24 @@ const VirtualGridInner = <T,>(
|
|
|
1408
1447
|
* throttled `onScroll` relay (`handleVerticalScroll`) — which regresses it by up to the
|
|
1409
1448
|
* `callbackThrottleMs` window mid-drive; a driver base read from the mirror overstates
|
|
1410
1449
|
* `actualDelta` by the staleness gap, drives the axis residual negative, and the driver
|
|
1411
|
-
* then issues REAL backward `scrollBy` writes (the 3.6.0 y-axis tap yo-yo — v3.6.1).
|
|
1450
|
+
* then issues REAL backward `scrollBy` writes (the 3.6.0 y-axis tap yo-yo — v3.6.1). The grid
|
|
1451
|
+
* handle's `getScrollPosition` reads its y here too. Only once the grid has unmounted (a kept
|
|
1452
|
+
* handle, a tap frame between the unmount commit and the passive cleanup) is the embedded
|
|
1453
|
+
* handle gone; then the mirror is the position `applyVy` holds still at, and it is returned.
|
|
1412
1454
|
* 埋め込みハンドル経由の鮮度 y 位置 — `applyVy` の `scrollBy` が updater を解決するのと
|
|
1413
1455
|
* **同一の権威** (ペイン内部 ref)。`getMaxVy` の鮮度チャネル、および「`getHx` が `applyHx`
|
|
1414
1456
|
* の書き先セルそのものを読む」x 側と対称。ここで `vyRef` を読んではならない: あの鏡像には
|
|
1415
1457
|
* 第 2 の書き手 (スロットル済み `onScroll` 中継 = `handleVerticalScroll`) が居て、駆動中に
|
|
1416
1458
|
* 最大 `callbackThrottleMs` 窓ぶん巻き戻す。鏡像基準の `actualDelta` は staleness ギャップ
|
|
1417
1459
|
* ぶん過大になり、軸残差が負へ落ちて、ドライバ自身が**実後退**の `scrollBy` を発行する
|
|
1418
|
-
* (3.6.0 の y 軸タップヨーヨー — v3.6.1)
|
|
1460
|
+
* (3.6.0 の y 軸タップヨーヨー — v3.6.1)。グリッドのハンドルの `getScrollPosition` も y をここで読む。
|
|
1461
|
+
* 埋め込みハンドルが無いのはグリッドがアンマウントした後だけ (保持したハンドル・アンマウントの確定から
|
|
1462
|
+
* 受け身の後始末までのタップのフレーム) で、そのときは `applyVy` が留まる位置である鏡像を返す。
|
|
1419
1463
|
*/
|
|
1420
1464
|
const getVy = useCallback(() => {
|
|
1421
1465
|
const inner = scrollHandleRef.current
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
return vyRef.current
|
|
1425
|
-
}
|
|
1426
|
-
const position = inner.getScrollPosition()
|
|
1427
|
-
// ペイン未接続の番兵 (-1) も applyVy の不動姿勢と同じ座標へ落とす (負位置は実在しない)
|
|
1428
|
-
return position < 0 ? vyRef.current : position
|
|
1466
|
+
// 埋め込みのペインは埋め込みのハンドルと同じ確定で付き外れするので、ハンドルがある間は未接続の番兵 (-1) が返らない
|
|
1467
|
+
return inner === null ? vyRef.current : inner.getScrollPosition()
|
|
1429
1468
|
}, [])
|
|
1430
1469
|
/**
|
|
1431
1470
|
* Fresh y extent through the embedded handle — the same freshness channel the vertical bar
|
|
@@ -1442,17 +1481,19 @@ const VirtualGridInner = <T,>(
|
|
|
1442
1481
|
}, [])
|
|
1443
1482
|
/**
|
|
1444
1483
|
* The y apply seam (§3.5-8): wraps the embedded `scrollBy` AND syncs `vyRef.current =
|
|
1445
|
-
* applied` before returning — the grid handle `scrollBy`
|
|
1446
|
-
* `applyHxRef`. Without the sync, x-frame `onScroll({ x, y: vyRef.current })`
|
|
1447
|
-
* during a diagonal drag carry y stale by up to the `callbackThrottleMs` window
|
|
1484
|
+
* applied` before returning — the y half of the grid handle's `scrollBy` as well, symmetric
|
|
1485
|
+
* with `applyHxRef`. Without the sync, x-frame `onScroll({ x, y: vyRef.current })`
|
|
1486
|
+
* notifications during a diagonal drag carry y stale by up to the `callbackThrottleMs` window
|
|
1487
|
+
* (MV-XY29). Without the embedded handle (only after the grid unmounted) nothing moves and the
|
|
1488
|
+
* current mirror is returned.
|
|
1448
1489
|
* y 適用シーム (§3.5-8): 埋め込み `scrollBy` をラップし、**返す前に** `vyRef.current =
|
|
1449
|
-
* applied` を同期する —
|
|
1450
|
-
* 同期が無いと対角ドラッグ中の x フレーム通知が stale y を運ぶ (MV-XY29)
|
|
1490
|
+
* applied` を同期する — グリッドハンドルの `scrollBy` の y 半分もこれを通り、`applyHxRef` と対称。
|
|
1491
|
+
* 同期が無いと対角ドラッグ中の x フレーム通知が stale y を運ぶ (MV-XY29)。埋め込みハンドルが無い
|
|
1492
|
+
* (グリッドのアンマウントの後だけ) ときは何も動かさず、今の鏡像を返す。
|
|
1451
1493
|
*/
|
|
1452
1494
|
const applyVy = useCallback((delta: number): number => {
|
|
1453
1495
|
const inner = scrollHandleRef.current
|
|
1454
1496
|
if (inner === null) {
|
|
1455
|
-
// ハンドル未接続時は現在値を返して動かない (グリッドハンドル scrollBy と同じ姿勢)
|
|
1456
1497
|
return vyRef.current
|
|
1457
1498
|
}
|
|
1458
1499
|
const applied = inner.scrollBy(delta)
|
|
@@ -1488,7 +1529,7 @@ const VirtualGridInner = <T,>(
|
|
|
1488
1529
|
return minmax(baseOpacity * resolvedTapX.opacity, 0, 1)
|
|
1489
1530
|
}, [isTapActive, resolvedTapX.opacity])
|
|
1490
1531
|
|
|
1491
|
-
// ---- ビューポート / 総幅 /
|
|
1532
|
+
// ---- ビューポート / 総幅 / 列幅木の版数の変化での再クランプ + 列窓追随。保留列アンカーが
|
|
1492
1533
|
// あれば位置はアンカーから再導出 (単なる再クランプでは推定空間の hx が残留する) ----
|
|
1493
1534
|
useLayoutEffect(() => {
|
|
1494
1535
|
const pending = pendingColAnchorRef.current
|
|
@@ -1498,7 +1539,7 @@ const VirtualGridInner = <T,>(
|
|
|
1498
1539
|
applyHxRef.current(resolveColumnAnchorHx(pending))
|
|
1499
1540
|
}
|
|
1500
1541
|
refreshColumns()
|
|
1501
|
-
}, [viewport.width, totalWidth,
|
|
1542
|
+
}, [viewport.width, totalWidth, colTreeRevision, frozenWidth, trailingWidth, refreshColumns, resolveColumnAnchorHx])
|
|
1502
1543
|
|
|
1503
1544
|
// ---- 初期アンカー復元 (横) — 生 px でなく {col, offsetX} (行と同一契約)。マウント時 1 回。
|
|
1504
1545
|
// 保留列アンカーとして張り、以後の self-heal による列左の実測化へ追随させる ----
|
|
@@ -1526,7 +1567,6 @@ const VirtualGridInner = <T,>(
|
|
|
1526
1567
|
// ---- 縦軸の結線 ----
|
|
1527
1568
|
const handleVerticalScroll = useCallback((position: number) => {
|
|
1528
1569
|
vyRef.current = position
|
|
1529
|
-
attachedRef.current = true
|
|
1530
1570
|
onScrollRef.current?.({ x: hxRef.current, y: position })
|
|
1531
1571
|
}, [])
|
|
1532
1572
|
const handleVerticalRangeChange = useCallback(
|
|
@@ -1571,14 +1611,14 @@ const VirtualGridInner = <T,>(
|
|
|
1571
1611
|
// (行凍結設計 §4-7、3.5.0 プラン §6.1-13)
|
|
1572
1612
|
const maxColsPerRow = resolveMaxColsPerRow(__maxRenderedCells, effectiveRenderedRows + frozenRows.length + trailingRows.length)
|
|
1573
1613
|
// self-heal の暴走ガード: 乖離→治癒→再乖離の非収束が連続する = getColWidth が自身の
|
|
1574
|
-
// 過去値と不一致を繰り返す契約違反 (振動アクセサ)
|
|
1614
|
+
// 過去値と不一致を繰り返す契約違反 (振動アクセサ)。放置すると治癒→版数→再走査の
|
|
1575
1615
|
// マイクロタスクループが無限旋回するため、上限で治癒を停止し 1 回だけ警告する。
|
|
1576
1616
|
// 状態は {count, warned} のみ — 窓キーは保持しない (キー基準の解消は窓端を動かす振動に
|
|
1577
1617
|
// 永久回避されるため構造的に持たない。解消は収束パスのみ)
|
|
1578
1618
|
const healBurstRef = useRef({ count: 0, warned: false })
|
|
1579
1619
|
const { placedColumns, truncatedCols } = useMemo(() => {
|
|
1580
|
-
//
|
|
1581
|
-
void
|
|
1620
|
+
// 列幅木の版数は木の中身の反応辺 (木は同一性を保つので、木の参照では失効しない)
|
|
1621
|
+
void colTreeRevision
|
|
1582
1622
|
// 凍結帯 (§8): スクロール帯幅 ≤ 0 (W_F + W_T ≥ viewport の縮退 — 3.5.0 一般化) は
|
|
1583
1623
|
// スクロール列を描かない — 凍結帯 / 末尾帯自体は独立 memo が描き続ける定義済み縮退
|
|
1584
1624
|
const bandWidth = Math.max(0, viewport.width - frozenWidth - trailingWidth)
|
|
@@ -1621,7 +1661,7 @@ const VirtualGridInner = <T,>(
|
|
|
1621
1661
|
// deps へ trailingWidth / trailingColumns.length を明示追加 (3.5.0 プラン §6.1-6 / F4):
|
|
1622
1662
|
// bandWidth 式とスクロール列予算が同 memo 内で両値を読む — 追加を怠ると列窓キー不変の
|
|
1623
1663
|
// T トグルで memo が stale 化し、予算が trailing 分だけ過大に残る
|
|
1624
|
-
}, [columnWindow, colCount, viewport.width, frozenWidth, trailingWidth, frozenColumns.length, trailingColumns.length,
|
|
1664
|
+
}, [columnWindow, colCount, viewport.width, frozenWidth, trailingWidth, frozenColumns.length, trailingColumns.length, colTreeRevision, validatedGetColWidth, colTree, maxColsPerRow, flushWidthUpdates])
|
|
1625
1665
|
|
|
1626
1666
|
// 打切りは無言にしない (設計 §3.2 — 行側の病的ガードと違い正当構成で到達し得る)
|
|
1627
1667
|
const lastTruncationKeyRef = useRef("")
|
|
@@ -1774,6 +1814,12 @@ const VirtualGridInner = <T,>(
|
|
|
1774
1814
|
useImperativeHandle(
|
|
1775
1815
|
ref,
|
|
1776
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
|
+
*/
|
|
1777
1823
|
scrollTo: (pos) => {
|
|
1778
1824
|
const inner = scrollHandleRef.current
|
|
1779
1825
|
let y = vyRef.current
|
|
@@ -1790,13 +1836,14 @@ const VirtualGridInner = <T,>(
|
|
|
1790
1836
|
}
|
|
1791
1837
|
return { x, y }
|
|
1792
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
|
+
*/
|
|
1793
1844
|
scrollBy: (delta) => {
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
if (delta.y !== undefined && delta.y !== 0 && inner !== null) {
|
|
1797
|
-
y = inner.scrollBy(delta.y)
|
|
1798
|
-
vyRef.current = y
|
|
1799
|
-
}
|
|
1845
|
+
// 縦の差分はタップの駆動と同じ継ぎ目 (applyVy) で当て、適用と鏡像の同期を 2 通りに書かない
|
|
1846
|
+
const y = delta.y !== undefined && delta.y !== 0 ? applyVy(delta.y) : vyRef.current
|
|
1800
1847
|
let x = hxRef.current
|
|
1801
1848
|
if (delta.x !== undefined && delta.x !== 0) {
|
|
1802
1849
|
pendingColAnchorRef.current = null
|
|
@@ -1804,6 +1851,12 @@ const VirtualGridInner = <T,>(
|
|
|
1804
1851
|
}
|
|
1805
1852
|
return { x, y }
|
|
1806
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
|
+
*/
|
|
1807
1860
|
scrollToCell: (row, col, options) => {
|
|
1808
1861
|
const inner = scrollHandleRef.current
|
|
1809
1862
|
if (inner !== null && rowCount > 0) {
|
|
@@ -1828,7 +1881,18 @@ const VirtualGridInner = <T,>(
|
|
|
1828
1881
|
}
|
|
1829
1882
|
}
|
|
1830
1883
|
},
|
|
1831
|
-
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
1832
1896
|
getScrollAnchor: () => {
|
|
1833
1897
|
if (rowCount === 0 || colCount === 0) {
|
|
1834
1898
|
return null
|
|
@@ -1843,7 +1907,7 @@ const VirtualGridInner = <T,>(
|
|
|
1843
1907
|
// 凍結帯 (§8): アンカーはスクロール帯の可視先頭 = 木座標 W_F + hx で捕獲する
|
|
1844
1908
|
// (復元側 resolveColumnAnchorHx が W_F を引き戻すため往復が恒等)
|
|
1845
1909
|
const bandStart = frozenWidthRef.current + hxRef.current
|
|
1846
|
-
const found = colTree.findIndexAtOrAfter(bandStart,
|
|
1910
|
+
const found = colTree.findIndexAtOrAfter(bandStart, FENWICK_LOOKUP_ONLY)
|
|
1847
1911
|
if (found.index === -1) {
|
|
1848
1912
|
return { row: rowAnchor.index + effectiveFrozenRows, col: colCount - 1, offsetX: 0, offsetY: rowAnchor.offsetPx }
|
|
1849
1913
|
}
|
|
@@ -1851,15 +1915,30 @@ const VirtualGridInner = <T,>(
|
|
|
1851
1915
|
return { row: rowAnchor.index + effectiveFrozenRows, col: minmax(found.index + 1, 0, colCount - 1), offsetX: 0, offsetY: rowAnchor.offsetPx }
|
|
1852
1916
|
}
|
|
1853
1917
|
// 見つけた列の左端は木から読み直す (探索の結果の型は列が無い場合の undefined を含むため)
|
|
1854
|
-
const column = colTree.prefixSum(found.index,
|
|
1918
|
+
const column = colTree.prefixSum(found.index, FENWICK_LOOKUP_ONLY)
|
|
1855
1919
|
// 行はシフト空間 → フル行空間へ +R 翻訳 (行凍結設計 §3 — 復元側は −R で往復恒等)
|
|
1856
1920
|
return { row: rowAnchor.index + effectiveFrozenRows, col: found.index, offsetX: Math.max(0, bandStart - (column.cumulative - column.currentValue)), offsetY: rowAnchor.offsetPx }
|
|
1857
1921
|
},
|
|
1922
|
+
/**
|
|
1923
|
+
* Reads the cell viewport (the measured or given box without the bars).
|
|
1924
|
+
* セルのビューポート (測ったか与えられた箱からバーを除いたもの) を読む処理。
|
|
1925
|
+
*/
|
|
1858
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
|
+
*/
|
|
1859
1932
|
// 縦はインセット込みの行側 getContentSize を単一情報源にする (range.totalHeight は木総高のみ)。
|
|
1860
1933
|
// 3.5.0: height = H_F + 埋め込み + H_T
|
|
1861
1934
|
getContentSize: () => ({ width: totalWidthRef.current, height: scrollHandleRef.current === null ? 0 : frozenHeightRef.current + trailingHeightRef.current + Math.max(0, scrollHandleRef.current.getContentSize()) }),
|
|
1862
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
|
+
*/
|
|
1863
1942
|
updateRowSize: (row, px) => {
|
|
1864
1943
|
validateTrackSize(px, "row", row)
|
|
1865
1944
|
// 凍結行はグリッド所有バンド — epoch 繰上げでアクセサ再読 (帯に第 2 の格納庫は
|
|
@@ -1873,13 +1952,20 @@ const VirtualGridInner = <T,>(
|
|
|
1873
1952
|
scrollHandleRef.current?.updateItemSize(Math.trunc(row) - effectiveFrozenRows, px)
|
|
1874
1953
|
}
|
|
1875
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
|
+
*/
|
|
1876
1959
|
updateColSize: (col, px) => {
|
|
1877
1960
|
validateTrackSize(px, "col", col)
|
|
1878
|
-
// 総幅は ref へも即時反映 (同 tick の scrollBy/scrollTo が旧上限でクランプしないため)
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
setWidthEpoch((epoch) => epoch + 1)
|
|
1961
|
+
// 総幅は ref へも即時反映 (同 tick の scrollBy/scrollTo が旧上限でクランプしないため)。幅が変われば木の版数が進み、
|
|
1962
|
+
// 窓キー不変でもセル配置を失効させる (バッチループは React 自動バッチングで 1 コミット)。同じ幅は何も失効させない
|
|
1963
|
+
commitTotalWidth(changeColTree(() => colTree.update(minmax(Math.trunc(col), 0, Math.max(0, colCount - 1)), px)))
|
|
1882
1964
|
},
|
|
1965
|
+
/**
|
|
1966
|
+
* Reads the effective frozen bands of both axes and both ends (the visible trailing sizes through `trailingVisibleSize`).
|
|
1967
|
+
* 両軸・両端の実効の凍結帯を読む処理 (見えている末尾の寸法は `trailingVisibleSize` で求める)。
|
|
1968
|
+
*/
|
|
1883
1969
|
getFrozenSize: () => ({
|
|
1884
1970
|
cols: effectiveFrozenCols,
|
|
1885
1971
|
width: frozenWidthRef.current,
|
|
@@ -1893,7 +1979,18 @@ const VirtualGridInner = <T,>(
|
|
|
1893
1979
|
trailingVisibleWidth: trailingVisibleSize(trailingWidthRef.current, frozenWidthRef.current, viewportRef.current.width),
|
|
1894
1980
|
trailingVisibleHeight: trailingVisibleSize(trailingHeightRef.current, frozenHeightRef.current, viewportRef.current.height),
|
|
1895
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
|
+
*/
|
|
1896
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
|
+
*/
|
|
1897
1994
|
focusRowAtIndex: (row, options) => {
|
|
1898
1995
|
// 凍結行はバンド行 DOM へ直接フォーカス、末尾帯行も帯行 DOM へ直接フォーカス
|
|
1899
1996
|
// (3.5.0 — 3 分岐。T = 0 は分岐不成立で従来挙動と恒等)、スクロール行は −R 翻訳して委譲
|
|
@@ -1907,11 +2004,11 @@ const VirtualGridInner = <T,>(
|
|
|
1907
2004
|
}
|
|
1908
2005
|
},
|
|
1909
2006
|
}),
|
|
1910
|
-
[rowCount, colCount, colTree, buildRange, commitTotalWidth, resolveColumnAnchorHx, effectiveFrozenCols, effectiveFrozenRows, effectiveTrailingCols, effectiveTrailingRows],
|
|
2007
|
+
[rowCount, colCount, colTree, changeColTree, buildRange, commitTotalWidth, resolveColumnAnchorHx, getVy, applyVy, effectiveFrozenCols, effectiveFrozenRows, effectiveTrailingCols, effectiveTrailingRows],
|
|
1911
2008
|
)
|
|
1912
2009
|
|
|
1913
2010
|
return (
|
|
1914
|
-
<div ref={
|
|
2011
|
+
<div ref={attachRoot} className={twMerge("aqvs-grid", effectiveFrozenRows > 0 ? "aqvs-grid-has-frozen-rows" : "", effectiveTrailingRows > 0 ? "aqvs-grid-has-trailing-rows" : "", className)} data-testid={testId}>
|
|
1915
2012
|
{effectiveFrozenRows > 0 ? (
|
|
1916
2013
|
// 凍結行帯 (行凍結設計 §4-2): グリッド所有の帯外バンド — 行本体は renderRow を
|
|
1917
2014
|
// そのまま再利用する (F > 0 の凍結セル / クリップ / hx 残差 var は継承で無償追随
|