@aiquants/virtualscroll 3.7.1 → 3.8.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.
@@ -6,19 +6,31 @@
6
6
  * scrolling that never lands total-size pixels in the DOM). Column count inherits the full
7
7
  * row-axis scale profile (<= 2^53 - 1). Grid semantics (selection / editing / clipboard)
8
8
  * are deliberately NOT owned here — consumers attach them via getCellProps / getRowProps /
9
- * contentProps. Design: docs/plans/2026.09.01 virtualgrid-horizontal-axis-design-plan (v6).
9
+ * contentProps. Bounding contract (both axes): the anchored coordinates written to the DOM (cell /
10
+ * row offsets and the residual transforms) stay in the full-precision band while
11
+ * z × (ANCHOR_REBASE_DISTANCE + windowSpan) <= 2^24, where z is the ancestor scale and
12
+ * windowSpan <= viewport + (2 × overscan + 2) × MAX_TRACK_SIZE is the rendered window; with the
13
+ * default overscan of 3 and a viewport of at most 2^13 px it holds up to z = 5.31.
10
14
  * Since v3.6.0 the grid owns ONE unified two-axis tap circle (axis="xy", corner-anchored via the
11
- * three-arm placement law) driven by useGridTapScroll; both per-bar circles are suppressed
12
- * grid-side (docs/plans/2026.09.16 unified-tap-circle plan).
15
+ * three-arm placement law) driven by useGridTapScroll, so one drag vector scrolls both axes. Both
16
+ * per-bar circles are suppressed grid-side because two circles can never be dragged together: each
17
+ * bar's document-level pointerdown listener ends its own drag when the other circle is pressed.
18
+ * A standalone ScrollBar / VirtualScroll keeps its own circle.
13
19
  *
14
20
  * VirtualGrid — 実証済みの VirtualScroll 行軸に、対称の横列エンジン (同一の兆スケール機構:
15
21
  * 疎 Fenwick 幅木・共有 computeRenderingRanges (BigInt huge 分岐込み)・ANCHOR_REBASE_DISTANCE
16
22
  * の量子化アンカー再基準化・総サイズ px を DOM に着地させない合成スクロール) を合成した
17
23
  * 汎用 2D 仮想化プリミティブ。列数は行軸のスケールプロファイル (≤ 2^53 − 1) を完全継承する。
18
24
  * グリッド操作の意味論 (選択 / 編集 / クリップボード) は意図的に非所有 — 消費側が
19
- * getCellProps / getRowProps / contentProps で装着する。v3.6.0 からグリッドは統合 2 軸
20
- * タップサークル (axis="xy"・3 アーム則のコーナーアンカー・useGridTapScroll 駆動) を 1 つ
21
- * 所有し、両バーのサークルはグリッド側で抑止する。
25
+ * getCellProps / getRowProps / contentProps で装着する。有界化契約 (両軸): 祖先スケール z と
26
+ * 描画窓スパン windowSpan (≤ viewport + (2 × overscan + 2) × MAX_TRACK_SIZE) について
27
+ * z × (ANCHOR_REBASE_DISTANCE + windowSpan) ≤ 2^24 である限り、DOM へ書くアンカー基準の座標
28
+ * (セル / 行の位置と残差 transform) は完全精度帯に収まる。overscan が既定の 3 で viewport が
29
+ * 2^13 px 以下なら z = 5.31 まで成り立つ。v3.6.0 からグリッドは統合 2 軸タップサークル
30
+ * (axis="xy"・3 アーム則のコーナーアンカー・useGridTapScroll 駆動) を 1 つ所有し、1 本の
31
+ * ドラッグで両軸をスクロールする。両バーのサークルはグリッド側で抑止する — 2 つのサークルは
32
+ * 同時にドラッグできない (各バーの document 捕捉の pointerdown が、他方のサークルの押下で自身の
33
+ * ドラッグを終える) ため。単体の ScrollBar / VirtualScroll は自身のサークルを保つ。
22
34
  */
23
35
 
24
36
  import { type CSSProperties, forwardRef, type HTMLAttributes, type ReactNode, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react"
@@ -36,13 +48,15 @@ import { ANCHOR_REBASE_DISTANCE, computeRenderingRanges, MAX_RENDERED_ITEMS, Vir
36
48
 
37
49
  /**
38
50
  * Hard per-track size cap (px) for both axes. Values above this are rejected with a RangeError
39
- * (fail-fast, no silent clamp): the §6 bounding contract `z × (ANCHOR_REBASE_DISTANCE +
40
- * windowSpan) <= 2^24` relies on every track being bounded, and a single oversized track would
41
- * let cell coordinates / the residual transform pierce the measured LayoutUnit wall (2^25).
51
+ * (fail-fast, no silent clamp): the bounding contract `z × (ANCHOR_REBASE_DISTANCE + windowSpan)
52
+ * <= 2^24` (z = ancestor scale, windowSpan <= viewport + (2 × overscan + 2) × MAX_TRACK_SIZE)
53
+ * relies on every track being bounded, and a single oversized track would let cell coordinates /
54
+ * the residual transform pierce the measured LayoutUnit wall (2^25).
42
55
  *
43
56
  * 両軸共通のトラックサイズ上限 (px)。超過は RangeError で fail-fast (暗黙 clamp なし):
44
- * §6 の有界化契約はトラック有界性に立脚し、1 本の過大トラックでセル座標 / 残差 transform が
45
- * 実測の LayoutUnit 壁 (2^25) を踏み抜くため。
57
+ * 有界化契約 `z × (ANCHOR_REBASE_DISTANCE + windowSpan) ≤ 2^24` (z = 祖先スケール、windowSpan ≤
58
+ * viewport + (2 × overscan + 2) × MAX_TRACK_SIZE) はトラック有界性に立脚し、1 本の過大トラックで
59
+ * セル座標 / 残差 transform が実測の LayoutUnit 壁 (2^25) を踏み抜くため。
46
60
  */
47
61
  export const MAX_TRACK_SIZE = 262_144 // 2^18 px
48
62
 
@@ -61,25 +75,25 @@ const DEFAULT_GRID_OVERSCAN = 3
61
75
  * Safety cap for cell nodes materialized in one render pass (rendered rows × rendered cols).
62
76
  * Unlike the row-side MAX_RENDERED_ITEMS (a pathological-input guard), the 2D product can be
63
77
  * reached by LEGITIMATE configurations (min zoom on large displays), so truncation is never
64
- * silent — onRenderTruncated fires. Initial value assumes WQHD; §9-3 of the design plan owns
65
- * the final calibration (4K floor ≈ 55,955).
78
+ * silent — onRenderTruncated fires. The value covers a WQHD display at minimum zoom; the same
79
+ * configuration on a 4K display needs about 55,955 cells and is truncated there.
66
80
  *
67
81
  * 1 レンダーで具現化するセルノード数 (描画行 × 描画列) の安全上限。行側 MAX_RENDERED_ITEMS
68
82
  * (病的入力ガード) と違い、2D 積は正当な構成 (大型ディスプレイの最小ズーム) で到達し得るため
69
- * 打切りは無言にしない — onRenderTruncated が発火する。初期値は WQHD 前提で、最終確定は
70
- * 設計計画 §9-3 の所有 (4K の床 ≈ 55,955)。
83
+ * 打切りは無言にしない — onRenderTruncated が発火する。値は WQHD ディスプレイの最小ズームを
84
+ * 賄う大きさで、同じ構成の 4K ディスプレイでは約 55,955 セルが必要となり打ち切られる。
71
85
  */
72
86
  export const MAX_RENDERED_CELLS = 32_768 // 2^15
73
87
 
74
88
  /**
75
- * Hard cap for `frozenLeadingCols` (design §8). The frozen band is NOT windowed — every frozen
89
+ * Hard cap for `frozenLeadingCols`. The frozen band is NOT windowed — every frozen
76
90
  * column materializes in every rendered row and is EXEMPT from the MAX_RENDERED_CELLS
77
91
  * truncation (the scroll-band budget is reduced by the frozen count instead) — so the cap is
78
92
  * what bounds the band: worst case 128 x MAX_RENDERED_ITEMS rows, and W_F <= 128 x
79
93
  * MAX_TRACK_SIZE = 2^25 (the measured layout wall; practical W_F <= viewport << 2^24). Real
80
94
  * frozen panes are single-digit; violations fail fast with a RangeError.
81
95
  *
82
- * `frozenLeadingCols` の上限 (設計 §8)。凍結帯は窓化されず全描画行で具現化し、かつ
96
+ * `frozenLeadingCols` の上限。凍結帯は窓化されず全描画行で具現化し、かつ
83
97
  * MAX_RENDERED_CELLS の打切り対象外 (代わりにスクロール帯予算を凍結数ぶん控除) のため、帯を
84
98
  * 有界にするのはこの上限そのもの: 最悪 128 × MAX_RENDERED_ITEMS 行、W_F ≤ 128 × MAX_TRACK_SIZE
85
99
  * = 2^25 (実測壁ちょうど — 実運用は W_F ≤ viewport ≪ 2^24)。違反は fail-fast の RangeError。
@@ -87,44 +101,42 @@ export const MAX_RENDERED_CELLS = 32_768 // 2^15
87
101
  export const MAX_FROZEN_LEADING_COLS = 128
88
102
 
89
103
  /**
90
- * Hard cap for `frozenLeadingRows` (frozen-rows design plan §4-1). The frozen-row band is
91
- * grid-owned and OUT of the embedded VirtualScroll (index-shift architecture — plan §2):
92
- * every band row materializes every rendered column and is exempt from MAX_RENDERED_CELLS
104
+ * Hard cap for `frozenLeadingRows`. The frozen-row band is grid-owned and OUT of the embedded
105
+ * VirtualScroll (the grid shifts the scroll rows' indices by the frozen count before handing them
106
+ * over): every band row materializes every rendered column and is exempt from MAX_RENDERED_CELLS
93
107
  * (the budget denominator counts band rows instead), so the cap bounds the band itself.
94
- * `frozenLeadingRows` の上限 (行凍結設計 §4-1)。凍結行帯はグリッド所有で埋め込み
95
- * VirtualScroll の外 (インデックスシフト方式 — 設計 §2)。帯行は描画列を全て具現化し
108
+ * Violations fail fast with a RangeError.
109
+ * `frozenLeadingRows` の上限。凍結行帯はグリッド所有で埋め込み VirtualScroll の外 (スクロール行の
110
+ * インデックスを凍結数ぶんずらしてから渡すインデックスシフト方式)。帯行は描画列を全て具現化し
96
111
  * MAX_RENDERED_CELLS の打切り対象外 (予算分母に帯行数を算入) のため、帯を有界にするのは
97
112
  * この上限そのもの。違反は fail-fast の RangeError。
98
113
  */
99
114
  export const MAX_FROZEN_LEADING_ROWS = 128
100
115
 
101
116
  /**
102
- * Hard cap for `frozenTrailingCols` (v3.5.0 trailing-freeze plan §3). The trailing band is NOT
103
- * windowed — every trailing column materializes in every rendered row and is EXEMPT from the
104
- * MAX_RENDERED_CELLS truncation (the scroll-band budget is reduced by the trailing count
105
- * instead) — so the cap is what bounds the band: worst case 128 x MAX_RENDERED_ITEMS rows, and
106
- * W_T <= 128 x MAX_TRACK_SIZE = 2^25 (the measured layout wall; practical W_T <= viewport <<
107
- * 2^24). Violations fail fast with a RangeError.
117
+ * Hard cap for `frozenTrailingCols`. The trailing band is NOT windowed — every trailing column
118
+ * materializes in every rendered row and is EXEMPT from the MAX_RENDERED_CELLS truncation (the
119
+ * scroll-band budget is reduced by the trailing count instead) — so the cap is what bounds the
120
+ * band: worst case 128 x MAX_RENDERED_ITEMS rows, and W_T <= 128 x MAX_TRACK_SIZE = 2^25 (the
121
+ * measured layout wall; practical W_T <= viewport << 2^24). Violations fail fast with a
122
+ * RangeError.
108
123
  *
109
- * `frozenTrailingCols` の上限 (3.5.0 末尾凍結プラン §3)。末尾帯は窓化されず全描画行で具現化し、
110
- * かつ MAX_RENDERED_CELLS の打切り対象外 (代わりにスクロール帯予算を末尾数ぶん控除) のため、
111
- * 帯を有界にするのはこの上限そのもの: 最悪 128 × MAX_RENDERED_ITEMS 行、W_T ≤ 128 ×
112
- * MAX_TRACK_SIZE = 2^25 (実測壁ちょうど — 実運用は W_T ≤ viewport ≪ 2^24)。違反は fail-fast の
113
- * RangeError。
124
+ * `frozenTrailingCols` の上限。末尾帯は窓化されず全描画行で具現化し、かつ MAX_RENDERED_CELLS の
125
+ * 打切り対象外 (代わりにスクロール帯予算を末尾数ぶん控除) のため、帯を有界にするのはこの上限
126
+ * そのもの: 最悪 128 × MAX_RENDERED_ITEMS 行、W_T ≤ 128 × MAX_TRACK_SIZE = 2^25 (実測壁ちょうど —
127
+ * 実運用は W_T ≤ viewport ≪ 2^24)。違反は fail-fast の RangeError。
114
128
  */
115
129
  export const MAX_FROZEN_TRAILING_COLS = 128
116
130
 
117
131
  /**
118
- * Hard cap for `frozenTrailingRows` (v3.5.0 trailing-freeze plan §3). The trailing-row band is
119
- * grid-owned and OUT of the embedded VirtualScroll (the SECOND out-of-pane band, below the
120
- * pane): every band row materializes every rendered column and is exempt from
121
- * MAX_RENDERED_CELLS (the budget denominator counts band rows instead), so the cap bounds the
122
- * band itself. Violations fail fast with a RangeError.
132
+ * Hard cap for `frozenTrailingRows`. The trailing-row band is grid-owned and OUT of the embedded
133
+ * VirtualScroll (the SECOND out-of-pane band, below the pane): every band row materializes every
134
+ * rendered column and is exempt from MAX_RENDERED_CELLS (the budget denominator counts band rows
135
+ * instead), so the cap bounds the band itself. Violations fail fast with a RangeError.
123
136
  *
124
- * `frozenTrailingRows` の上限 (3.5.0 末尾凍結プラン §3)。末尾行帯はグリッド所有で埋め込み
125
- * VirtualScroll の外 (ペインの**下**の第 2 帯外バンド)。帯行は描画列を全て具現化し
126
- * MAX_RENDERED_CELLS の打切り対象外 (予算分母に帯行数を算入) のため、帯を有界にするのは
127
- * この上限そのもの。違反は fail-fast の RangeError。
137
+ * `frozenTrailingRows` の上限。末尾行帯はグリッド所有で埋め込み VirtualScroll の外 (ペインの**下**の
138
+ * 第 2 帯外バンド)。帯行は描画列を全て具現化し MAX_RENDERED_CELLS の打切り対象外 (予算分母に
139
+ * 帯行数を算入) のため、帯を有界にするのはこの上限そのもの。違反は fail-fast の RangeError。
128
140
  */
129
141
  export const MAX_FROZEN_TRAILING_ROWS = 128
130
142
 
@@ -236,10 +248,10 @@ export type VirtualGridBehaviorOptions = VirtualScrollBehaviorOptions & {
236
248
  */
237
249
  resetOnGetColWidthChange?: boolean
238
250
  /**
239
- * Explicit Fenwick baseValue for the column width tree (design §3.4): skips the stride
251
+ * Explicit Fenwick baseValue for the column width tree: skips the stride
240
252
  * sampling of getColWidth at construction, so unmaterialized columns are estimated at exactly
241
253
  * this width. Integer in [0, MAX_TRACK_SIZE] — RangeError otherwise (fail-fast, no clamp).
242
- * 列幅木の Fenwick baseValue の明示指定 (設計 §3.4): 構築時の getColWidth ストライド
254
+ * 列幅木の Fenwick baseValue の明示指定: 構築時の getColWidth ストライド
243
255
  * サンプリングを省略し、未具現化列をこの幅で見積もる。[0, MAX_TRACK_SIZE] の整数 —
244
256
  * 違反は RangeError (fail-fast、clamp なし)。
245
257
  */
@@ -311,14 +323,14 @@ export type VirtualGridProps<T> = {
311
323
  /** Restores both axes from a {row, col, offsetY, offsetX} anchor (px-restore is refuted row-side — anchors survive estimation-space changes). / 両軸を {row, col, offsetY, offsetX} アンカーから復元 (生 px 復元は行側で反証済み)。 */
312
324
  initialScrollAnchor?: { row: number; col: number; offsetY: number; offsetX: number }
313
325
  /**
314
- * Number of leading columns frozen at the left edge (design §8; default 0). The frozen band
326
+ * Number of leading columns frozen at the left edge (default 0). The frozen band
315
327
  * lives OUTSIDE the anchor machinery: tree coordinates [0, W_F) render as direct static
316
328
  * lefts (safe — W_F <= viewport << 2^24), the scroll band's window search starts at the
317
329
  * W_F offset, and its residual transform carries the -W_F origin-shift term. Non-negative
318
330
  * integer <= MAX_FROZEN_LEADING_COLS — RangeError otherwise (fail-fast); values beyond the
319
331
  * CURRENT colCount freeze every column (documented dynamic clamp, not a fallback — the
320
332
  * scroll band then reports the EMPTY window start > end and renders no scroll cells).
321
- * 左端に凍結する先頭列数 (設計 §8。既定 0)。凍結帯はアンカー機構の**外**: 木座標 [0, W_F)
333
+ * 左端に凍結する先頭列数 (既定 0)。凍結帯はアンカー機構の**外**: 木座標 [0, W_F)
322
334
  * を静的 left で直接描画 (W_F は上限 128 × 2^18 = 2^25 で有界 — 実運用は viewport ≪ 2^24)、
323
335
  * スクロール帯の窓探索は W_F
324
336
  * オフセット始まり、残差 transform は −W_F の原点シフト項を持つ。0 以上の整数かつ
@@ -327,14 +339,14 @@ export type VirtualGridProps<T> = {
327
339
  */
328
340
  frozenLeadingCols?: number
329
341
  /**
330
- * Number of leading rows pinned at the top (frozen-rows design plan — default 0). The band
342
+ * Number of leading rows pinned at the top (default 0). The band
331
343
  * is grid-owned and OUT of the embedded VirtualScroll: the scroll rows live in a SHIFTED
332
344
  * index space (itemCount = rowCount − R, getItem = i + R), so vy is the scroll-band
333
345
  * logical position and the vertical bar maps the band naturally. Non-negative integer <=
334
346
  * MAX_FROZEN_LEADING_ROWS — RangeError otherwise (fail-fast); values beyond the CURRENT
335
347
  * rowCount freeze every row (documented dynamic clamp — the scroll rows then report the
336
348
  * canonical EMPTY window start > end and render nothing).
337
- * 上端に凍結する先頭行数 (行凍結設計 — 既定 0)。帯はグリッド所有で埋め込み VirtualScroll
349
+ * 上端に凍結する先頭行数 (既定 0)。帯はグリッド所有で埋め込み VirtualScroll
338
350
  * の外: スクロール行は**シフト済みインデックス空間** (itemCount = rowCount − R、getItem =
339
351
  * i + R) に住み、vy はスクロール帯の論理位置・縦バーは帯を自然に写像する。0 以上の整数
340
352
  * かつ MAX_FROZEN_LEADING_ROWS 以下 — 違反は RangeError (fail-fast)。現在の rowCount を
@@ -343,14 +355,14 @@ export type VirtualGridProps<T> = {
343
355
  */
344
356
  frozenLeadingRows?: number
345
357
  /**
346
- * Number of trailing columns frozen at the right edge (v3.5.0 trailing-freeze plan §3;
347
- * default 0). Integer in [0, MAX_FROZEN_TRAILING_COLS] — RangeError otherwise (fail-fast).
358
+ * Number of trailing columns frozen at the right edge (default 0). Integer in
359
+ * [0, MAX_FROZEN_TRAILING_COLS] — RangeError otherwise (fail-fast).
348
360
  * Dynamic clamp (documented contract): min(T, colCount − effectiveFrozenCols) — the LEADING
349
361
  * band wins the count space. Trailing cells render in a right-anchored clip outside the
350
362
  * anchor machinery (band-local lefts); `--aqvs-grid-trailing-width` is written iff T > 0.
351
363
  * `scrollToCell` / `initialScrollAnchor` targeting a trailing column are horizontal no-ops
352
364
  * (always visible). T = 0 is structurally identical to the pre-trailing DOM.
353
- * 右端に凍結する末尾列数 (3.5.0 末尾凍結プラン §3。既定 0)。[0, MAX_FROZEN_TRAILING_COLS]
365
+ * 右端に凍結する末尾列数 (既定 0)。[0, MAX_FROZEN_TRAILING_COLS]
354
366
  * の整数 — 違反は RangeError (fail-fast)。動的クランプ (文書化契約): min(T, colCount −
355
367
  * effectiveFrozenCols) — カウント空間は**先頭が勝つ**。末尾セルはアンカー機構の外の
356
368
  * 右アンカークリップに帯ローカル left で描画し、`--aqvs-grid-trailing-width` は T > 0 の
@@ -359,19 +371,19 @@ export type VirtualGridProps<T> = {
359
371
  */
360
372
  frozenTrailingCols?: number
361
373
  /**
362
- * Number of trailing rows pinned at the bottom (v3.5.0 trailing-freeze plan §3; default 0).
374
+ * Number of trailing rows pinned at the bottom (default 0).
363
375
  * Integer in [0, MAX_FROZEN_TRAILING_ROWS] — RangeError otherwise (fail-fast). Dynamic
364
376
  * clamp (documented contract): min(T, rowCount − effectiveFrozenRows) — leading wins. The
365
377
  * band is grid-owned BELOW the pane; the embedded scroll rows shrink at the TAIL only
366
378
  * (itemCount = rowCount − R − T, getItem = i + R unchanged), so a runtime T change does NOT
367
- * remount the embedded VirtualScroll (ADR-23). `scrollToCell` / `initialScrollAnchor`
379
+ * remount the embedded VirtualScroll. `scrollToCell` / `initialScrollAnchor`
368
380
  * targeting a trailing row are vertical no-ops (always visible). T = 0 is structurally
369
381
  * identical to the pre-trailing DOM.
370
- * 下端に凍結する末尾行数 (3.5.0 末尾凍結プラン §3。既定 0)。[0, MAX_FROZEN_TRAILING_ROWS]
382
+ * 下端に凍結する末尾行数 (既定 0)。[0, MAX_FROZEN_TRAILING_ROWS]
371
383
  * の整数 — 違反は RangeError (fail-fast)。動的クランプ (文書化契約): min(T, rowCount −
372
384
  * effectiveFrozenRows) — 先頭が勝つ。帯はペインの**下**のグリッド所有バンドで、埋め込み
373
385
  * スクロール行は**末尾**だけが伸縮する (itemCount = rowCount − R − T、getItem = i + R
374
- * 不変) — T の実行時変更は埋め込み VirtualScroll を再マウントしない (ADR-23)。
386
+ * 不変) — T の実行時変更は埋め込み VirtualScroll を再マウントしない。
375
387
  * `scrollToCell` / `initialScrollAnchor` の末尾行狙いは縦 no-op (常時可視)。T = 0 は
376
388
  * DOM 構造まで従来と恒等。
377
389
  */
@@ -391,6 +403,19 @@ export type VirtualGridProps<T> = {
391
403
  /** TEST-ONLY seam: overrides MAX_RENDERED_CELLS so the truncation path is gateable without materializing 32k DOM nodes in jsdom. / テスト専用シーム: 打切り経路を jsdom で 32k ノード無しにゲート化するための上限上書き。 */
392
404
  __maxRenderedCells?: number
393
405
  behaviorOptions?: VirtualGridBehaviorOptions
406
+ /**
407
+ * Scrollbar options. The bar-local members — `width`, `enableThumbDrag`, `enableTrackClick`,
408
+ * `enableArrowButtons` and `enableArrowButtonTabStops` — reach BOTH bars alike (the embedded
409
+ * vertical bar and the horizontal bar), so no setting ever covers only half of the grid.
410
+ * `tapScrollCircleOptions` configures the grid's single two-axis circle (the per-bar circles are
411
+ * suppressed); the row-axis features `renderThumbOverlay` (fed with visible row indices) and
412
+ * `enableScrollToTopBottomButtons` (the Top / Bottom pills) belong to the embedded VirtualScroll.
413
+ * スクロールバー設定。バー固有のメンバー (`width` / `enableThumbDrag` / `enableTrackClick` /
414
+ * `enableArrowButtons` / `enableArrowButtonTabStops`) は内包の縦バーと横バーの両方へ同じく届き、
415
+ * 設定がグリッドの片側にだけ効くことは無い。`tapScrollCircleOptions` はグリッド唯一の 2 軸サークルの
416
+ * 設定 (バーごとのサークルは抑止)、行軸の機能である `renderThumbOverlay` (可視行インデックスを
417
+ * 受け取る) と `enableScrollToTopBottomButtons` (Top / Bottom ピル) は内包 VirtualScroll の所有。
418
+ */
394
419
  scrollBarOptions?: VirtualScrollScrollBarOptions
395
420
  liveRegion?: VirtualGridLiveRegionOptions
396
421
  /**
@@ -432,17 +457,17 @@ export type VirtualGridHandle = {
432
457
  * frozen column is a horizontal no-op that leaves any previously armed column anchor in
433
458
  * place (the visual status quo is preserved; manual horizontal input clears anchors as
434
459
  * usual); targeting a leading/trailing frozen row is a vertical no-op. Band membership is
435
- * judged at CALL time — a T change AFTER arming follows the ADR-23 clamp semantics: a
436
- * pending row anchor whose target row lands inside the trailing band after a T increase
437
- * clamps to the last scroll row. Overlap registration (ADR-19-1): when the leading +
438
- * trailing band extents exceed the viewport, occluded trailing cells cannot be scrolled
439
- * into view — the no-op is exact there too.
460
+ * judged at CALL time — a T change AFTER arming clamps instead of remounting: a pending row
461
+ * anchor whose target row lands inside the trailing band after a T increase clamps to the
462
+ * last scroll row. Overlapping bands: when the leading + trailing band extents exceed the
463
+ * viewport, occluded trailing cells cannot be scrolled into view — the no-op is exact there
464
+ * too.
440
465
  * セル狙いジャンプ。"nearest" は無し。先頭 / 末尾凍結列狙いの横成分は no-op で、既存の
441
466
  * 列アンカーはそのまま残る (視覚的現状維持 — 手動横入力が従来どおり解除する)。先頭 /
442
467
  * 末尾凍結行狙いの縦成分も no-op。帯所属の判定は**呼出し時** — 張った後の T 変更は
443
- * ADR-23 のクランプ意味論 (T 増加で末尾帯入りする行を狙った保留行アンカーは最終
444
- * スクロール行へクランプ) に従う。重複登記 (ADR-19-1): 先頭 + 末尾の帯寸がビューポートを
445
- * 超えるとき、隠れた末尾セルはどのスクロール位置でも可視化できない — no-op はそこでも正確。
468
+ * 再マウントせずクランプする (T 増加で末尾帯入りする行を狙った保留行アンカーは最終
469
+ * スクロール行へクランプ)。帯の重なり: 先頭 + 末尾の帯寸がビューポートを超えるとき、
470
+ * 隠れた末尾セルはどのスクロール位置でも可視化できない — no-op はそこでも正確。
446
471
  */
447
472
  scrollToCell(row: number, col: number, options?: { alignY?: "top" | "bottom" | "center"; alignX?: "start" | "end" | "center"; offsetX?: number; offsetY?: number }): void
448
473
  /** Synchronous-fresh position read (internal refs — safe mid-event; {-1,-1} pre-attach). / 同期・最新の位置読み (内部 ref — イベント中も安全。未接続 {-1,-1})。 */
@@ -493,16 +518,26 @@ export type VirtualGridHandle = {
493
518
  /**
494
519
  * Validates a track size returned by a consumer accessor: finite integer in [0, MAX_TRACK_SIZE].
495
520
  * Throws RangeError otherwise (fail-fast — a contract violation surfaces to the error boundary,
496
- * possibly during render since the width tree materializes lazily).
521
+ * possibly during render since the width tree materializes lazily). The message is complete on
522
+ * its own: it names the accessor, the index and the value, states the range, and says how to
523
+ * comply and why the cap exists.
497
524
  *
498
525
  * 消費側アクセサのトラックサイズ検証: [0, MAX_TRACK_SIZE] の有限整数。違反は RangeError
499
526
  * (fail-fast — 幅木は遅延具現化のため render 中の throw もあり得る契約違反として error
500
- * boundary へ)。
527
+ * boundary へ)。メッセージは単独で完結: アクセサ・インデックス・値、許容範囲、従い方と上限の理由。
528
+ *
529
+ * @param value - Size returned by the accessor (px) / アクセサが返したサイズ (px)
530
+ * @param axis - Track axis ("row" = getRowHeight, "col" = getColWidth) / トラックの軸
531
+ * @param index - Track index passed to the accessor / アクセサへ渡したトラックのインデックス
532
+ * @returns `value` unchanged / 検証済みの `value` (無変更)
533
+ * @throws {RangeError} When `value` is not an integer in [0, MAX_TRACK_SIZE] / [0, MAX_TRACK_SIZE] の整数でない場合
501
534
  */
502
535
  const validateTrackSize = (value: number, axis: "row" | "col", index: number): number => {
503
536
  if (!(Number.isInteger(value) && value >= 0 && value <= MAX_TRACK_SIZE)) {
537
+ const accessor = axis === "row" ? "getRowHeight" : "getColWidth"
538
+ const tracks = axis === "row" ? "rows" : "columns"
504
539
  throw new RangeError(
505
- `[VirtualGrid] ${axis === "row" ? "getRowHeight" : "getColWidth"}(${index}) returned ${value} — must be an integer in [0, ${MAX_TRACK_SIZE}] (0 = hidden). Oversized tracks would pierce the browser layout-coordinate wall (see the design plan §3.1/§6).`,
540
+ `[VirtualGrid] ${accessor}(${index}) returned ${value} — must be an integer in [0, ${MAX_TRACK_SIZE}] (0 = hidden). Round fractional sizes to whole pixels and split content larger than ${MAX_TRACK_SIZE} px across several ${tracks}: a larger track could push cell positions past the 2^25 px layout-coordinate limit of browsers.`,
506
541
  )
507
542
  }
508
543
  return value
@@ -829,7 +864,7 @@ const VirtualGridInner = <T,>(
829
864
  const frozenWidthRef = useRef(0)
830
865
 
831
866
  // ---- 末尾凍結列帯 (3.5.0 プラン §6.1-3) — 先頭側と同文の fail-fast。動的クランプは
832
- // min(T, colCount − 先頭実効) で**先頭がカウント空間で勝つ** (ADR-19-1 の文書化契約) ----
867
+ // min(T, colCount − 先頭実効) で**先頭がカウント空間で勝つ** (文書化した契約) ----
833
868
  if (!(Number.isInteger(frozenTrailingCols) && frozenTrailingCols >= 0 && frozenTrailingCols <= MAX_FROZEN_TRAILING_COLS)) {
834
869
  throw new RangeError(`[VirtualGrid] frozenTrailingCols must be an integer in [0, ${MAX_FROZEN_TRAILING_COLS}], received ${frozenTrailingCols}.`)
835
870
  }
@@ -1324,7 +1359,7 @@ const VirtualGridInner = <T,>(
1324
1359
  }
1325
1360
  }, [trailingWidth, effectiveTrailingCols])
1326
1361
 
1327
- // ---- 統合 2 軸タップサークル (v3.6.0 — ADR-27..32) ----
1362
+ // ---- 統合 2 軸タップサークル ----
1328
1363
  // 埋め込み VirtualScroll へ転送するオプションのメモ化フォーク (§3.1): tapScrollCircleOptions
1329
1364
  // だけ enabled: false を強制し、兄弟フィールド (width 等) は必ず生存させる。両バーの
1330
1365
  // サークルはグリッド側だけで抑止し、単体 VirtualScroll / ScrollPane / ScrollBar は触らない
@@ -1636,7 +1671,7 @@ const VirtualGridInner = <T,>(
1636
1671
  // 行直下の独立 subtree で木絶対 left の直接描画 — DOM は**前置** (論理列順 = 読み上げ /
1637
1672
  // フォーカス順)。末尾セルは右アンカー inner の帯ローカル left で**後置** (同じく論理列順)。
1638
1673
  // class はモードマーカー (描画トレイト) のまま、data-aqvs-frozen-row は F_eff > 0 のみ
1639
- // (ADR-25 — 末尾のみのグリッドが凍結列ゼロで属性を出すフック面の嘘の封じ)。
1674
+ // (末尾のみのグリッドが凍結列ゼロで属性を出すと、フック面が嘘をつくため)。
1640
1675
  // 描画順は自由: クリップが帯への侵入を構造的に遮断するため重なり自体が無い
1641
1676
  return (
1642
1677
  <div {...rowProps} data-aqvs-frozen-row={effectiveFrozenCols > 0 ? "" : undefined} className={twMerge("aqvs-grid-row aqvs-grid-row-frozen-host", rowProps?.className)} style={rowProps?.style}>
@@ -1970,13 +2005,19 @@ const VirtualGridInner = <T,>(
1970
2005
  pendingColAnchorRef.current = null
1971
2006
  return applyHxRef.current(typeof request === "function" ? request(previous ?? hxRef.current) : request)
1972
2007
  }}
2008
+ // バー固有の設定は縦バー (内包 VirtualScroll へ scrollBarOptions を全量透過) と同じ値を
2009
+ // 横バーへも渡す — 片方のバーにだけ効く設定面を作らない
2010
+ enableThumbDrag={scrollBarOptions?.enableThumbDrag}
2011
+ enableTrackClick={scrollBarOptions?.enableTrackClick}
2012
+ enableArrowButtons={scrollBarOptions?.enableArrowButtons}
2013
+ enableArrowButtonTabStops={scrollBarOptions?.enableArrowButtonTabStops}
1973
2014
  scrollBarWidth={scrollBarWidth}
1974
2015
  locale={locale}
1975
2016
  labels={labels}
1976
2017
  />
1977
2018
  </div>
1978
2019
  {tapCircleEnabled && gridScrollable && (
1979
- // 統合 2 軸タップサークル (v3.6.0 — ADR-30 の 3 アーム則): (C) 可視上限
2020
+ // 統合 2 軸タップサークルの 3 アーム配置則: (C) 可視上限
1980
2021
  // `100% − (sbw + size)` / (R) 到達性 `100% − sbw + off` / (Q) 遠象限床
1981
2022
  // `75% − size/2`。全アームが root 寸・sbw・size・オフセットのみに依存 —
1982
2023
  // 帯幅非依存 (凍結トグル不変)。wrapper は純配置で transition を持たない