@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.
@@ -2,7 +2,7 @@ import { TapScrollAxisSpeedParams } from './computeTapScrollVelocity.cjs';
2
2
  import { TapScrollCircleDragState, TapScrollCircleHandle } from './TapScrollCircle.cjs';
3
3
  /**
4
4
  * Default corner-relative offset (px) for BOTH grid tap-circle axes — the reachability arm (R)
5
- * of the three-arm placement law (plan §5). Derivation: full speed needs
5
+ * of the three-arm placement law. Derivation: full speed needs
6
6
  * `maxVisualDistance = 240 px` of advancing pull toward the far screen edge, which is a hard
7
7
  * stop on touch (`touch-action: none`). With `off = −200` the disc center sits
8
8
  * `sbw − off − size/2 = 12 + 200 − 20 = 192 px` inboard of each far root edge, giving a
@@ -12,11 +12,11 @@ import { TapScrollCircleDragState, TapScrollCircleHandle } from './TapScrollCirc
12
12
  * nav-zone envelope ≥ ~2/3. Deliberately a LITERAL: it must NOT track a consumer-supplied
13
13
  * `maxVisualDistance` (coupling two knobs would make one silently move the other), and
14
14
  * shrinking `maxVisualDistance` instead is forbidden (it changes the speed law's `maxDistance`
15
- * input and breaks the T1 identity anchor).
15
+ * input, so a pure-axis drag would no longer reproduce the bar's own speed law).
16
16
  * グリッドタップサークル両軸のコーナー相対既定オフセット (px) — 3 アーム配置則の到達性アーム
17
17
  * (R)。導出: 全速には前進方向へ 240 px の引きが要り、タッチではスクリーン端がハードストップ。
18
18
  * −200 で中心は各遠端から 192 px 内側 = 最悪ケース引き分率 0.80 (ナビ帯込み 0.66)。意図的な
19
- * リテラル — 消費側 `maxVisualDistance` へ連動させない (ノブ連動の登記済み禁止)。
19
+ * リテラル — 消費側 `maxVisualDistance` へ連動させない (2 つのノブを連動させると一方が他方を黙って動かす)。
20
20
  */
21
21
  export declare const GRID_TAP_CIRCLE_DEFAULT_OFFSET = -200;
22
22
  /** Parameters for `useGridTapScroll` (read live through a params ref — identity-stability is not required of callers). / `useGridTapScroll` のパラメータ (params ref 経由で毎フレーム最新を読む — 呼出し側に identity 安定を要求しない)。 */
@@ -25,9 +25,9 @@ export type UseGridTapScrollParams = {
25
25
  enabled: boolean;
26
26
  /** Shared pull range (= `max(maxVisualDistance, 1)`). / 共有引き範囲 (= `max(maxVisualDistance, 1)`)。 */
27
27
  maxDistance: number;
28
- /** Horizontal speed parameters (§4.1 Pₓ). / 横軸速度パラメータ (§4.1 Pₓ)。 */
28
+ /** Horizontal speed parameters (Pₓ, built from the scroll-band width and the column count). / 横軸速度パラメータ (Pₓ。スクロール帯幅と列数から構成)。 */
29
29
  xSpeedParams: TapScrollAxisSpeedParams;
30
- /** Vertical speed parameters (§4.1 P_y). / 縦軸速度パラメータ (§4.1 P_y)。 */
30
+ /** Vertical speed parameters (P_y, built from the embedded pane height and the scroll-row count). / 縦軸速度パラメータ (P_y。埋め込みペイン高とスクロール行数から構成)。 */
31
31
  ySpeedParams: TapScrollAxisSpeedParams;
32
32
  /** The grid's x apply seam (absolute position in, clamped applied position out). / グリッドの x 適用シーム (絶対位置 → クランプ後位置)。 */
33
33
  applyHxRef: {
@@ -35,21 +35,21 @@ export type UseGridTapScrollParams = {
35
35
  };
36
36
  /** Synchronous-fresh hx read. / hx の同期・最新読み。 */
37
37
  getHx: () => number;
38
- /** The ONE extracted x clamp law (`getMaxHx` SSOT — §3.5-7). / 唯一抽出の x クランプ則 (`getMaxHx` SSOT — §3.5-7)。 */
38
+ /** The ONE extracted x clamp law (`getMaxHx` SSOT — never re-derived from the grid's tree). / 唯一抽出の x クランプ則 (`getMaxHx` SSOT — グリッドの木から再導出しない)。 */
39
39
  getMaxHx: () => number;
40
- /** y apply seam: wraps `scrollBy(delta)` AND syncs `vyRef.current = applied` before returning (§3.5-8 — NOT a bare handle passthrough). / y 適用シーム: `scrollBy(delta)` をラップし `vyRef.current = applied` を同期してから返す (§3.5-8 — 素のハンドル素通しではない)。 */
40
+ /** y apply seam: wraps `scrollBy(delta)` AND syncs `vyRef.current = applied` before returning (NOT a bare handle passthrough — without the sync, x-frame notifications during a diagonal drag carry a stale y). / y 適用シーム: `scrollBy(delta)` をラップし `vyRef.current = applied` を同期してから返す (素のハンドル素通しではない — 同期が無いと対角ドラッグ中の x フレーム通知が stale な y を運ぶ)。 */
41
41
  applyVy: (delta: number) => number;
42
42
  /** Synchronous-fresh vy read — MUST read the same authority `applyVy` resolves against (the embedded pane's position), never a notification mirror with a second writer (v3.6.1 — a lagging mirror base overstates `actualDelta` and drives the residual negative = yo-yo). / vy の同期・最新読み — `applyVy` が解決するのと同一権威 (埋め込みペイン位置) を読むこと。第 2 の書き手を持つ通知鏡像は禁止 (v3.6.1 — 遅延鏡像基準は `actualDelta` を過大化し残差を負へ落とすヨーヨー)。 */
43
43
  getVy: () => number;
44
44
  /** y extent read through the embedded handle's freshness channel (never re-derived). / 埋め込みハンドルの鮮度チャネル経由の y 延長 (再導出禁止)。 */
45
45
  getMaxVy: () => number;
46
- /** Pending column anchor — cleared ONLY on frames whose APPLIED hx delta ≠ 0 (§3.5-5). / 保留列アンカー — 適用済み hx デルタ ≠ 0 のフレームのみ解除 (§3.5-5)。 */
46
+ /** Pending column anchor — cleared ONLY on frames whose APPLIED hx delta ≠ 0 (a request the clamp swallows keeps it). / 保留列アンカー — 適用済み hx デルタ ≠ 0 のフレームのみ解除 (クランプに吸収された要求では残す)。 */
47
47
  pendingColAnchorRef: {
48
48
  current: unknown;
49
49
  };
50
- /** x extent-growth freshness key (columnWindow / total width — §3.5-3). / x 延長成長の鮮度キー (columnWindow / 総幅 — §3.5-3)。 */
50
+ /** x extent-growth freshness key (columnWindow / total width — a change re-arms a loop parked at the edge). / x 延長成長の鮮度キー (columnWindow / 総幅 — 変化で端に止まったループを再武装)。 */
51
51
  xExtentFreshness: unknown;
52
- /** y extent-growth freshness key (the vertical range state — §3.5-3). / y 延長成長の鮮度キー (縦レンジ state — §3.5-3)。 */
52
+ /** y extent-growth freshness key (the vertical range state — a change re-arms a loop parked at the edge). / y 延長成長の鮮度キー (縦レンジ state — 変化で端に止まったループを再武装)。 */
53
53
  yExtentFreshness: unknown;
54
54
  };
55
55
  /** Result of `useGridTapScroll`. / `useGridTapScroll` の返り値。 */
@@ -65,7 +65,7 @@ export type UseGridTapScrollResult = {
65
65
  * Owns the unified tap circle's drag state and the two-axis rAF integration loop.
66
66
  * 統合タップサークルのドラッグ状態と 2 軸 rAF 積分ループを所有するフック。
67
67
  *
68
- * Per-frame semantics (plan §8.3 — implemented verbatim):
68
+ * Per-frame semantics:
69
69
  *
70
70
  * ```text
71
71
  * dt = min(max((t − last)/1000, 0), TAP_SCROLL_MAX_FRAME_DELTA_SECONDS); skip if dt ≤ 0
@@ -2,7 +2,7 @@ import { TapScrollAxisSpeedParams } from './computeTapScrollVelocity.ts';
2
2
  import { TapScrollCircleDragState, TapScrollCircleHandle } from './TapScrollCircle.tsx';
3
3
  /**
4
4
  * Default corner-relative offset (px) for BOTH grid tap-circle axes — the reachability arm (R)
5
- * of the three-arm placement law (plan §5). Derivation: full speed needs
5
+ * of the three-arm placement law. Derivation: full speed needs
6
6
  * `maxVisualDistance = 240 px` of advancing pull toward the far screen edge, which is a hard
7
7
  * stop on touch (`touch-action: none`). With `off = −200` the disc center sits
8
8
  * `sbw − off − size/2 = 12 + 200 − 20 = 192 px` inboard of each far root edge, giving a
@@ -12,11 +12,11 @@ import { TapScrollCircleDragState, TapScrollCircleHandle } from './TapScrollCirc
12
12
  * nav-zone envelope ≥ ~2/3. Deliberately a LITERAL: it must NOT track a consumer-supplied
13
13
  * `maxVisualDistance` (coupling two knobs would make one silently move the other), and
14
14
  * shrinking `maxVisualDistance` instead is forbidden (it changes the speed law's `maxDistance`
15
- * input and breaks the T1 identity anchor).
15
+ * input, so a pure-axis drag would no longer reproduce the bar's own speed law).
16
16
  * グリッドタップサークル両軸のコーナー相対既定オフセット (px) — 3 アーム配置則の到達性アーム
17
17
  * (R)。導出: 全速には前進方向へ 240 px の引きが要り、タッチではスクリーン端がハードストップ。
18
18
  * −200 で中心は各遠端から 192 px 内側 = 最悪ケース引き分率 0.80 (ナビ帯込み 0.66)。意図的な
19
- * リテラル — 消費側 `maxVisualDistance` へ連動させない (ノブ連動の登記済み禁止)。
19
+ * リテラル — 消費側 `maxVisualDistance` へ連動させない (2 つのノブを連動させると一方が他方を黙って動かす)。
20
20
  */
21
21
  export declare const GRID_TAP_CIRCLE_DEFAULT_OFFSET = -200;
22
22
  /** Parameters for `useGridTapScroll` (read live through a params ref — identity-stability is not required of callers). / `useGridTapScroll` のパラメータ (params ref 経由で毎フレーム最新を読む — 呼出し側に identity 安定を要求しない)。 */
@@ -25,9 +25,9 @@ export type UseGridTapScrollParams = {
25
25
  enabled: boolean;
26
26
  /** Shared pull range (= `max(maxVisualDistance, 1)`). / 共有引き範囲 (= `max(maxVisualDistance, 1)`)。 */
27
27
  maxDistance: number;
28
- /** Horizontal speed parameters (§4.1 Pₓ). / 横軸速度パラメータ (§4.1 Pₓ)。 */
28
+ /** Horizontal speed parameters (Pₓ, built from the scroll-band width and the column count). / 横軸速度パラメータ (Pₓ。スクロール帯幅と列数から構成)。 */
29
29
  xSpeedParams: TapScrollAxisSpeedParams;
30
- /** Vertical speed parameters (§4.1 P_y). / 縦軸速度パラメータ (§4.1 P_y)。 */
30
+ /** Vertical speed parameters (P_y, built from the embedded pane height and the scroll-row count). / 縦軸速度パラメータ (P_y。埋め込みペイン高とスクロール行数から構成)。 */
31
31
  ySpeedParams: TapScrollAxisSpeedParams;
32
32
  /** The grid's x apply seam (absolute position in, clamped applied position out). / グリッドの x 適用シーム (絶対位置 → クランプ後位置)。 */
33
33
  applyHxRef: {
@@ -35,21 +35,21 @@ export type UseGridTapScrollParams = {
35
35
  };
36
36
  /** Synchronous-fresh hx read. / hx の同期・最新読み。 */
37
37
  getHx: () => number;
38
- /** The ONE extracted x clamp law (`getMaxHx` SSOT — §3.5-7). / 唯一抽出の x クランプ則 (`getMaxHx` SSOT — §3.5-7)。 */
38
+ /** The ONE extracted x clamp law (`getMaxHx` SSOT — never re-derived from the grid's tree). / 唯一抽出の x クランプ則 (`getMaxHx` SSOT — グリッドの木から再導出しない)。 */
39
39
  getMaxHx: () => number;
40
- /** y apply seam: wraps `scrollBy(delta)` AND syncs `vyRef.current = applied` before returning (§3.5-8 — NOT a bare handle passthrough). / y 適用シーム: `scrollBy(delta)` をラップし `vyRef.current = applied` を同期してから返す (§3.5-8 — 素のハンドル素通しではない)。 */
40
+ /** y apply seam: wraps `scrollBy(delta)` AND syncs `vyRef.current = applied` before returning (NOT a bare handle passthrough — without the sync, x-frame notifications during a diagonal drag carry a stale y). / y 適用シーム: `scrollBy(delta)` をラップし `vyRef.current = applied` を同期してから返す (素のハンドル素通しではない — 同期が無いと対角ドラッグ中の x フレーム通知が stale な y を運ぶ)。 */
41
41
  applyVy: (delta: number) => number;
42
42
  /** Synchronous-fresh vy read — MUST read the same authority `applyVy` resolves against (the embedded pane's position), never a notification mirror with a second writer (v3.6.1 — a lagging mirror base overstates `actualDelta` and drives the residual negative = yo-yo). / vy の同期・最新読み — `applyVy` が解決するのと同一権威 (埋め込みペイン位置) を読むこと。第 2 の書き手を持つ通知鏡像は禁止 (v3.6.1 — 遅延鏡像基準は `actualDelta` を過大化し残差を負へ落とすヨーヨー)。 */
43
43
  getVy: () => number;
44
44
  /** y extent read through the embedded handle's freshness channel (never re-derived). / 埋め込みハンドルの鮮度チャネル経由の y 延長 (再導出禁止)。 */
45
45
  getMaxVy: () => number;
46
- /** Pending column anchor — cleared ONLY on frames whose APPLIED hx delta ≠ 0 (§3.5-5). / 保留列アンカー — 適用済み hx デルタ ≠ 0 のフレームのみ解除 (§3.5-5)。 */
46
+ /** Pending column anchor — cleared ONLY on frames whose APPLIED hx delta ≠ 0 (a request the clamp swallows keeps it). / 保留列アンカー — 適用済み hx デルタ ≠ 0 のフレームのみ解除 (クランプに吸収された要求では残す)。 */
47
47
  pendingColAnchorRef: {
48
48
  current: unknown;
49
49
  };
50
- /** x extent-growth freshness key (columnWindow / total width — §3.5-3). / x 延長成長の鮮度キー (columnWindow / 総幅 — §3.5-3)。 */
50
+ /** x extent-growth freshness key (columnWindow / total width — a change re-arms a loop parked at the edge). / x 延長成長の鮮度キー (columnWindow / 総幅 — 変化で端に止まったループを再武装)。 */
51
51
  xExtentFreshness: unknown;
52
- /** y extent-growth freshness key (the vertical range state — §3.5-3). / y 延長成長の鮮度キー (縦レンジ state — §3.5-3)。 */
52
+ /** y extent-growth freshness key (the vertical range state — a change re-arms a loop parked at the edge). / y 延長成長の鮮度キー (縦レンジ state — 変化で端に止まったループを再武装)。 */
53
53
  yExtentFreshness: unknown;
54
54
  };
55
55
  /** Result of `useGridTapScroll`. / `useGridTapScroll` の返り値。 */
@@ -65,7 +65,7 @@ export type UseGridTapScrollResult = {
65
65
  * Owns the unified tap circle's drag state and the two-axis rAF integration loop.
66
66
  * 統合タップサークルのドラッグ状態と 2 軸 rAF 積分ループを所有するフック。
67
67
  *
68
- * Per-frame semantics (plan §8.3 — implemented verbatim):
68
+ * Per-frame semantics:
69
69
  *
70
70
  * ```text
71
71
  * dt = min(max((t − last)/1000, 0), TAP_SCROLL_MAX_FRAME_DELTA_SECONDS); skip if dt ≤ 0
@@ -1 +1 @@
1
- {"version":3,"file":"useGridTapScroll.d.ts","sourceRoot":"","sources":["../src/useGridTapScroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAA4B,KAAK,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAEvG,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAG5F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAalD,4MAA4M;AAC5M,MAAM,MAAM,sBAAsB,GAAG;IACjC,uIAAuI;IACvI,OAAO,EAAE,OAAO,CAAA;IAChB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,YAAY,EAAE,wBAAwB,CAAA;IACtC,oEAAoE;IACpE,YAAY,EAAE,wBAAwB,CAAA;IACtC,sHAAsH;IACtH,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAA;IAC1D,gDAAgD;IAChD,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,4GAA4G;IAC5G,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,4OAA4O;IAC5O,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,8YAA8Y;IAC9Y,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,2HAA2H;IAC3H,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,0IAA0I;IAC1I,mBAAmB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;IACzC,uHAAuH;IACvH,gBAAgB,EAAE,OAAO,CAAA;IACzB,8GAA8G;IAC9G,gBAAgB,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAAG;IACjC,4FAA4F;IAC5F,WAAW,EAAE,OAAO,CAAA;IACpB,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACpE,4HAA4H;IAC5H,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;CACpE,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,sBAAsB,KAAG,sBAuQjE,CAAA"}
1
+ {"version":3,"file":"useGridTapScroll.d.ts","sourceRoot":"","sources":["../src/useGridTapScroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAA4B,KAAK,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAEvG,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAG5F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAalD,4MAA4M;AAC5M,MAAM,MAAM,sBAAsB,GAAG;IACjC,uIAAuI;IACvI,OAAO,EAAE,OAAO,CAAA;IAChB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAA;IACnB,gIAAgI;IAChI,YAAY,EAAE,wBAAwB,CAAA;IACtC,6IAA6I;IAC7I,YAAY,EAAE,wBAAwB,CAAA;IACtC,sHAAsH;IACtH,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAA;IAC1D,gDAAgD;IAChD,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,mJAAmJ;IACnJ,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,oVAAoV;IACpV,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,8YAA8Y;IAC9Y,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,2HAA2H;IAC3H,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,mLAAmL;IACnL,mBAAmB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;IACzC,qKAAqK;IACrK,gBAAgB,EAAE,OAAO,CAAA;IACzB,4JAA4J;IAC5J,gBAAgB,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAAG;IACjC,4FAA4F;IAC5F,WAAW,EAAE,OAAO,CAAA;IACpB,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACpE,4HAA4H;IAC5H,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;CACpE,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,sBAAsB,KAAG,sBAuQjE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/virtualscroll",
3
- "version": "3.7.1",
3
+ "version": "3.8.0",
4
4
  "description": "High-performance virtual scrolling component for React with variable item heights",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -107,8 +107,8 @@
107
107
  "license-check": "pnpm dlx license-checker --production --onlyAllow \"MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;Unlicense\"",
108
108
  "license-check:json": "pnpm dlx license-checker --production --onlyAllow \"MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;Unlicense\" --json",
109
109
  "clean": "rimraf dist",
110
- "publish:patch": "pnpm run typecheck && pnpm run --if-present test && pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
111
- "publish:minor": "pnpm run typecheck && pnpm run --if-present test && pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
112
- "publish:major": "pnpm run typecheck && pnpm run --if-present test && pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks"
110
+ "publish:patch": "pnpm run typecheck && pnpm run --if-present test && node ../../.config/scripts/check-publish-leaks.mjs && pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
111
+ "publish:minor": "pnpm run typecheck && pnpm run --if-present test && node ../../.config/scripts/check-publish-leaks.mjs && pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
112
+ "publish:major": "pnpm run typecheck && pnpm run --if-present test && node ../../.config/scripts/check-publish-leaks.mjs && pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks"
113
113
  }
114
114
  }
package/src/ScrollBar.tsx CHANGED
@@ -160,6 +160,40 @@ export type ScrollBarProps = {
160
160
  enableTrackClick?: boolean
161
161
  /** Whether arrow buttons control the scroll position. / 矢印ボタンによるスクロール操作を許可するかどうか。 */
162
162
  enableArrowButtons?: boolean
163
+ /**
164
+ * Whether the two arrow buttons are Tab stops (default `true`).
165
+ *
166
+ * `false` renders both with `tabIndex={-1}`, which takes them out of the sequential focus order
167
+ * and changes nothing else: pointer presses and press-and-hold repeat, the accessible names and
168
+ * Enter / Space on an arrow focused from script all keep working. The arrows are descendants of
169
+ * the `role="scrollbar"` root, whose children ARIA 1.2 makes presentational, so whether assistive
170
+ * technology lists them as buttons of their own is the browser's choice (Chromium does); this
171
+ * option changes nothing about that. Set it to `false` when the host already owns keyboard
172
+ * scrolling of this viewport (roving row focus with Arrow / Page / Home / End, a grid keyboard
173
+ * model): the arrows then duplicate that path and cost every keyboard user two extra Tab presses
174
+ * per bar — native scrollbars are never Tab stops either. The default stays `true` because
175
+ * without such a host model the arrows are the only scrolling control a keyboard user can reach
176
+ * with Tab: `ScrollPane` / `VirtualScroll` move their content by transform, so there is no native
177
+ * scroller the browser could make focusable, and their rows are not Tab stops. No effect while
178
+ * the arrows are disabled (`enableArrowButtons: false`, or nothing to scroll) — a disabled button
179
+ * is never focusable.
180
+ *
181
+ * 矢印ボタン 2 個を Tab の止まり先にするかどうか (既定 `true`)。
182
+ *
183
+ * `false` は両方を `tabIndex={-1}` で描画し、順次フォーカス移動の順序から外すだけの指定。
184
+ * ポインタ押下と長押しリピート、アクセシブルネーム、スクリプトからフォーカスした矢印での
185
+ * Enter / Space はすべて維持。矢印は `role="scrollbar"` のルートの子孫で、ARIA 1.2 はその子を
186
+ * presentational とするため、支援技術が矢印を独立したボタンとして示すかどうかはブラウザの選択
187
+ * (Chromium は示す) であり、本オプションはそこに関与しない。ホストがこのビューポートのキーボード
188
+ * スクロールを既に持つ場合 (行のロービングフォーカスと矢印 / Page / Home / End、グリッドのキーボードモデル) に
189
+ * `false` を指定 — 矢印はその経路の重複となり、キーボード利用者にバー 1 本あたり 2 回の余分な Tab を
190
+ * 課すため (ネイティブのスクロールバーも Tab の止まり先にならない)。既定を `true` に据え置く理由は、
191
+ * ホスト側のモデルが無ければ矢印がキーボード利用者の Tab で届く唯一のスクロール操作部品であること
192
+ * (`ScrollPane` / `VirtualScroll` はコンテンツを transform で動かすため、ブラウザがフォーカス可能に
193
+ * できるネイティブのスクロール領域が無く、行も Tab の止まり先ではない)。矢印が無効な間
194
+ * (`enableArrowButtons: false` またはスクロール不要) は効果なし — disabled のボタンはそもそもフォーカス不能。
195
+ */
196
+ enableArrowButtonTabStops?: boolean
163
197
  /** Whether the scrollbar is horizontal. / スクロールバーが水平かどうか。 */
164
198
  horizontal?: boolean
165
199
  /**
@@ -611,6 +645,7 @@ export const ScrollBar = ({
611
645
  enableThumbDrag = true,
612
646
  enableTrackClick = true,
613
647
  enableArrowButtons = true,
648
+ enableArrowButtonTabStops = true,
614
649
  horizontal = false,
615
650
  enableHorizontalTapCircle = false,
616
651
  stretchMainSize = false,
@@ -860,7 +895,7 @@ export const ScrollBar = ({
860
895
  */
861
896
  const stepAutoScroll = useCallback(
862
897
  (timestamp: number) => {
863
- // この rAF ループ意味論は useGridTapScroll (グリッド 2 軸ドライバ) と双子 — 片方を直したらもう片方も直すこと (ADR-27)
898
+ // この rAF ループ意味論は useGridTapScroll (グリッド 2 軸ドライバ) と双子 — 片方を直したらもう片方も直すこと
864
899
  const state = tapDragStateRef.current
865
900
  const metrics = latestMetricsRef.current
866
901
  if (!state.active || state.direction === 0) {
@@ -1436,18 +1471,25 @@ export const ScrollBar = ({
1436
1471
  }, [tapCircleOffsetX, tapCircleOffsetY, tapCircleSize])
1437
1472
 
1438
1473
  /**
1439
- * Renders an arrow button for scrollbar controls.
1440
- * The buttons stay in the tab order (tabIndex=0) so keyboard users can reach
1441
- * the Enter/Space handlers, which are the only keyboard scrolling entry point.
1474
+ * Renders one arrow button of the bar. It is a Tab stop (`tabIndex=0`) unless the host opted out
1475
+ * with `enableArrowButtonTabStops={false}` (`tabIndex=-1`); either way it stays focusable from
1476
+ * script, pointer-operable and named, and Enter / Space scrolls one step while it has focus.
1477
+ *
1478
+ * バーの矢印ボタン 1 個の描画。ホストが `enableArrowButtonTabStops={false}` で外さない限り Tab の
1479
+ * 止まり先 (`tabIndex=0`)、外した場合は `tabIndex=-1`。いずれの場合もスクリプトからのフォーカス・
1480
+ * ポインタ操作・アクセシブルネームは維持し、フォーカス中の Enter / Space で 1 ステップ移動。
1442
1481
  *
1443
- * スクロールバー制御用矢印ボタンを描画。キーボードで Enter/Space ハンドラへ
1444
- * 到達できるよう tabIndex=0 でタブ順序に残す。
1482
+ * @param direction - Step direction (-1 = toward the start, 1 = toward the end) / ステップ方向 (-1 = 始端側、1 = 終端側)
1483
+ * @param label - Accessible name / アクセシブルネーム
1484
+ * @param icon - Decorative glyph, hidden from assistive technology / 支援技術から隠す装飾グリフ
1485
+ * @param key - React key / React キー
1486
+ * @returns The arrow button element / 矢印ボタン要素
1445
1487
  */
1446
1488
  const renderArrowButton = (direction: 1 | -1, label: string, icon: string, key: string) => (
1447
1489
  <button
1448
1490
  key={key}
1449
1491
  type="button"
1450
- tabIndex={0}
1492
+ tabIndex={enableArrowButtonTabStops ? 0 : -1}
1451
1493
  className="aqvs-scrollbar-arrow-button"
1452
1494
  style={{
1453
1495
  [mainSizeKey]: scrollBarWidth,
@@ -79,6 +79,21 @@ export type ScrollPaneProps = {
79
79
  enableTrackClick?: boolean
80
80
  /** Whether arrow buttons control the scroll position. / 矢印ボタンのスクロール操作を許可するかどうか。 */
81
81
  enableArrowButtons?: boolean
82
+ /**
83
+ * Whether the scrollbar's two arrow buttons are Tab stops (default `true`), forwarded to the
84
+ * pane's ScrollBar. Set it to `false` when the host already provides keyboard scrolling for this
85
+ * pane: the arrows then only add two redundant Tab stops (native scrollbars are never Tab stops
86
+ * either). `false` changes `tabIndex` alone — the arrows stay pointer-operable and named. The
87
+ * default stays `true` because otherwise they are the pane's only scrolling control that Tab
88
+ * reaches. Full contract: `ScrollBarProps["enableArrowButtonTabStops"]`.
89
+ * スクロールバーの矢印ボタン 2 個を Tab の止まり先にするかどうか (既定 `true`)。ペインの ScrollBar へ
90
+ * 転送。ホストがこのペインのキーボードスクロールを既に提供する場合に `false` — 矢印は冗長な Tab の
91
+ * 止まり先を 2 つ足すだけになるため (ネイティブのスクロールバーも Tab の止まり先にならない)。`false` が
92
+ * 変えるのは `tabIndex` だけで、ポインタ操作とアクセシブルネームは維持。
93
+ * 既定が `true` なのは、それ以外ではペインで Tab の届く唯一のスクロール操作部品だから。契約の全文は
94
+ * `ScrollBarProps["enableArrowButtonTabStops"]`。
95
+ */
96
+ enableArrowButtonTabStops?: boolean
82
97
  /** Whether dragging the content area scrolls the pane. / コンテンツ領域のドラッグでスクロールさせるかどうか。 */
83
98
  enablePointerDrag?: boolean
84
99
  /**
@@ -327,6 +342,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
327
342
  enableThumbDrag = true,
328
343
  enableTrackClick = true,
329
344
  enableArrowButtons = true,
345
+ enableArrowButtonTabStops = true,
330
346
  enablePointerDrag = true,
331
347
  pointerDragInputs = DEFAULT_POINTER_DRAG_INPUTS,
332
348
  onScroll,
@@ -1675,6 +1691,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
1675
1691
  enableThumbDrag={enableThumbDrag}
1676
1692
  enableTrackClick={enableTrackClick}
1677
1693
  enableArrowButtons={enableArrowButtons}
1694
+ enableArrowButtonTabStops={enableArrowButtonTabStops}
1678
1695
  scrollBarWidth={scrollBarWidth}
1679
1696
  ariaControls={contentId}
1680
1697
  tapScrollCircleOptions={tapScrollCircleOptions}