@aiquants/virtualscroll 3.7.0 → 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.
- package/CHANGELOG.md +152 -39
- package/README.md +92 -5
- package/dist/ScrollBar.d.cts +35 -1
- package/dist/ScrollBar.d.ts +35 -1
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.cts +15 -0
- package/dist/ScrollPane.d.ts +15 -0
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/TapScrollCircle.d.ts.map +1 -1
- package/dist/VirtualGrid.d.cts +70 -57
- package/dist/VirtualGrid.d.ts +70 -57
- package/dist/VirtualGrid.d.ts.map +1 -1
- package/dist/VirtualScroll.d.cts +59 -2
- package/dist/VirtualScroll.d.ts +59 -2
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/computeTapScrollVelocity.d.cts +3 -3
- package/dist/computeTapScrollVelocity.d.ts +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +2106 -2102
- package/dist/pointerCapture.d.cts +68 -0
- package/dist/pointerCapture.d.ts +69 -0
- package/dist/pointerCapture.d.ts.map +1 -0
- package/dist/useGridTapScroll.d.cts +11 -11
- package/dist/useGridTapScroll.d.ts +11 -11
- package/dist/useGridTapScroll.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ScrollBar.tsx +69 -41
- package/src/ScrollPane.tsx +62 -18
- package/src/TapScrollCircle.tsx +35 -25
- package/src/VirtualGrid.tsx +111 -70
- package/src/VirtualScroll.tsx +167 -5
- package/src/computeTapScrollVelocity.ts +3 -3
- package/src/pointerCapture.ts +89 -0
- package/src/styles/virtualscroll.css +1 -1
- package/src/useGridTapScroll.ts +12 -12
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module pointerCapture
|
|
3
|
+
* @description The single capability rule for the Pointer Capture API, shared by every pointer interaction in this package.
|
|
4
|
+
*
|
|
5
|
+
* @description 本パッケージのすべてのポインタ操作が共有する、Pointer Capture API の唯一の能力判定モジュール。
|
|
6
|
+
*
|
|
7
|
+
* `ScrollPane` (コンテンツのドラッグスクロール)・`ScrollBar` (サム / トラックのドラッグ)・
|
|
8
|
+
* `TapScrollCircle` (タップスクロールサークル) は、キャプチャの取得・解放・保持確認を
|
|
9
|
+
* 必ずここを通して行う。要素の `setPointerCapture` / `releasePointerCapture` /
|
|
10
|
+
* `hasPointerCapture` を直接呼んではならない。
|
|
11
|
+
*
|
|
12
|
+
* ❗ **3 つのメソッドが揃っている要素だけを「対応」とみなす。** jsdom (React のテストの既定環境) は
|
|
13
|
+
* このどれも実装しないため、直接呼ぶと利用側の `user.click` 1 回で `TypeError` になる。
|
|
14
|
+
* 一部だけを実装する環境で「取得はしたが解放も確認もできない」状態を作らないよう、判定は 1 本にしてある。
|
|
15
|
+
*
|
|
16
|
+
* 非対応の要素ではキャプチャ無しで操作を続ける (縮退)。コンテンツ領域とサムのドラッグは
|
|
17
|
+
* `window` / `document` のリスナーが追従を受け持つため動き続け、トラックとタップサークルは
|
|
18
|
+
* ポインタが要素上にある間だけ追従する。失われるのは、ウインドウ外や iframe 上で離したときの
|
|
19
|
+
* 終端イベントの受信と、`lostpointercapture` による強制解放の検知である。
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Outcome of a pointer capture request.
|
|
23
|
+
* ポインタキャプチャ要求の結果。
|
|
24
|
+
*
|
|
25
|
+
* - `"captured"`: The element now holds the capture. / 要素がキャプチャを保持した。
|
|
26
|
+
* - `"unsupported"`: The element does not implement the Pointer Capture API; nothing was attempted. / 要素が API を実装しておらず、要求自体を行っていない。
|
|
27
|
+
* - `"inactive-pointer"`: The UA rejected the pointer as inactive (`NotFoundError`). / ポインタが非アクティブとして拒否された (`NotFoundError`)。
|
|
28
|
+
* - `"rejected"`: The UA rejected the request for any other reason. / それ以外の理由で拒否された。
|
|
29
|
+
*/
|
|
30
|
+
export type PointerCaptureResult = "captured" | "unsupported" | "inactive-pointer" | "rejected";
|
|
31
|
+
/**
|
|
32
|
+
* Reports whether the element implements the whole Pointer Capture API.
|
|
33
|
+
* 要素が Pointer Capture API (取得・解放・保持確認の 3 メソッド) をすべて実装しているかの判定処理。
|
|
34
|
+
*
|
|
35
|
+
* @param element The element to inspect. 判定対象の要素。
|
|
36
|
+
* @returns True when set / release / has are all callable. 3 メソッドがすべて呼び出し可能な場合に true。
|
|
37
|
+
*/
|
|
38
|
+
export declare const supportsPointerCapture: (element: Element) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Reports whether the element holds (or has a pending) capture of the pointer.
|
|
41
|
+
* 要素が指定ポインタのキャプチャを保持 (または保留) しているかの判定処理。
|
|
42
|
+
*
|
|
43
|
+
* @param element The element to inspect. 判定対象の要素。
|
|
44
|
+
* @param pointerId The pointer to look up. 対象のポインタ ID。
|
|
45
|
+
* @returns True when the capture is held; always false when the API is unsupported. 保持している場合に true。API 非対応の要素では常に false。
|
|
46
|
+
*/
|
|
47
|
+
export declare const isPointerCaptured: (element: Element, pointerId: number) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Requests capture of the pointer for the element and classifies the outcome without throwing.
|
|
50
|
+
* 要素へ指定ポインタのキャプチャを要求し、例外を投げずに結果を分類する処理。
|
|
51
|
+
*
|
|
52
|
+
* @param element The element that should receive the capture. キャプチャを受け取る要素。
|
|
53
|
+
* @param pointerId The pointer to capture. キャプチャするポインタ ID。
|
|
54
|
+
* @returns The classified outcome of the request. 要求結果の分類。
|
|
55
|
+
*/
|
|
56
|
+
export declare const capturePointer: (element: Element, pointerId: number) => PointerCaptureResult;
|
|
57
|
+
/**
|
|
58
|
+
* Releases the element's capture of the pointer when it holds one; does nothing otherwise.
|
|
59
|
+
* 要素が指定ポインタのキャプチャを保持していれば解放し、保持していなければ何もしない処理。
|
|
60
|
+
*
|
|
61
|
+
* 呼び出し側が状態を確定させてから呼ぶこと。`lostpointercapture` を同期発火するブラウザでは、
|
|
62
|
+
* この呼び出しの最中に喪失ハンドラーが走る。
|
|
63
|
+
*
|
|
64
|
+
* @param element The element that may hold the capture. キャプチャを保持している可能性のある要素。
|
|
65
|
+
* @param pointerId The pointer to release. 解放するポインタ ID。
|
|
66
|
+
* @returns Nothing. なし。
|
|
67
|
+
*/
|
|
68
|
+
export declare const releaseCapturedPointer: (element: Element, pointerId: number) => void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module pointerCapture
|
|
3
|
+
* @description The single capability rule for the Pointer Capture API, shared by every pointer interaction in this package.
|
|
4
|
+
*
|
|
5
|
+
* @description 本パッケージのすべてのポインタ操作が共有する、Pointer Capture API の唯一の能力判定モジュール。
|
|
6
|
+
*
|
|
7
|
+
* `ScrollPane` (コンテンツのドラッグスクロール)・`ScrollBar` (サム / トラックのドラッグ)・
|
|
8
|
+
* `TapScrollCircle` (タップスクロールサークル) は、キャプチャの取得・解放・保持確認を
|
|
9
|
+
* 必ずここを通して行う。要素の `setPointerCapture` / `releasePointerCapture` /
|
|
10
|
+
* `hasPointerCapture` を直接呼んではならない。
|
|
11
|
+
*
|
|
12
|
+
* ❗ **3 つのメソッドが揃っている要素だけを「対応」とみなす。** jsdom (React のテストの既定環境) は
|
|
13
|
+
* このどれも実装しないため、直接呼ぶと利用側の `user.click` 1 回で `TypeError` になる。
|
|
14
|
+
* 一部だけを実装する環境で「取得はしたが解放も確認もできない」状態を作らないよう、判定は 1 本にしてある。
|
|
15
|
+
*
|
|
16
|
+
* 非対応の要素ではキャプチャ無しで操作を続ける (縮退)。コンテンツ領域とサムのドラッグは
|
|
17
|
+
* `window` / `document` のリスナーが追従を受け持つため動き続け、トラックとタップサークルは
|
|
18
|
+
* ポインタが要素上にある間だけ追従する。失われるのは、ウインドウ外や iframe 上で離したときの
|
|
19
|
+
* 終端イベントの受信と、`lostpointercapture` による強制解放の検知である。
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Outcome of a pointer capture request.
|
|
23
|
+
* ポインタキャプチャ要求の結果。
|
|
24
|
+
*
|
|
25
|
+
* - `"captured"`: The element now holds the capture. / 要素がキャプチャを保持した。
|
|
26
|
+
* - `"unsupported"`: The element does not implement the Pointer Capture API; nothing was attempted. / 要素が API を実装しておらず、要求自体を行っていない。
|
|
27
|
+
* - `"inactive-pointer"`: The UA rejected the pointer as inactive (`NotFoundError`). / ポインタが非アクティブとして拒否された (`NotFoundError`)。
|
|
28
|
+
* - `"rejected"`: The UA rejected the request for any other reason. / それ以外の理由で拒否された。
|
|
29
|
+
*/
|
|
30
|
+
export type PointerCaptureResult = "captured" | "unsupported" | "inactive-pointer" | "rejected";
|
|
31
|
+
/**
|
|
32
|
+
* Reports whether the element implements the whole Pointer Capture API.
|
|
33
|
+
* 要素が Pointer Capture API (取得・解放・保持確認の 3 メソッド) をすべて実装しているかの判定処理。
|
|
34
|
+
*
|
|
35
|
+
* @param element The element to inspect. 判定対象の要素。
|
|
36
|
+
* @returns True when set / release / has are all callable. 3 メソッドがすべて呼び出し可能な場合に true。
|
|
37
|
+
*/
|
|
38
|
+
export declare const supportsPointerCapture: (element: Element) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Reports whether the element holds (or has a pending) capture of the pointer.
|
|
41
|
+
* 要素が指定ポインタのキャプチャを保持 (または保留) しているかの判定処理。
|
|
42
|
+
*
|
|
43
|
+
* @param element The element to inspect. 判定対象の要素。
|
|
44
|
+
* @param pointerId The pointer to look up. 対象のポインタ ID。
|
|
45
|
+
* @returns True when the capture is held; always false when the API is unsupported. 保持している場合に true。API 非対応の要素では常に false。
|
|
46
|
+
*/
|
|
47
|
+
export declare const isPointerCaptured: (element: Element, pointerId: number) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Requests capture of the pointer for the element and classifies the outcome without throwing.
|
|
50
|
+
* 要素へ指定ポインタのキャプチャを要求し、例外を投げずに結果を分類する処理。
|
|
51
|
+
*
|
|
52
|
+
* @param element The element that should receive the capture. キャプチャを受け取る要素。
|
|
53
|
+
* @param pointerId The pointer to capture. キャプチャするポインタ ID。
|
|
54
|
+
* @returns The classified outcome of the request. 要求結果の分類。
|
|
55
|
+
*/
|
|
56
|
+
export declare const capturePointer: (element: Element, pointerId: number) => PointerCaptureResult;
|
|
57
|
+
/**
|
|
58
|
+
* Releases the element's capture of the pointer when it holds one; does nothing otherwise.
|
|
59
|
+
* 要素が指定ポインタのキャプチャを保持していれば解放し、保持していなければ何もしない処理。
|
|
60
|
+
*
|
|
61
|
+
* 呼び出し側が状態を確定させてから呼ぶこと。`lostpointercapture` を同期発火するブラウザでは、
|
|
62
|
+
* この呼び出しの最中に喪失ハンドラーが走る。
|
|
63
|
+
*
|
|
64
|
+
* @param element The element that may hold the capture. キャプチャを保持している可能性のある要素。
|
|
65
|
+
* @param pointerId The pointer to release. 解放するポインタ ID。
|
|
66
|
+
* @returns Nothing. なし。
|
|
67
|
+
*/
|
|
68
|
+
export declare const releaseCapturedPointer: (element: Element, pointerId: number) => void;
|
|
69
|
+
//# sourceMappingURL=pointerCapture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointerCapture.d.ts","sourceRoot":"","sources":["../src/pointerCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,aAAa,GAAG,kBAAkB,GAAG,UAAU,CAAA;AAE/F;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,OAAO,KAAG,OAAoK,CAAA;AAE9N;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,OAAO,EAAE,WAAW,MAAM,KAAG,OAAkF,CAAA;AAE1J;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,EAAE,WAAW,MAAM,KAAG,oBAYpE,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,OAAO,EAAE,WAAW,MAAM,KAAG,IAI5E,CAAA"}
|
|
@@ -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
|
|
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
|
|
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 (
|
|
28
|
+
/** Horizontal speed parameters (Pₓ, built from the scroll-band width and the column count). / 横軸速度パラメータ (Pₓ。スクロール帯幅と列数から構成)。 */
|
|
29
29
|
xSpeedParams: TapScrollAxisSpeedParams;
|
|
30
|
-
/** Vertical speed parameters (
|
|
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 —
|
|
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 (
|
|
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 (
|
|
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 —
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
28
|
+
/** Horizontal speed parameters (Pₓ, built from the scroll-band width and the column count). / 横軸速度パラメータ (Pₓ。スクロール帯幅と列数から構成)。 */
|
|
29
29
|
xSpeedParams: TapScrollAxisSpeedParams;
|
|
30
|
-
/** Vertical speed parameters (
|
|
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 —
|
|
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 (
|
|
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 (
|
|
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 —
|
|
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 —
|
|
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
|
|
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,
|
|
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.
|
|
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
|
@@ -8,6 +8,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
|
|
|
8
8
|
import { twMerge } from "tailwind-merge"
|
|
9
9
|
import { resolveVirtualScrollLabels, type VirtualScrollLabelOverrides, type VirtualScrollLocale } from "./labels.ts"
|
|
10
10
|
import { Logger } from "./logger.ts"
|
|
11
|
+
import { capturePointer, releaseCapturedPointer } from "./pointerCapture.ts"
|
|
11
12
|
import { TapScrollCircle, type TapScrollCircleDragState, type TapScrollCircleHandle, type TapScrollCircleRenderProps } from "./TapScrollCircle.tsx"
|
|
12
13
|
import { getAxisScale, minmax } from "./utils.ts"
|
|
13
14
|
|
|
@@ -159,6 +160,40 @@ export type ScrollBarProps = {
|
|
|
159
160
|
enableTrackClick?: boolean
|
|
160
161
|
/** Whether arrow buttons control the scroll position. / 矢印ボタンによるスクロール操作を許可するかどうか。 */
|
|
161
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
|
|
162
197
|
/** Whether the scrollbar is horizontal. / スクロールバーが水平かどうか。 */
|
|
163
198
|
horizontal?: boolean
|
|
164
199
|
/**
|
|
@@ -610,6 +645,7 @@ export const ScrollBar = ({
|
|
|
610
645
|
enableThumbDrag = true,
|
|
611
646
|
enableTrackClick = true,
|
|
612
647
|
enableArrowButtons = true,
|
|
648
|
+
enableArrowButtonTabStops = true,
|
|
613
649
|
horizontal = false,
|
|
614
650
|
enableHorizontalTapCircle = false,
|
|
615
651
|
stretchMainSize = false,
|
|
@@ -859,7 +895,7 @@ export const ScrollBar = ({
|
|
|
859
895
|
*/
|
|
860
896
|
const stepAutoScroll = useCallback(
|
|
861
897
|
(timestamp: number) => {
|
|
862
|
-
// この rAF ループ意味論は useGridTapScroll (グリッド 2 軸ドライバ) と双子 — 片方を直したらもう片方も直すこと
|
|
898
|
+
// この rAF ループ意味論は useGridTapScroll (グリッド 2 軸ドライバ) と双子 — 片方を直したらもう片方も直すこと
|
|
863
899
|
const state = tapDragStateRef.current
|
|
864
900
|
const metrics = latestMetricsRef.current
|
|
865
901
|
if (!state.active || state.direction === 0) {
|
|
@@ -1136,10 +1172,10 @@ export const ScrollBar = ({
|
|
|
1136
1172
|
|
|
1137
1173
|
/**
|
|
1138
1174
|
* Pointer up handler for thumb dragging.
|
|
1139
|
-
* Cleans up drag state and visual feedback.
|
|
1175
|
+
* Cleans up drag state and visual feedback, releasing the capture only when the press acquired one.
|
|
1140
1176
|
*
|
|
1141
1177
|
* つまみドラッグ用のポインタ上げハンドラー。
|
|
1142
|
-
*
|
|
1178
|
+
* ドラッグ状態と視覚的フィードバックをクリーンアップし、押下時にキャプチャを取得できていた場合だけ解放します。
|
|
1143
1179
|
*/
|
|
1144
1180
|
const handleThumbPointerUp = useCallback(
|
|
1145
1181
|
(event: PointerEvent) => {
|
|
@@ -1152,9 +1188,8 @@ export const ScrollBar = ({
|
|
|
1152
1188
|
document.removeEventListener("pointerup", handleThumbPointerUp)
|
|
1153
1189
|
document.removeEventListener("pointercancel", handleThumbPointerUp)
|
|
1154
1190
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
captureTarget.releasePointerCapture(event.pointerId)
|
|
1191
|
+
if (state.captureTarget !== null) {
|
|
1192
|
+
releaseCapturedPointer(state.captureTarget, event.pointerId)
|
|
1158
1193
|
}
|
|
1159
1194
|
|
|
1160
1195
|
thumbDragStateRef.current = { pointerId: null, startThumbPosition: 0, startClientX: 0, startClientY: 0, scale: 1, captureTarget: null }
|
|
@@ -1227,8 +1262,10 @@ export const ScrollBar = ({
|
|
|
1227
1262
|
// スクロールバーのつまみをポインタイベントで処理
|
|
1228
1263
|
/**
|
|
1229
1264
|
* Handles pointer down interaction on the thumb.
|
|
1265
|
+
* Captures the pointer when the element supports it; otherwise the drag continues on the document listeners alone.
|
|
1230
1266
|
*
|
|
1231
1267
|
* つまみ押下時のインタラクションを処理。
|
|
1268
|
+
* 要素がキャプチャに対応していれば取得し、取得できなければ document のリスナーだけでドラッグを続ける。
|
|
1232
1269
|
*/
|
|
1233
1270
|
const handlePointerDownOnThumb = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
1234
1271
|
if (!scrollBarVisible) {
|
|
@@ -1247,18 +1284,10 @@ export const ScrollBar = ({
|
|
|
1247
1284
|
|
|
1248
1285
|
// ポインターキャプチャを取得し、iframe/ウィンドウ外での pointerup 取りこぼしによる
|
|
1249
1286
|
// ドラッグ固着を防ぐ。キャプチャ後もイベントは document までバブリングするため既存の
|
|
1250
|
-
// document
|
|
1287
|
+
// document リスナはそのまま機能する。取得できなかったときは解放対象を持たない。
|
|
1251
1288
|
const element = event.currentTarget
|
|
1252
1289
|
const scale = getMainAxisScale(element)
|
|
1253
|
-
|
|
1254
|
-
if (element.setPointerCapture) {
|
|
1255
|
-
try {
|
|
1256
|
-
element.setPointerCapture(event.pointerId)
|
|
1257
|
-
captureTarget = element
|
|
1258
|
-
} catch {
|
|
1259
|
-
captureTarget = null
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1290
|
+
const captureTarget = capturePointer(element, event.pointerId) === "captured" ? element : null
|
|
1262
1291
|
|
|
1263
1292
|
thumbDragStateRef.current = {
|
|
1264
1293
|
pointerId: event.pointerId,
|
|
@@ -1282,8 +1311,10 @@ export const ScrollBar = ({
|
|
|
1282
1311
|
|
|
1283
1312
|
/**
|
|
1284
1313
|
* Handles pointer down interaction on the track.
|
|
1314
|
+
* Jumps to the pressed position and captures the pointer when the element supports it.
|
|
1285
1315
|
*
|
|
1286
1316
|
* トラック押下時のインタラクションを処理。
|
|
1317
|
+
* 押下位置へ移動し、要素がキャプチャに対応していれば取得する。
|
|
1287
1318
|
*/
|
|
1288
1319
|
const handlePointerDownOnTrack = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
1289
1320
|
if (!scrollBarVisible) {
|
|
@@ -1311,15 +1342,9 @@ export const ScrollBar = ({
|
|
|
1311
1342
|
const initialPosition = translateToScrollPosition(startThumbPosition)
|
|
1312
1343
|
resolveScrollRequest(initialPosition)
|
|
1313
1344
|
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1316
|
-
|
|
1317
|
-
try {
|
|
1318
|
-
element.setPointerCapture(event.pointerId)
|
|
1319
|
-
} catch {
|
|
1320
|
-
// キャプチャ取得失敗時はキャプチャなしで続行 (要素上の pointermove では追従する)。
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1345
|
+
// 取得できなくても (非アクティブ pointerId・API 非対応) ドラッグ状態の設定と preventDefault は続ける。
|
|
1346
|
+
// キャプチャ無しでも要素上の pointermove では追従する。
|
|
1347
|
+
capturePointer(element, event.pointerId)
|
|
1323
1348
|
|
|
1324
1349
|
trackDragStateRef.current = {
|
|
1325
1350
|
pointerId: event.pointerId,
|
|
@@ -1374,19 +1399,17 @@ export const ScrollBar = ({
|
|
|
1374
1399
|
|
|
1375
1400
|
/**
|
|
1376
1401
|
* Handles pointer up interaction on the track.
|
|
1402
|
+
* Releases the capture when held and ends the track drag.
|
|
1377
1403
|
*
|
|
1378
1404
|
* トラックドラッグ終了時の処理。
|
|
1405
|
+
* キャプチャを保持していれば解放し、トラックドラッグを終える。
|
|
1379
1406
|
*/
|
|
1380
1407
|
const handlePointerUpOnTrack = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
1381
1408
|
if (trackDragStateRef.current.pointerId !== event.pointerId) {
|
|
1382
1409
|
return
|
|
1383
1410
|
}
|
|
1384
1411
|
|
|
1385
|
-
|
|
1386
|
-
const element = event.currentTarget
|
|
1387
|
-
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
1388
|
-
element.releasePointerCapture(event.pointerId)
|
|
1389
|
-
}
|
|
1412
|
+
releaseCapturedPointer(event.currentTarget, event.pointerId)
|
|
1390
1413
|
|
|
1391
1414
|
resetTrackDragState()
|
|
1392
1415
|
|
|
@@ -1396,19 +1419,17 @@ export const ScrollBar = ({
|
|
|
1396
1419
|
|
|
1397
1420
|
/**
|
|
1398
1421
|
* Handles pointer cancel interaction on the track.
|
|
1422
|
+
* Releases the capture when held and discards the track drag.
|
|
1399
1423
|
*
|
|
1400
1424
|
* トラックドラッグキャンセル時の処理。
|
|
1425
|
+
* キャプチャを保持していれば解放し、トラックドラッグを破棄する。
|
|
1401
1426
|
*/
|
|
1402
1427
|
const handlePointerCancelOnTrack = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
1403
1428
|
if (trackDragStateRef.current.pointerId !== event.pointerId) {
|
|
1404
1429
|
return
|
|
1405
1430
|
}
|
|
1406
1431
|
|
|
1407
|
-
|
|
1408
|
-
const element = event.currentTarget
|
|
1409
|
-
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
1410
|
-
element.releasePointerCapture(event.pointerId)
|
|
1411
|
-
}
|
|
1432
|
+
releaseCapturedPointer(event.currentTarget, event.pointerId)
|
|
1412
1433
|
|
|
1413
1434
|
resetTrackDragState()
|
|
1414
1435
|
}
|
|
@@ -1450,18 +1471,25 @@ export const ScrollBar = ({
|
|
|
1450
1471
|
}, [tapCircleOffsetX, tapCircleOffsetY, tapCircleSize])
|
|
1451
1472
|
|
|
1452
1473
|
/**
|
|
1453
|
-
* Renders
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
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 ステップ移動。
|
|
1456
1481
|
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
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 / 矢印ボタン要素
|
|
1459
1487
|
*/
|
|
1460
1488
|
const renderArrowButton = (direction: 1 | -1, label: string, icon: string, key: string) => (
|
|
1461
1489
|
<button
|
|
1462
1490
|
key={key}
|
|
1463
1491
|
type="button"
|
|
1464
|
-
tabIndex={0}
|
|
1492
|
+
tabIndex={enableArrowButtonTabStops ? 0 : -1}
|
|
1465
1493
|
className="aqvs-scrollbar-arrow-button"
|
|
1466
1494
|
style={{
|
|
1467
1495
|
[mainSizeKey]: scrollBarWidth,
|