@aiquants/virtualscroll 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,168 @@
2
2
 
3
3
  All notable changes to `@aiquants/virtualscroll` are documented here.
4
4
 
5
+ ## 2.5.0 (2026-08-20)
6
+
7
+ **ペイン外入力の橋渡し。** ホイールリスナーは `.aqvs-scroll-pane` にしか付かないため、消費側がペインの
8
+ 外に置いた要素 (列ヘッダー帯・固定フッター・横スクロールバー行など) はホイールの死角になっていました。
9
+ 明細表では列ヘッダーを仮想スクロールの外に固定するのが定石なので、この死角は必ず生じます。
10
+
11
+ ### Added
12
+
13
+ - **`VirtualScrollHandle.scrollBy(delta)`** — ペイン自身のホイールと**同一の意味**でデルタ分スクロールする。
14
+ float のまま相対加算し、スクロールアンカーを張らない (手動スクロール扱いなので既存の保留アンカーは
15
+ むしろ解除される)。適用後の**論理**位置を返す。
16
+ - **`VirtualScrollHandle.applyWheel(event)` / `ScrollPaneHandle.applyWheel(event)`** — ペイン自身の規則で
17
+ 1 つのホイールを適用し、消費したかを返す。**ホイールの適用規則 (軸分解・ズーム素通し・横成分の委譲先・
18
+ スクロール可否・慣性停止・速度倍率) はこの 1 つの関数に閉じており**、ペイン根のリスナーもこれを通る。
19
+ - **`useWheelBridge(target, options?)` / `WheelBridgeTarget`** — ペイン外の要素へペインと同じホイール
20
+ 意味論を与える公開フック。返す ref を対象要素へ渡すだけでよい。**ホイールの意味論に影響する設定は
21
+ 受け取らない** (すべて `applyWheel` の向こう側にあるため、ペイン内と帯の上で挙動が食い違うことが
22
+ 表現できない)。オプションは橋渡しの ON/OFF (`WheelBridgeOptions.enableBridge`、既定 `true`) のみ。引数は
23
+ `{ applyWheel }` を要求する構造的な型なので、`VirtualScroll` / `ScrollPane` のどちらのハンドルでも使える。
24
+ React 19 の `onWheel` は passive で `preventDefault()` が効かないため、フックが `{ passive: false }` で登録する。
25
+ 自前 ref との合成に対して冪等 (**要素ごと**の台帳を持ち、既に張ってある要素へは張り直さない)。
26
+ 台帳を単一スロットにすると、ある要素の ref が呼び直されただけで別の要素のリスナーが剥がれ、
27
+ ヘッダー帯とフッター帯で同じフックを使ったときに生きている帯が入れ替わる。
28
+ - **`horizontalKeyInputs`** (`readonly ("arrow" | "shift-arrow")[]`、既定 `[]`) と **`horizontalKeyStep`**
29
+ (既定 40px) — 行にフォーカスがあるとき `←/→` や `Shift + ←/→` を `onWheelHorizontal` へ横スクロール量
30
+ として流す。段階的な文字列ではなく**種別の配列**なのは、「素の矢印だけ横スクロール、`Shift + ←/→` は
31
+ 消費側の範囲選択に残す」という実在の要求が段階的な union では表現できないため (`behaviorOptions.pointerDragInputs`
32
+ と同じ作法)。既定が空なのは、行キーハンドラが capture フェーズに付くため、既定で `←/→` を消費すると
33
+ ツリーの展開/折りたたみ等を奪ってしまうから。消費するのは `preventDefault()` のみで
34
+ **伝播は止めない** (縦の矢印キーと同じ契約。`stopPropagation()` は `document` / `window` の bubble
35
+ リスナーごとイベントを消し、ホットキーライブラリ (`document.addEventListener("keydown", …)` 形式) や
36
+ キー入力のテレメトリ、消費側が一覧を囲む要素へ張った bubble ハンドラまで巻き添えにする)。
37
+ **行そのものにフォーカスがあるときだけ**働く (行の中の自作スライダー相当などから矢印を奪わない)。
38
+ テキスト選択中の `Shift + ←/→` は選択範囲の伸縮として消費側に残す (判定は `Range.intersectsNode` で
39
+ 行い、`Ctrl+A` のような行を跨ぐ選択も検出する)。`enableKeyboardNavigation: false` との併用は
40
+ 機能が丸ごと無効になるため `Logger.warn` で警告する。`horizontalKeyStep` は**有限かつ正**のみを受け付け、不正値は
41
+ `Logger.warn` のうえキーを消費しない (既定への暗黙の読み替えをしない)。
42
+ - ❗ `onWheelHorizontal` は**キーボード由来の横量も**受け取るようになった。名前は 2.x で公開済みのため
43
+ 据え置き、docstring と仕様書で意味を「横軸が N px 動いた」と明記した。
44
+
45
+ ### Fixed
46
+
47
+ - **`scrollTo` で連続入力を橋渡しすると 2 つの非対称が出る問題を、専用 API の追加と文書化で解消** —
48
+ `scrollTo` は行解決のため絶対位置を `Math.floor` するので 1px 未満のデルタが毎回消え (精密トラックパッドで
49
+ 一切動かない)、`scrollToIndex` 経由でアンカーを張るので以後のサイズ変化のたびに一覧が過去の位置へ
50
+ 戻っていた。アンカーを張ること自体はジャンプ API として正しい仕様なので、`scrollTo` の挙動は変えず
51
+ デルタ用の `scrollBy` を追加し、docstring と仕様書に使い分けと `-1` 番兵の落とし穴を明記した。
52
+ - **README の `tabIndex={-1}` の説明が誤っていた点を訂正** — `tabindex="-1"` はタブ順に**入れません**。
53
+ 実際の問題は「フォーカス可能になり VirtualScroll が実 DOM フォーカスを行へ移すこと」です。
54
+
55
+ ### Changed
56
+
57
+ - `ScrollPane` のホイール処理を `applyWheel` として切り出し、命令ハンドルへ公開した。挙動は 1 点だけ
58
+ 意図的に変わる: 「縦成分が無い」の判定を生の `deltaY` ではなく **px 換算後**の値で行う。
59
+ `deltaMode: DOM_DELTA_PAGE` かつ帯が 0 のとき (自己計測の初回コミット等) 換算後は 0 になり、
60
+ 従来は `preventDefault()` だけして 1px も動かず**祖先のスクロールも奪って**いた。適用できる量が
61
+ 無いイベントは消費しない、に揃えた。それ以外は不変 (既存の wheel 系テスト全件で回帰なしを確認)。
62
+ - 内部の軸分解 `resolveWheelAxes` は **ctrl 押下でもデルタを 0 に潰さなくなった** (事実だけを返す解決器へ)。
63
+ 「ズームは横取りしない」という判断は適用側が持つ。従来は解決器が 0 を詰めていたため適用側の ctrl 判定が
64
+ 到達不能な飾りになっており、「ctrl+shift+ホイールを横スクロールとして食う」退行を検出できなかった。
65
+ - コールバック参照の同期を `useEffect` から `useLayoutEffect` へ変更 (差し替え直後の入力が 1 フレーム古い
66
+ コールバックを読む窓を閉じる)。
67
+
68
+ - **`ScrollPane.applyWheel` は方針値を ref から読む** — lexical に閉じ込めると、消費側が保持したハンドルが
69
+ その時点の方針を凍結する (実測: 一覧が絞り込みでスクロール不能になった後も、保持済みハンドルは
70
+ `preventDefault` し続けてページのスクロールを殺す)。`VirtualScrollHandle.applyWheel` は毎回 ref を辿るため
71
+ 元から凍結せず、両者の非対称を解消した。
72
+
73
+ - **ペインの入れ子でホイールが 2 回適用される問題を修正** — 内側のペインが消費したホイールが
74
+ バブリングで外側のペインにも届き、1 ノッチで内外の一覧が両方動いていた (実測: どちらも 100px)。
75
+ `applyWheel` の内側に「**本パッケージが**消費した」印 (`WeakSet<WheelEvent>`) を持たせ、
76
+ 呼び出し元がどれでも二重適用を弾くようにした。❗ `event.defaultPrevented` では代用できない
77
+ (「誰かが止めた」と区別が付かず、ページ全体のスクロールロック中に一覧がまったく
78
+ スクロールできなくなる)。
79
+ - **行の中の要素にフォーカスがあるとき矢印キーを奪わないようにした** (縦横・PageUp/PageDown すべて)。
80
+ 従来は `role` で実装したスライダー相当などが keydown を**どちらのフェーズでも受け取れず**、
81
+ さらに縦矢印ではフォーカスが隣の行へ飛んでいた。❗ **これは縦キー操作の挙動変更である。**
82
+ 行の中の要素にフォーカスがある状態での `↑↓` / `PageUp` / `PageDown` は、行の走査ではなく
83
+ その要素へ届くようになった。
84
+ - **`useWheelBridge` の対象を差し替えても古い一覧を掴み続けないようにした** — ref コールバックの
85
+ identity を固定し、対象ハンドルを ref 越しに読む。クリーンアップを捨てる合成 ref では台帳が
86
+ 「既に張ってある」と判断して張り直さないため、差し替え後の一覧が永久に動かなかった。
87
+ - **警告の網羅と抑制** — `horizontalKeyInputs` を指定しつつ `onWheelHorizontal` が無い組み合わせも
88
+ 警告するようにした (`@aiquants/directory-tree` のように `onWheelHorizontal` を `Omit` で封じる
89
+ ラッパーで踏みやすい)。`horizontalKeyStep` の検証警告は keydown 毎ではなく prop 変化時に出す
90
+ (キーリピート中にコンソールが溢れるため)。
91
+
92
+ ### Notes
93
+
94
+ - `resolveWheelAxes` / `ResolvedWheelAxes` は**公開していません** (内部実装)。公開すると `16px` 行高・
95
+ 同値の扱い・4 フィールドの形が恒久的に凍結されます。消費側が必要とするのは「ペインと同じ意味論で
96
+ ホイールを流し込むこと」であり、それは `useWheelBridge` が満たします。
97
+ - 行は依然としてタブ順に入りません (`tabIndex={-1}`)。キーボードのみで横スクロールへ到達させたい消費側は
98
+ `handle.focusItemAtIndex(0)` などでフォーカスの入口を用意してください (ロービングタブインデックスの
99
+ 導入は全消費側のタブ順を変えるため見送り)。デモ (`/horizontal`) にその実演ボタンを追加しました。
100
+ - 既知の限界 (いずれも仕様書 §11.4 に記載): 行内でドラッグ選択/ダブルクリックすると選択が残り、それを
101
+ 畳むキーボード手段が無いため `Shift + ←/→` が効かないままになる / Shadow DOM 内の選択は検出できない /
102
+ 横スクロールは支援技術へ何も伝わらない / 縦スクロールでフォーカス行が仮想化で消えるとフォーカスが
103
+ `<body>` へ落ちる / 位置を量子化する消費側では `horizontalKeyStep` が量子未満だと 1px も動かない /
104
+ 連続スクロール速度は OS のキーリピート設定に依存する (環境で最大 2.8 倍程度の差)。
105
+ - **橋渡しはペインの外の要素にだけ張ってください。** 内側の要素はペイン自身のリスナーが既に拾っています
106
+ (二重適用は消費印が弾きますが、意味のない登録です)。
107
+ - **既知の限界 (未修正・既存)**: 中断されたトランジション。`sizeRef` / `wheelPolicyRef` は render 本体で
108
+ 代入するため、`startTransition` 内の絞り込みが Suspense で中断されると**コミットされなかった**
109
+ `contentSize` が残り、保留中は「スクロール可能な一覧なのにホイールが効かない」状態になります。
110
+ 両 ref を layout effect へ移すのが筋ですが、それだけではタップスクロールの走破が 1 コミット遅れて
111
+ 短くなる退行が実機 E2E で出ることを確認したため、本リリースでは見送りました (恒久対応には
112
+ クランプ経路の見直しが必要)。❗ 2 つの ref は**必ず同じタイミングで代入すること** — 片方だけを
113
+ layout effect にすると「消費はするがクランプは古い寸法」で一覧が先頭へ飛びます。
114
+ - **既知の限界 (未修正)**: ペインは端に達してもホイールを消費するため、ページへスクロールが連鎖しません
115
+ (いわゆるスクロールトラップ)。ネイティブのスクロール領域と異なる挙動で、修正には端判定の追加が必要です。
116
+ 本リリースの範囲外としました。
117
+ - 行の中の要素へフォーカスが入ると、キーボードだけで行へ戻る手段がありません (行はタブ順に入らないため)。
118
+ 消費側で `Escape` などの復帰口を用意してください。
119
+ - `Omit<VirtualScrollProps, "onWheelHorizontal">` で横軸を封じているラッパーは、`horizontalKeyInputs` と
120
+ `horizontalKeyStep` も同じ `Omit` に加えてください。この 2 つがトップレベルにあるのは、1 箇所の `Omit` で
121
+ 横軸のシーム全体を封じられるようにするためです。
122
+
123
+ ## 2.4.0 (2026-08-19)
124
+
125
+ **帯の自己計測 (`viewportSize` の optional 化)。** 自分の高さを自分で測れるコンポーネントが消費側に
126
+ 自分の高さを渡させていた責務の逆転を解消します。既存消費者は `viewportSize` を渡し続ければ**現行どおり**
127
+ 動作します (非破壊)。
128
+
129
+ ### Added
130
+
131
+ - **`viewportSize` を省略できるようになりました** (`VirtualScroll` / `ScrollPane`)。省略時はペインが
132
+ `.aqvs-scroll-pane-content` の実高さを `ResizeObserver` で追跡し、クリップ窓・最大スクロール位置・
133
+ つまみ写像・描画枚数・スクロール可否のすべてがそれに追従します。
134
+
135
+ ```tsx
136
+ // ホストが高さを所有し、VirtualScroll がそれを埋める
137
+ <div className="min-h-0 flex-1">
138
+ <VirtualScroll itemCount={n} getItem={getItem} getItemHeight={getItemHeight}>{renderRow}</VirtualScroll>
139
+ </div>
140
+ ```
141
+
142
+ ホスト側がルート要素の高さを確定させること (`h-full` / `flex-1` + `min-h-0` / 明示 px /
143
+ `position: absolute` など) が前提です。高さが auto のホストでは帯が 0 のままになるため警告します。
144
+ `viewportSize` を明示する正当なケースは「帯を計測ではなく**算出**で知っている」場合のみです
145
+ (例: ドロップダウンが件数 × 行高から高さを決める)。
146
+ - **`ScrollPane` に `onViewportSizeChange` を追加。** 自己計測した帯の高さを所有者へ通知します
147
+ (`viewportSize` 指定時は呼ばれません)。`VirtualScroll` は描画枚数とアライン計算にこの値を使います。
148
+
149
+ ### Fixed
150
+
151
+ - **`viewportSize` と実際の帯が食い違ったときに何も検知できなかった問題** — 食い違いは例外も視覚的な
152
+ 破綻も出さずに 5 つの導出値を壊します (はみ出し / 死に帯 / 末尾へ永久に到達できない /
153
+ つまみが視野外へ消える / ホイールとドラッグの無効化)。`Logger.warn` で 2 種類を通知するようにしました:
154
+ 明示値がルートの実帯と 1px を超えてずれている場合と、自己計測でレイアウト済みなのに帯が 0 の場合です
155
+ (非表示 = `offsetParent === null` は正常な未計測状態として黙ります)。同じ内容は繰り返し警告しません。
156
+
157
+ ### Changed
158
+
159
+ - **DOM 契約 (追加のみ)**: 自己計測モードのとき `.aqvs-scroll-pane` に `data-self-measured="true"` が付き、
160
+ コンテンツ要素のインライン `height` は**書かれなくなります** (CSS の `height: 100%` がレイアウトを所有)。
161
+ `viewportSize` を渡している既存消費者の DOM は不変です。
162
+ - **CSS (追加のみ)**: `.aqvs-scroll-pane[data-self-measured="true"] { height: 100% }` を追加しました。
163
+ ルート要素は元々高さを持たず、明示モードではコンテンツのインライン高さに押し広げられて決まっていた
164
+ ため、高さを書かない自己計測モードではこの規則が無いと帯が 0 に潰れます (実ブラウザで実測)。
165
+ 明示モードには適用しないため、既存消費者のルート要素の箱は変わりません。
166
+
5
167
  ## 2.3.0 (2026-08-19)
6
168
 
7
169
  DOM 契約が変わるため minor: `.aqvs-scroll-to-edge-overlay` は非表示中に `inert` を、
package/README.md CHANGED
@@ -82,7 +82,6 @@ function App() {
82
82
  itemCount={items.length}
83
83
  getItem={getItem}
84
84
  getItemHeight={getItemHeight}
85
- viewportSize={400}
86
85
  overscanCount={5}
87
86
  >
88
87
  {(item, index) => (
@@ -105,6 +104,31 @@ function App() {
105
104
  }
106
105
  ```
107
106
 
107
+ ## Sizing
108
+
109
+ `viewportSize` is **optional and best omitted**. When it is absent the component measures its own band
110
+ (the content element, via `ResizeObserver`) and every derived value follows automatically.
111
+
112
+ ```tsx
113
+ // The host owns the height; VirtualScroll fills it.
114
+ <div className="min-h-0 flex-1">
115
+ <VirtualScroll itemCount={n} getItem={getItem} getItemHeight={getItemHeight}>{renderRow}</VirtualScroll>
116
+ </div>
117
+ ```
118
+
119
+ **The host must establish a definite height** (`h-full`, `flex-1` + `min-h-0`, an explicit px height,
120
+ `position: absolute`, …). With an auto-height host the band stays 0 and the package logs a warning —
121
+ there is nothing to measure. In self-measured mode the root carries `data-self-measured="true"` and the
122
+ packaged rule `.aqvs-scroll-pane[data-self-measured="true"] { height: 100% }` stretches it to the host,
123
+ so the stylesheet import is required for this mode as well.
124
+
125
+ **Pass `viewportSize` only when the band is known by calculation, not measurement** — a dropdown whose
126
+ height comes from row count × row height, for example. If you pass a value that disagrees with the real
127
+ band by more than 1px, the package warns: the clip window, the max scroll position, the thumb mapping,
128
+ the rendered row count and scrollability all derive from it, so all five would be silently wrong (rows
129
+ spilling over the next element, a dead band at the bottom, an end you can never reach, a thumb that
130
+ leaves the viewport, or a list that ignores the wheel entirely).
131
+
108
132
  ## Horizontal Scrolling
109
133
 
110
134
  `VirtualScroll` virtualizes and scrolls the **vertical** axis only. To add a horizontal axis — e.g. a
@@ -133,7 +157,7 @@ function Grid() {
133
157
  return (
134
158
  <div style={{ ['--hx' as string]: `${hscroll}px`, display: 'flex', flexDirection: 'column' }}>
135
159
  <div style={{ height: 440 }}>
136
- <VirtualScroll itemCount={rows.length} getItem={getItem} getItemHeight={getItemHeight} viewportSize={440} onWheelHorizontal={onWheelHorizontal}>
160
+ <VirtualScroll itemCount={rows.length} getItem={getItem} getItemHeight={getItemHeight} onWheelHorizontal={onWheelHorizontal}>
137
161
  {(row) => (
138
162
  <div style={{ display: 'flex' }}>
139
163
  <div style={{ width: 180, flexShrink: 0 }}>{row.label}{/* frozen column */}</div>
@@ -159,6 +183,123 @@ function Grid() {
159
183
  A runnable version lives in the demo at **`/horizontal`** (`pnpm demo:dev`), and
160
184
  [`@aiquants/directory-tree`](../directory-tree)'s TreeGrid mode uses this exact pattern in production.
161
185
 
186
+ ## Bridging input from outside the pane
187
+
188
+ The wheel listener lives on `.aqvs-scroll-pane` only, so anything you render **outside** it — a frozen
189
+ column header, a sticky footer, the horizontal scrollbar row, an empty-state panel — is a wheel dead
190
+ zone. Data tables pin the header outside the virtual list by convention, so this gap is the norm, not
191
+ an edge case.
192
+
193
+ Use `useWheelBridge`: it gives that element the pane's own wheel semantics.
194
+
195
+ ```tsx
196
+ import { useRef } from 'react'
197
+ import { useWheelBridge, VirtualScroll, type VirtualScrollHandle } from '@aiquants/virtualscroll'
198
+
199
+ const listRef = useRef<VirtualScrollHandle>(null)
200
+ const headerRef = useWheelBridge(listRef)
201
+
202
+ <div ref={headerRef}>frozen header — outside the pane</div>
203
+ <VirtualScroll ref={listRef} onWheelHorizontal={setScrollX} ...>{renderRow}</VirtualScroll>
204
+ ```
205
+
206
+ **The hook takes no configuration that affects wheel semantics, and that is the point.** Wheel speed, the horizontal sink, whether
207
+ the list can scroll, inertia and axis resolution all live behind the handle, in one function
208
+ (`applyWheel`) that the pane's own listener goes through too. Give the bridge its own settings and the
209
+ same grid ends up scrolling at one speed over the rows and another over the header — we measured exactly that
210
+ — 30px vs 10px for one wheel tick at `wheelSpeedMultiplier: 3` — in a work-in-progress build that did give
211
+ the bridge its own settings. With a single home for the rules, that
212
+ mismatch is not expressible. The hook's job is `{ passive: false }` registration, teardown, and the
213
+ `enableBridge` on/off switch — nothing else. Deciding whether an event was already applied belongs to
214
+ `applyWheel`, which tracks the events **this package** consumed (see below).
215
+
216
+ Its one option is `enableBridge` (default `true`) — a plain on/off switch, not a semantic knob. Pass
217
+ `{ enableBridge: false }` to stop bridging without unmounting the element:
218
+
219
+ ```tsx
220
+ const headerRef = useWheelBridge(listRef, { enableBridge: !isEditing })
221
+ ```
222
+
223
+ It works with `ScrollPane` directly too: the parameter is typed against `WheelBridgeTarget`
224
+ (`{ applyWheel }`), which both `VirtualScrollHandle` and `ScrollPaneHandle` satisfy.
225
+
226
+ Composing with your own ref is safe — the hook keeps a per-element ledger and skips re-attaching to an
227
+ element it already holds, so the common inline merge cannot pile up listeners (without it, four
228
+ re-renders leave five live wheel listeners on one element). Return the hook's value so React 19 can run
229
+ its cleanup on unmount:
230
+
231
+ ```tsx
232
+ <div ref={(node) => { myRef.current = node; return headerRef(node) }} />
233
+ ```
234
+
235
+ Dropping that `return` still works — the ledger keeps it correct — but the listener then stays on the
236
+ element until the element itself is garbage-collected.
237
+
238
+ The ledger is keyed **per element**, so one hook can serve several bands (a header and a footer) without
239
+ them fighting over a single slot.
240
+
241
+ ⚠️ **The pane does not chain scrolling to the page at its edges.** It consumes the wheel even when
242
+ already at position 0 or at the maximum, so a user wheeling down a long page has to move the pointer off
243
+ the list to continue. This differs from a native scroll container and is a known limitation.
244
+
245
+ ⚠️ **`onWheel` cannot do this.** React 19 registers `onWheel` as a passive listener, so `preventDefault()`
246
+ is ignored and the page scrolls instead. The hook attaches a `{ passive: false }` listener itself.
247
+
248
+ ⚠️ **Only bridge elements OUTSIDE the pane.** Anything inside the pane is already covered by the pane's
249
+ own listener, so bridging it is a redundant registration — the consumed-event mark keeps it from being
250
+ applied twice, but the extra listener is pure overhead on the wheel hot path.
251
+
252
+ ⚠️ **Do not bridge with `scrollTo`.** It is a *jump* API: it floors the absolute position (sub-pixel
253
+ trackpad deltas vanish entirely) and pins a scroll anchor (every later size change re-pins the list to
254
+ that row, so filtering a list makes it jump back to where it used to be). `scrollBy` is the delta API and
255
+ takes the pane's own path. Also never write `scrollTo(handle.getScrollPosition() + delta)` — the getter
256
+ returns the `-1` sentinel while the pane is unconnected.
257
+
258
+ ### Horizontal scrolling from the keyboard
259
+
260
+ With `onWheelHorizontal` wired **and `behaviorOptions.enableKeyboardNavigation` left on** (the default),
261
+ `horizontalKeyInputs` lets a focused row emit horizontal deltas too:
262
+
263
+ | Value | Keys | Use it when |
264
+ | --- | --- | --- |
265
+ | `[]` (default) | — | Never steal the row's own arrow handling |
266
+ | `["shift-arrow"]` | `Shift + ←/→` | Safe alongside tree expand/collapse and grid cell navigation |
267
+ | `["arrow"]` | `←/→` | Grids that bind `Shift + ←/→` to range selection |
268
+ | `["arrow", "shift-arrow"]` | both | Rows that use no arrow keys at all |
269
+
270
+ It is an array rather than a graduated string because `["arrow"]` — plain arrows scroll, `Shift + ←/→`
271
+ stays with your range selection — is a real requirement that `"none" | "shift-arrows" | "arrows"`
272
+ cannot express, since `"arrows"` subsumes `"shift-arrows"`. `behaviorOptions.pointerDragInputs` takes a
273
+ type array for the same reason.
274
+
275
+ The default is empty on purpose: the package's row key handler runs in the **capture** phase, ahead of
276
+ your row's own handler. When it consumes a key it calls `preventDefault()` only and deliberately does
277
+ **not** stop propagation — `stopPropagation()` would delete the event from `document` and `window`
278
+ bubble listeners too, breaking global hotkey libraries and keydown telemetry. Check `defaultPrevented`
279
+ in your own handler, exactly as you would for the vertical arrows.
280
+
281
+ It also fires only when the row **itself** is the event target. Focus sitting on something inside the
282
+ row — a `role="slider"` cell, a link, a nested `overflow-x:auto` region — is never hijacked, so you do
283
+ not have to avoid `["arrow"]` just because your rows contain arrow-key widgets.
284
+
285
+ `Shift + ←/→` is left alone while a non-collapsed text selection sits inside the row, so keyboard users
286
+ can still extend a selection. `horizontalKeyStep` (default 40) must be finite and positive; anything
287
+ else logs a warning and leaves the key untouched rather than silently substituting the default.
288
+
289
+ ⚠️ **This does nothing when `enableKeyboardNavigation` is `false`** — the row key handler is what reads
290
+ these keys, and turning navigation off removes it. The package emits a `Logger.warn` when it sees that
291
+ combination. Note the next section tells `listbox` / `tree` / `grid` authors to turn navigation off; if
292
+ you follow both, your arrow keys are dead. Pick one.
293
+
294
+ ⚠️ **Rows are not in the page tab sequence** (`tabIndex={-1}`). This feature fires only while a row has
295
+ focus, so if you want keyboard-only users to reach it, give them an entry point — for example call
296
+ `handle.focusItemAtIndex(0)` when the list receives focus.
297
+
298
+ ⚠️ **Wrappers that own the horizontal axis should seal these too.** If you re-export VirtualScroll with
299
+ `Omit<VirtualScrollProps, 'onWheelHorizontal'>`, add `horizontalKeyInputs` and `horizontalKeyStep` to
300
+ that same `Omit`. They sit at the top level, next to `onWheelHorizontal`, precisely so one `Omit` closes
301
+ the whole horizontal seam.
302
+
162
303
  ## Composite widget roles (listbox / tree / grid)
163
304
 
164
305
  Building a `listbox`, `tree` or `grid` on top of VirtualScroll? Put the role on **`contentProps`**,
@@ -171,8 +312,10 @@ not on a wrapper around `<VirtualScroll>`:
171
312
  getItemHeight={() => 36}
172
313
  viewportSize={300}
173
314
  contentProps={{ id: listboxId, role: "listbox", "aria-label": "Warehouses" }}
174
- // Required: with keyboard navigation ON, every row wrapper gets tabIndex={-1}, which puts the
175
- // popup in the page tab sequence AND makes the wrappers opaque to the owned-element relationship.
315
+ // Required: with keyboard navigation ON, every row wrapper gets tabIndex={-1}. That does not add a
316
+ // tab stop, but it makes the wrappers focusable VirtualScroll then moves real DOM focus onto a row
317
+ // — and it makes them opaque to the owned-element relationship.
318
+ // ⚠️ This also disables `horizontalKeyInputs` — the row key handler goes away with it.
176
319
  behaviorOptions={{ enableKeyboardNavigation: false }}>
177
320
  {(option) => <div role="option" aria-selected={false}>{option.label}</div>}
178
321
  </VirtualScroll>
@@ -204,14 +347,16 @@ exists in the DOM.
204
347
  | `itemCount` | `number` | ✅ | Total number of items |
205
348
  | `getItem` | `(index: number) => T` | ✅ | Function to get item at index |
206
349
  | `getItemHeight` | `(index: number) => number` | ✅ | Function to get item height |
207
- | `viewportSize` | `number` | | Height of the visible area |
350
+ | `viewportSize` | `number` | | Height of the visible band. **Omit it** — the component then measures its host with a `ResizeObserver`. Pass it only when you know the band by calculation rather than measurement (e.g. a dropdown sized from row count × row height). See [Sizing](#sizing) |
208
351
  | `overscanCount` | `number` | ❌ | Number of items to render outside viewport (default: 15) |
209
352
  | `className` | `string` | ❌ | CSS class name. Lands on the scroll root (`.aqvs-scroll-pane`) — see [Custom Scrollbar Styling](#custom-scrollbar-styling) |
210
353
  | `getItemKey` | `(index: number) => React.Key` | ❌ | Stable React key per index (defaults to the index) |
211
354
  | `testId` | `string` | ❌ | Emitted as `data-testid` on the scroll root. DOM hooks should use `data-*`, never class selectors |
212
355
  | `onScroll` | `(position: number, totalHeight: number) => void` | ❌ | Scroll event handler |
213
356
  | `onRangeChange` | `(range: VirtualScrollRange) => void` | ❌ | Range change handler |
214
- | `onWheelHorizontal` | `(deltaX: number) => void` | ❌ | Opt-in horizontal wheel delegation. When set, horizontal-dominant wheel / trackpad gestures (and shift+wheel) are delegated to this handler so the parent can implement horizontal scrolling. When omitted, horizontal gestures and shift+wheel bypass vertical scrolling so parent native scrolling works. See [Horizontal Scrolling](#horizontal-scrolling). |
357
+ | `onWheelHorizontal` | `(deltaX: number) => void` | ❌ | Opt-in horizontal delegation. Horizontal-dominant wheel / trackpad gestures (and shift+wheel) are delegated to this handler so the parent can implement horizontal scrolling. ⚠️ **Keyboard deltas arrive here too** when `horizontalKeyInputs` is set. When omitted, horizontal gestures bypass vertical scrolling so parent native scrolling works. See [Horizontal Scrolling](#horizontal-scrolling). |
358
+ | `horizontalKeyInputs` | `readonly ("arrow" \| "shift-arrow")[]` | ❌ | Keyboard gestures that emit a horizontal delta through `onWheelHorizontal` (default: `[]`, so row-level arrow handling is never stolen). Fires only when the row itself is the event target. ⚠️ Requires `behaviorOptions.enableKeyboardNavigation` (default `true`) — with it off the rows have no key handler at all and this prop does nothing (the package warns) |
359
+ | `horizontalKeyStep` | `number` | ❌ | Pixels per horizontal arrow press (default: 40). Must be finite and positive; anything else warns and leaves the key untouched |
215
360
  | `background` | `ReactNode` | ❌ | Background element |
216
361
  | `initialScrollIndex` | `number` | ❌ | Initial scroll index |
217
362
  | `initialScrollOffset` | `number` | ❌ | Initial scroll offset (**logical** px). Not suitable for restoring positions of variable-height lists across remounts — use `initialScrollAnchor` |
@@ -241,7 +386,7 @@ exists in the DOM.
241
386
  | --- | --- | --- |
242
387
  | `enablePointerDrag` | `boolean` | Enable dragging the content area to scroll (default: true). ⚠️ Setting this to `false` removes the only way to scroll on touch devices — the pane is transform-based and has no native scroller. Use `pointerDragInputs` to exclude a single pointer type instead. |
243
388
  | `pointerDragInputs` | `readonly ("mouse" \| "pen" \| "touch")[]` | Pointer types allowed to drag-scroll the content area (default: all three). `touch-action: none` is applied only when `"touch"` or `"pen"` is included. |
244
- | `enableKeyboardNavigation` | `boolean` | Enable keyboard navigation (default: true) |
389
+ | `enableKeyboardNavigation` | `boolean` | Enable keyboard navigation (default: true). Arrow / Page keys move row focus. ⚠️ They fire **only when the row wrapper itself is the event target** — focus inside a row (a `role="slider"` cell, a link, a nested scroll region) keeps its own keys |
245
390
  | `wheelSpeedMultiplier` | `number` | Multiplier for mouse wheel scrolling speed (default: 1) |
246
391
  | `inertiaOptions` | `ScrollPaneInertiaOptions` | Physics tuning for drag inertia |
247
392
  | `clipItemHeight` | `boolean` | Whether to clip item height (default: false) |
@@ -251,11 +396,13 @@ exists in the DOM.
251
396
 
252
397
  | Method | Type | Description |
253
398
  | --- | --- | --- |
254
- | `scrollTo` | `(position: number \| ((prev: number) => number)) => number` | Scroll to a **logical** position (updater receives the current logical position). Returns the applied **logical** position (2.0.0) |
399
+ | `scrollTo` | `(position: number \| ((prev: number) => number)) => number` | **Jump** to a **logical** position (updater receives the current logical position). Returns the applied **logical** position (2.0.0). ⚠️ Not for bridging continuous input — see [Bridging input from outside the pane](#bridging-input-from-outside-the-pane) |
400
+ | `scrollBy` | `(delta: number) => number` | Scroll **by a delta** with the pane's own wheel semantics (float accumulation, no anchor). Use this — not `scrollTo` — to bridge continuous input from outside the pane. Returns the applied **logical** position |
401
+ | `applyWheel` | `(event: WheelEvent) => boolean` | Apply one wheel event with the pane's own rules; returns whether it was consumed. The single entry point for wheel input originating outside the pane — normally reached through `useWheelBridge` |
255
402
  | `scrollToIndex` | `(index: number, options?: { align?: "top" \| "bottom" \| "center"; offset?: number }) => void` | Scroll to specific item index with optional alignment and offset |
256
403
  | `getScrollPosition` | `() => number` | Get current **logical** scroll position (2.0.0; `-1` when the pane is not connected) |
257
- | `getContentSize` | `() => number` | Get total content size (insets included) |
258
- | `getViewportSize` | `() => number` | Get viewport size |
404
+ | `getContentSize` | `() => number` | Get total content size (insets included). Returns the `-1` sentinel while the pane is unconnected |
405
+ | `getViewportSize` | `() => number` | Get viewport size. Returns the `-1` sentinel while the pane is unconnected |
259
406
  | `focusItemAtIndex` | `(index: number, options?: { ensureVisible?: boolean }) => void` | Focus item at specific index |
260
407
  | `getRange` | `() => VirtualScrollRange` | Get current range information (updated one render behind) |
261
408
  | `getScrollAnchor` | `() => { index: number; offsetPx: number } \| null` | Capture the current top visible row anchor for exact restore via `initialScrollAnchor` (`null` when `itemCount` is 0) |
@@ -366,7 +513,6 @@ function AdvancedExample() {
366
513
  itemCount={items.length}
367
514
  getItem={getItem}
368
515
  getItemHeight={getItemHeight}
369
- viewportSize={400}
370
516
  onRangeChange={handleRangeChange}
371
517
  scrollBarOptions={{
372
518
  renderThumbOverlay: renderThumbOverlay
@@ -476,7 +622,6 @@ export function UltraFastExample() {
476
622
  itemCount={items.length}
477
623
  getItem={(index) => items[index]}
478
624
  getItemHeight={() => 42}
479
- viewportSize={480}
480
625
  scrollBarOptions={{
481
626
  tapScrollCircleOptions: {
482
627
  maxSpeedMultiplier: 80, // Optional: override adaptive speed when needed
@@ -19,8 +19,32 @@ export type ScrollPaneProps = {
19
19
  children: (scrollPosition: number) => React.ReactNode;
20
20
  /** The total size of the content. / コンテンツの総サイズ。 */
21
21
  contentSize: number;
22
- /** The size of the visible area. / 表示領域のサイズ。 */
23
- viewportSize: number;
22
+ /**
23
+ * Size of the visible band (px). Omit it to let the pane measure its own band.
24
+ * 可視帯の高さ (px)。省略するとペインが自分の帯を計測する。
25
+ *
26
+ * ❗ **省略が既定の使い方である。** この値は帯のクリップ窓・最大スクロール位置・つまみ写像・
27
+ * 描画枚数・スクロール可否のすべての基準であり、実際の帯と食い違うと 5 通りの壊れ方が
28
+ * **例外も警告も出さずに**発生する (はみ出し / 死に帯 / 末尾へ到達不能 / つまみが視野外へ /
29
+ * ホイールとドラッグの無効化)。自分で測れるものを消費側に渡させると、同じ正解を各画面で
30
+ * 書き直すことになり、実際に推測値や定数が代入される。
31
+ *
32
+ * 省略時は `.aqvs-scroll-pane-content` の実高さ (CSS の `height: 100%` が決める) を
33
+ * `ResizeObserver` で追跡する。**ホスト側がルート要素の高さを確定させること**が前提で
34
+ * (`h-full` / `flex-1` + `min-h-0` / 明示 px / `position: absolute` など)、高さが auto の
35
+ * ホストでは帯が 0 のままになる (開発ビルドで警告する)。
36
+ *
37
+ * 明示する正当なケースは「帯の高さを計測ではなく算出で知っている」場合のみである
38
+ * (例: ドロップダウンが件数 × 行高から高さを決める)。
39
+ */
40
+ viewportSize?: number;
41
+ /**
42
+ * Notified when the self-measured viewport size changes (not called while `viewportSize` is given).
43
+ * 自己計測したビューポート高さが変化したときに通知する (`viewportSize` 指定時は呼ばれない)。
44
+ *
45
+ * 帯の数値を必要とする親 (`VirtualScroll` は描画枚数とアライン計算に使う) がこの口で受け取る。
46
+ */
47
+ onViewportSizeChange?: (viewportSize: number) => void;
24
48
  /** The width of the scrollbar. / スクロールバーの幅。 */
25
49
  scrollBarWidth?: number;
26
50
  /** Whether grabbing the scrollbar thumb is allowed. / スクロールバーのつまみ操作を許可するかどうか。 */
@@ -128,6 +152,16 @@ export type ScrollPaneHandle = {
128
152
  getScrollPosition: () => number;
129
153
  getContentSize: () => number;
130
154
  getViewportSize: () => number;
155
+ /**
156
+ * Applies one wheel event with the pane's own rules; returns whether it was consumed.
157
+ * ペイン自身の規則で 1 つのホイールイベントを適用し、消費したかどうかを返す。
158
+ *
159
+ * ペインの**外**に置いた要素 (列ヘッダー帯など) のホイールを、ペイン内と寸分違わぬ意味論で
160
+ * 流し込むための唯一の口。軸分解・速度倍率・スクロール可否・横委譲先・慣性停止のすべてが
161
+ * ペイン側の 1 箇所で決まるため、消費側が規則を書き直したり食い違わせたりできない。
162
+ * 直接使うより `useWheelBridge` (passive:false 登録と後始末込み) を推奨する。
163
+ */
164
+ applyWheel: (event: WheelEvent) => boolean;
131
165
  };
132
166
  /**
133
167
  * A component that provides a scrollable view with a custom scrollbar.
@@ -19,8 +19,32 @@ export type ScrollPaneProps = {
19
19
  children: (scrollPosition: number) => React.ReactNode;
20
20
  /** The total size of the content. / コンテンツの総サイズ。 */
21
21
  contentSize: number;
22
- /** The size of the visible area. / 表示領域のサイズ。 */
23
- viewportSize: number;
22
+ /**
23
+ * Size of the visible band (px). Omit it to let the pane measure its own band.
24
+ * 可視帯の高さ (px)。省略するとペインが自分の帯を計測する。
25
+ *
26
+ * ❗ **省略が既定の使い方である。** この値は帯のクリップ窓・最大スクロール位置・つまみ写像・
27
+ * 描画枚数・スクロール可否のすべての基準であり、実際の帯と食い違うと 5 通りの壊れ方が
28
+ * **例外も警告も出さずに**発生する (はみ出し / 死に帯 / 末尾へ到達不能 / つまみが視野外へ /
29
+ * ホイールとドラッグの無効化)。自分で測れるものを消費側に渡させると、同じ正解を各画面で
30
+ * 書き直すことになり、実際に推測値や定数が代入される。
31
+ *
32
+ * 省略時は `.aqvs-scroll-pane-content` の実高さ (CSS の `height: 100%` が決める) を
33
+ * `ResizeObserver` で追跡する。**ホスト側がルート要素の高さを確定させること**が前提で
34
+ * (`h-full` / `flex-1` + `min-h-0` / 明示 px / `position: absolute` など)、高さが auto の
35
+ * ホストでは帯が 0 のままになる (開発ビルドで警告する)。
36
+ *
37
+ * 明示する正当なケースは「帯の高さを計測ではなく算出で知っている」場合のみである
38
+ * (例: ドロップダウンが件数 × 行高から高さを決める)。
39
+ */
40
+ viewportSize?: number;
41
+ /**
42
+ * Notified when the self-measured viewport size changes (not called while `viewportSize` is given).
43
+ * 自己計測したビューポート高さが変化したときに通知する (`viewportSize` 指定時は呼ばれない)。
44
+ *
45
+ * 帯の数値を必要とする親 (`VirtualScroll` は描画枚数とアライン計算に使う) がこの口で受け取る。
46
+ */
47
+ onViewportSizeChange?: (viewportSize: number) => void;
24
48
  /** The width of the scrollbar. / スクロールバーの幅。 */
25
49
  scrollBarWidth?: number;
26
50
  /** Whether grabbing the scrollbar thumb is allowed. / スクロールバーのつまみ操作を許可するかどうか。 */
@@ -128,6 +152,16 @@ export type ScrollPaneHandle = {
128
152
  getScrollPosition: () => number;
129
153
  getContentSize: () => number;
130
154
  getViewportSize: () => number;
155
+ /**
156
+ * Applies one wheel event with the pane's own rules; returns whether it was consumed.
157
+ * ペイン自身の規則で 1 つのホイールイベントを適用し、消費したかどうかを返す。
158
+ *
159
+ * ペインの**外**に置いた要素 (列ヘッダー帯など) のホイールを、ペイン内と寸分違わぬ意味論で
160
+ * 流し込むための唯一の口。軸分解・速度倍率・スクロール可否・横委譲先・慣性停止のすべてが
161
+ * ペイン側の 1 箇所で決まるため、消費側が規則を書き直したり食い違わせたりできない。
162
+ * 直接使うより `useWheelBridge` (passive:false 登録と後始末込み) を推奨する。
163
+ */
164
+ applyWheel: (event: WheelEvent) => boolean;
131
165
  };
132
166
  /**
133
167
  * A component that provides a scrollable view with a custom scrollbar.
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollPane.d.ts","sourceRoot":"","sources":["../src/ScrollPane.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ScrollPane.d.ts","sourceRoot":"","sources":["../src/ScrollPane.tsx"],"names":[],"mappings":"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"}