@aiquants/virtualscroll 2.7.0 → 3.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/virtualscroll",
3
- "version": "2.7.0",
3
+ "version": "3.0.0",
4
4
  "description": "High-performance virtual scrolling component for React with variable item heights",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -253,15 +253,21 @@ export type VirtualScrollProps<T> = {
253
253
  * `onWheelHorizontal` へ横スクロール量を流すキーボード操作の種別 (既定: 無効)。
254
254
  *
255
255
  * - `[]` / 未指定 (既定): 横キーボードスクロールを行わない。
256
- * - `["shift-arrow"]`: `Shift + ←/→` のみ。木の展開/折りたたみ (`←/→`) やグリッドのセル移動と
257
- * 衝突しないため、既存の行 UI を持つ消費側でも安全に有効化できる。
258
- * - `["arrow"]`: 素の `←/→` のみ。`Shift + ←/→` を選択範囲の拡張に使うグリッド向け。
259
- * - `["arrow", "shift-arrow"]`: 両方。
256
+ * - `["arrow"]`: 素の `←/→` のみ。
260
257
  *
261
- * ❗ **配列なのは 4 状態が独立に必要だからである。** `"none" | "shift-arrows" | "arrows"` のような
262
- * 段階的な文字列にすると `"arrows"` が `"shift-arrows"` を含んでしまい、「素の矢印だけ横スクロール、
263
- * `Shift + ←/→` は消費側の範囲選択に残す」(Excel / データグリッドの標準) が**表現できない**。
264
- * 同じ理由で種別配列を採るのが `pointerDragInputs` であり、本パッケージの既存の作法に揃えてある。
258
+ * ❗ **`Shift + ←/→` は常に消費しない (3.0.0 で `"shift-arrow"` 入力を撤去)。** ブラウザ標準では
259
+ * Shift+矢印は選択範囲の伸縮であり、横取りすると行内テキストを選び直す手段がキーボードから消える。
260
+ * 2.x では「選択があるときだけ譲る」検出 (`getSelection` の交差判定) で共存させていたが、
261
+ * (a) Shadow DOM 内では選択が可搬に検出できない (Chromium の `window.getSelection()` は shadow root
262
+ * 内部を見せず、内部選択は非標準 API でしか取れない)、(b) `Ctrl+A` の全選択では全行が選択と交差して
263
+ * 横キーボードスクロールが選択解除まで全面ロックアウトする、という構造的欠陥が残った。検出の
264
+ * 精度を上げる案はどれも別の正しいユーザー意図を誤判定するため、機能ごと撤去して Shift 側を
265
+ * 無条件でブラウザへ返す。横スクロールの Shift 系入力が必要なら `shift+ホイール` が引き続き使える
266
+ * (`resolveWheelAxes` の軸規則。こちらは選択と衝突しない)。
267
+ *
268
+ * ❗ **語彙が 1 つでも配列なのは意図的である。** 種別配列は `pointerDragInputs` と同じ本パッケージの
269
+ * 作法であり、将来の入力種別追加が型の破壊なしにできる。boolean へ畳むと prop 名が変わり、横軸を
270
+ * `Omit<>` で封じているラッパー (例: `@aiquants/directory-tree`) の封印リストまで連鎖破壊する。
265
271
  *
266
272
  * ❗ **既定が無効なのは、行ハンドラを奪わないためである。** 本パッケージの行キーハンドラは
267
273
  * capture フェーズに付くため、消費側の行 (bubble) より先に走る。既定で `←/→` を消費すると
@@ -294,7 +300,7 @@ export type VirtualScrollProps<T> = {
294
300
  * `Omit<VirtualScrollProps, "onWheelHorizontal">` で横軸を封じている) が `behaviorOptions` を
295
301
  * そのまま素通しするため、封じたはずのシームへ横から到達できてしまう。
296
302
  */
297
- horizontalKeyInputs?: readonly ("arrow" | "shift-arrow")[]
303
+ horizontalKeyInputs?: readonly "arrow"[]
298
304
  /**
299
305
  * Pixels emitted per horizontal arrow key press (default: 40, matching browser arrow scrolling).
300
306
  * 横矢印キー 1 回あたりの移動量 (px。既定 40 = ブラウザの矢印スクロール相当)。
@@ -390,32 +396,6 @@ const ANCHOR_REBASE_DISTANCE = 1_048_576 // 2^20 px
390
396
  */
391
397
  const DEFAULT_HORIZONTAL_KEY_STEP = 40
392
398
 
393
- /**
394
- * Reports whether a non-collapsed text selection currently sits inside the given element.
395
- * 指定要素の中に折り畳まれていないテキスト選択が存在するかを返す処理。
396
- *
397
- * `Shift + ←/→` はブラウザ標準では選択範囲の伸縮である。行の中でテキストを選んでいる最中に
398
- * 横スクロールへ横取りすると、**選び直す手段がキーボードから消える**。選択が空 (キャレットだけ) の
399
- * ときは伸縮の起点が無いため横取りしてよい。
400
- *
401
- * @param element - Element to test the selection against / 選択範囲の所在を調べる要素
402
- * @returns True when a non-collapsed selection is inside the element / 折り畳まれていない選択が内側にある場合に true
403
- */
404
- const hasTextSelectionWithin = (element: HTMLElement): boolean => {
405
- const selection = element.ownerDocument.defaultView?.getSelection()
406
- if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
407
- return false
408
- }
409
- // ❗ 起点 (anchorNode) の包含では不十分。`Ctrl+A` のようにページ全体を選ぶと起点は行の外に落ち、
410
- // 「行の上に見えている選択」を取りこぼす (実ブラウザで実測)。範囲が行と**交差**するかで判定する。
411
- for (let index = 0; index < selection.rangeCount; index += 1) {
412
- if (selection.getRangeAt(index).intersectsNode(element)) {
413
- return true
414
- }
415
- }
416
- return false
417
- }
418
-
419
399
  /**
420
400
  * Converts a numeric size into a non-negative bigint for large collection handling.
421
401
  *
@@ -1915,14 +1895,14 @@ const VirtualScrollInner = <T,>(
1915
1895
  if (!emitHorizontal) {
1916
1896
  return
1917
1897
  }
1918
- // 押されたジェスチャが許可種別に含まれるかを確認する
1919
- const gesture = event.shiftKey ? "shift-arrow" : "arrow"
1920
- if (!horizontalKeyInputs?.includes(gesture)) {
1898
+ // ❗ Shift 併用は無条件で消費しない (3.0.0 で "shift-arrow" 入力を撤去)。ブラウザ標準では
1899
+ // 選択範囲の伸縮であり、選択の有無での条件分岐はしない 選択検出は Shadow DOM
1900
+ // 可搬に成立せず、Ctrl+A 全選択では全行が交差して解除まで全面ロックアウトするため
1901
+ if (event.shiftKey) {
1921
1902
  return
1922
1903
  }
1923
- // ❗ テキスト選択中の Shift+←/→ は選択範囲の伸縮であり、横スクロールで奪ってはならない
1924
- // (実ブラウザで実測: 奪うと行内のテキストを選び直せなくなる)
1925
- if (event.shiftKey && hasTextSelectionWithin(event.currentTarget)) {
1904
+ // 押されたジェスチャが許可種別に含まれるかを確認する
1905
+ if (!horizontalKeyInputs?.includes("arrow")) {
1926
1906
  return
1927
1907
  }
1928
1908
  // ❗ 不正な移動量は既定へ黙って読み替えず、消費もしない (Strict No-Fallback)。