@aiquants/virtualscroll 3.6.0 → 3.7.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 +73 -0
- package/README.md +84 -11
- package/dist/ScrollBar.d.cts +17 -1
- package/dist/ScrollBar.d.ts +17 -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/VirtualGrid.d.cts +17 -1
- package/dist/VirtualGrid.d.ts +17 -1
- package/dist/VirtualGrid.d.ts.map +1 -1
- package/dist/VirtualScroll.d.cts +24 -5
- package/dist/VirtualScroll.d.ts +24 -5
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2046 -1970
- package/dist/labels.d.cts +125 -0
- package/dist/labels.d.ts +126 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/useGridTapScroll.d.cts +1 -1
- package/dist/useGridTapScroll.d.ts +1 -1
- package/dist/useGridTapScroll.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ScrollBar.tsx +21 -4
- package/src/ScrollPane.tsx +19 -0
- package/src/VirtualGrid.tsx +50 -2
- package/src/VirtualScroll.tsx +39 -12
- package/src/index.ts +1 -0
- package/src/labels.ts +236 -0
- package/src/useGridTapScroll.ts +2 -2
package/src/VirtualScroll.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef, type ReactNode, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react"
|
|
2
|
+
import { resolveVirtualScrollLabels, type VirtualScrollLabelOverrides, type VirtualScrollLocale } from "./labels.ts"
|
|
2
3
|
import { Logger } from "./logger.ts"
|
|
3
4
|
import { ScrollPane, type ScrollPaneContentInsets, type ScrollPaneHandle, type ScrollPaneProps } from "./ScrollPane.tsx"
|
|
4
5
|
import { useFenwickMapTree } from "./useFenwickMapTree.ts"
|
|
@@ -224,12 +225,13 @@ export type VirtualScrollLiveRegionOptions = {
|
|
|
224
225
|
/**
|
|
225
226
|
* Formats the announcement text. Called after the visible range settles; returning the same
|
|
226
227
|
* string as last time leaves the DOM untouched (no re-announcement), returning `""` clears
|
|
227
|
-
* the region. The
|
|
228
|
-
*
|
|
228
|
+
* the region. The live-region wording and its language are consumer-owned: the package's
|
|
229
|
+
* built-in catalog (`locale` / `labels`) covers only the seven chrome strings (scrollbar arrow
|
|
230
|
+
* aria-labels, the scroll-to-edge pills and the empty state), never announcements.
|
|
229
231
|
* 読み上げ文言を組み立てる。可視範囲が静定した後に呼ばれ、前回と同じ文字列なら DOM を
|
|
230
|
-
* 触らない (再読み上げしない)。`""`
|
|
231
|
-
*
|
|
232
|
-
*
|
|
232
|
+
* 触らない (再読み上げしない)。`""` を返すとリージョンを空にする。読み上げの文面と言語は
|
|
233
|
+
* 消費側の所有物 — パッケージの内蔵カタログ (`locale` / `labels`) が扱うのはクローム 7 文言
|
|
234
|
+
* (スクロールバー矢印の aria-label、端スクロールピル、空状態) だけで、読み上げは対象外。
|
|
233
235
|
*
|
|
234
236
|
* @param range - The settled visible range / 静定した可視範囲
|
|
235
237
|
* @returns Announcement text / 読み上げ文言
|
|
@@ -310,6 +312,23 @@ export type VirtualScrollProps<T> = {
|
|
|
310
312
|
* その場合は `onRangeChange` から自前のリージョンを更新する従来手段を使う。
|
|
311
313
|
*/
|
|
312
314
|
liveRegion?: VirtualScrollLiveRegionOptions
|
|
315
|
+
/**
|
|
316
|
+
* UI chrome locale of the built-in strings (default `"en"`): the scrollbar arrow aria-labels,
|
|
317
|
+
* the scroll-to-edge pills and the empty-state text. Live-region wording is not affected (see
|
|
318
|
+
* {@link VirtualScrollLiveRegionOptions.format}). An unsupported value throws a RangeError at
|
|
319
|
+
* render; no language negotiation happens.
|
|
320
|
+
* 内蔵文言 (スクロールバー矢印の aria-label、端スクロールピル、空状態文言) の UI クロームロケール
|
|
321
|
+
* (既定 `"en"`)。ライブリージョンの文言には影響しない ({@link VirtualScrollLiveRegionOptions.format}
|
|
322
|
+
* 参照)。非対応値は描画時に RangeError、言語ネゴシエーションなし。
|
|
323
|
+
*/
|
|
324
|
+
locale?: VirtualScrollLocale
|
|
325
|
+
/**
|
|
326
|
+
* Per-key overrides laid over the catalog of `locale`; forwarded to the pane's scrollbar as-is.
|
|
327
|
+
* Unknown keys and blank values throw a RangeError at render.
|
|
328
|
+
* `locale` のカタログへ重ねるキー単位の上書き。ペインのスクロールバーへもそのまま転送。
|
|
329
|
+
* 未知キーと空白値は描画時に RangeError。
|
|
330
|
+
*/
|
|
331
|
+
labels?: VirtualScrollLabelOverrides
|
|
313
332
|
background?: ReactNode
|
|
314
333
|
children: (item: T, index: number) => ReactNode
|
|
315
334
|
initialScrollIndex?: number
|
|
@@ -1023,10 +1042,13 @@ const VirtualScrollInner = <T,>(
|
|
|
1023
1042
|
horizontalKeyStep = DEFAULT_HORIZONTAL_KEY_STEP,
|
|
1024
1043
|
contentProps,
|
|
1025
1044
|
liveRegion,
|
|
1045
|
+
locale,
|
|
1046
|
+
labels,
|
|
1026
1047
|
}: VirtualScrollProps<T>,
|
|
1027
1048
|
ref: React.Ref<VirtualScrollHandle>,
|
|
1028
1049
|
) => {
|
|
1029
1050
|
const { width: scrollBarWidth, enableThumbDrag, enableTrackClick, enableArrowButtons, enableScrollToTopBottomButtons, renderThumbOverlay, tapScrollCircleOptions } = scrollBarOptions ?? {}
|
|
1051
|
+
const resolvedLabels = useMemo(() => resolveVirtualScrollLabels(locale, labels), [locale, labels])
|
|
1030
1052
|
|
|
1031
1053
|
const { enablePointerDrag, pointerDragInputs, enableKeyboardNavigation = true, enableEscapeRowReturn = false, wheelSpeedMultiplier, inertiaOptions, overscrollBehavior, clipItemHeight = false, resetOnGetItemHeightChange = false } = behaviorOptions ?? {}
|
|
1032
1054
|
|
|
@@ -2168,9 +2190,11 @@ const VirtualScrollInner = <T,>(
|
|
|
2168
2190
|
}, [contentSize, renderingEndIndex, renderingStartIndex, resolvedInsets.top, scheduleRangeEffect, scrollPosition, visibleEndIndex, visibleStartIndex])
|
|
2169
2191
|
|
|
2170
2192
|
/**
|
|
2171
|
-
* Renders the auto-hiding
|
|
2193
|
+
* Renders the auto-hiding top/bottom pill overlay (texts from the resolved `scrollToTop` /
|
|
2194
|
+
* `scrollToBottom` labels), neutralized while hidden.
|
|
2172
2195
|
*
|
|
2173
|
-
*
|
|
2196
|
+
* 自動非表示の先頭/末尾ピルのオーバーレイ (文言は解決済みラベルの `scrollToTop` /
|
|
2197
|
+
* `scrollToBottom`) を描画し、非表示中は無効化する処理。
|
|
2174
2198
|
*
|
|
2175
2199
|
* ❗ 非表示は `opacity: 0` で表現するため、要素は DOM に残り続ける (フェードのために
|
|
2176
2200
|
* アンマウントしない)。`opacity` はフォーカス可能性に影響せず、CSS の `pointer-events: none`
|
|
@@ -2213,7 +2237,7 @@ const VirtualScrollInner = <T,>(
|
|
|
2213
2237
|
scrollToIndex(0)
|
|
2214
2238
|
setShowScrollButtons(false)
|
|
2215
2239
|
}}>
|
|
2216
|
-
|
|
2240
|
+
{resolvedLabels.scrollToTop}
|
|
2217
2241
|
</button>
|
|
2218
2242
|
</div>
|
|
2219
2243
|
) : (
|
|
@@ -2229,13 +2253,13 @@ const VirtualScrollInner = <T,>(
|
|
|
2229
2253
|
scrollToIndex(itemCount - 1)
|
|
2230
2254
|
setShowScrollButtons(false)
|
|
2231
2255
|
}}>
|
|
2232
|
-
|
|
2256
|
+
{resolvedLabels.scrollToBottom}
|
|
2233
2257
|
</button>
|
|
2234
2258
|
</div>
|
|
2235
2259
|
)}
|
|
2236
2260
|
</div>
|
|
2237
2261
|
)
|
|
2238
|
-
}, [enableScrollToTopBottomButtons, showScrollButtons, scrollDirection, scrollToIndex, itemCount])
|
|
2262
|
+
}, [enableScrollToTopBottomButtons, showScrollButtons, scrollDirection, scrollToIndex, itemCount, resolvedLabels])
|
|
2239
2263
|
|
|
2240
2264
|
// 量子化アンカー (fix: LayoutUnit/f32 精度対策)。行 top はコンテンツ絶対座標そのままではなく
|
|
2241
2265
|
// 「絶対座標 - アンカー」で描画し、ラッパー側 translateY にアンカーを足し戻す。
|
|
@@ -2258,7 +2282,7 @@ const VirtualScrollInner = <T,>(
|
|
|
2258
2282
|
return {
|
|
2259
2283
|
visibleItems: (
|
|
2260
2284
|
<div className="aqvs-no-items-container">
|
|
2261
|
-
<div className="aqvs-no-items-text">
|
|
2285
|
+
<div className="aqvs-no-items-text">{resolvedLabels.noItems}</div>
|
|
2262
2286
|
</div>
|
|
2263
2287
|
),
|
|
2264
2288
|
renderAnchor: 0,
|
|
@@ -2397,6 +2421,7 @@ const VirtualScrollInner = <T,>(
|
|
|
2397
2421
|
registerItemRef,
|
|
2398
2422
|
renderingEndIndex,
|
|
2399
2423
|
renderingStartIndex,
|
|
2424
|
+
resolvedLabels,
|
|
2400
2425
|
updateScrollPositionImmediate,
|
|
2401
2426
|
visibleStartIndex,
|
|
2402
2427
|
])
|
|
@@ -2588,7 +2613,9 @@ const VirtualScrollInner = <T,>(
|
|
|
2588
2613
|
visibleStartIndex={visibleStartIndex}
|
|
2589
2614
|
visibleEndIndex={visibleEndIndex}
|
|
2590
2615
|
renderOverlay={renderOverlay}
|
|
2591
|
-
initialScrollPosition={initialValues.position}
|
|
2616
|
+
initialScrollPosition={initialValues.position}
|
|
2617
|
+
locale={locale}
|
|
2618
|
+
labels={labels}>
|
|
2592
2619
|
{renderVisibleItems}
|
|
2593
2620
|
</ScrollPane>
|
|
2594
2621
|
)
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export { computeTapScrollVelocity, type TapScrollAxisSpeedParams, type TapScrollVelocityInput } from "./computeTapScrollVelocity.ts"
|
|
9
|
+
export { resolveVirtualScrollLabels, resolveVirtualScrollLocale, VIRTUAL_SCROLL_LABEL_CATALOGS, VIRTUAL_SCROLL_LABEL_KEYS, VIRTUAL_SCROLL_LOCALES, type VirtualScrollLabelOverrides, type VirtualScrollLabels, type VirtualScrollLocale } from "./labels.ts"
|
|
9
10
|
export { createResidualQuantizer, type ResidualQuantizer, type ResidualQuantizerOptions } from "./residualQuantizer.ts"
|
|
10
11
|
export { computeAutoTapScrollMaxSpeedMultiplier, computeTapScrollSpeed, ScrollBar, type ScrollBarProps, type ScrollBarTapCircleOptions, type ScrollBarThumbOverlayRenderProps, TAP_SCROLL_SPEED_DEFAULTS, type TapScrollSpeedInput } from "./ScrollBar.tsx"
|
|
11
12
|
export { ScrollPane, type ScrollPaneContentInsets, type ScrollPaneHandle, type ScrollPaneInertiaOptions, type ScrollPaneProps } from "./ScrollPane.tsx"
|
package/src/labels.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module labels
|
|
3
|
+
* @description Built-in UI chrome label catalog of the package. Covers exactly the seven strings
|
|
4
|
+
* the components render on their own: the ScrollBar arrow aria-labels, the VirtualScroll
|
|
5
|
+
* scroll-to-edge pill texts and the empty-state text. Live-region wording stays consumer-owned
|
|
6
|
+
* (`liveRegion.format` / `liveRegion.buildMessage`) and is not part of this catalog. The default
|
|
7
|
+
* locale is `"en"`; `"ja"` is the second locale. No language negotiation happens here: hosts map
|
|
8
|
+
* `navigator.language` (or anything else) to a supported locale themselves.
|
|
9
|
+
*
|
|
10
|
+
* @description パッケージ内蔵の UI クローム文言カタログ。コンポーネント自身が描画する 7 文言
|
|
11
|
+
* (ScrollBar 矢印の aria-label、VirtualScroll の端スクロールピル文言、空状態文言) だけを対象とし、
|
|
12
|
+
* ライブリージョンの文言は利用側の所有 (`liveRegion.format` / `liveRegion.buildMessage`) で
|
|
13
|
+
* 本カタログの対象外。既定ロケールは `"en"`、第 2 ロケールは `"ja"`。言語ネゴシエーションは行わず、
|
|
14
|
+
* `navigator.language` 等から対応ロケールへの対応付けはホスト側の責務。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Supported UI chrome locales, in declaration order. The first entry is NOT implicitly the
|
|
19
|
+
* default — the default lives only in {@link resolveVirtualScrollLocale}.
|
|
20
|
+
* 対応する UI クロームロケールの一覧 (宣言順)。先頭要素が暗黙の既定値になるわけではなく、
|
|
21
|
+
* 既定値は {@link resolveVirtualScrollLocale} にのみ存在。
|
|
22
|
+
*/
|
|
23
|
+
export const VIRTUAL_SCROLL_LOCALES = ["en", "ja"] as const
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A supported UI chrome locale (`"en"` | `"ja"`).
|
|
27
|
+
* 対応 UI クロームロケール (`"en"` | `"ja"`)。
|
|
28
|
+
*/
|
|
29
|
+
export type VirtualScrollLocale = (typeof VIRTUAL_SCROLL_LOCALES)[number]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The complete set of built-in chrome strings, one value per rendered surface.
|
|
33
|
+
* 内蔵クローム文言の完全な集合 (描画面ごとに 1 値)。
|
|
34
|
+
*/
|
|
35
|
+
export type VirtualScrollLabels = {
|
|
36
|
+
/**
|
|
37
|
+
* aria-label of the vertical ScrollBar start (up) arrow button.
|
|
38
|
+
* 縦 ScrollBar 始端 (上) 矢印ボタンの aria-label。
|
|
39
|
+
*/
|
|
40
|
+
readonly scrollUp: string
|
|
41
|
+
/**
|
|
42
|
+
* aria-label of the vertical ScrollBar end (down) arrow button.
|
|
43
|
+
* 縦 ScrollBar 終端 (下) 矢印ボタンの aria-label。
|
|
44
|
+
*/
|
|
45
|
+
readonly scrollDown: string
|
|
46
|
+
/**
|
|
47
|
+
* aria-label of the horizontal ScrollBar start (left) arrow button.
|
|
48
|
+
* 横 ScrollBar 始端 (左) 矢印ボタンの aria-label。
|
|
49
|
+
*/
|
|
50
|
+
readonly scrollLeft: string
|
|
51
|
+
/**
|
|
52
|
+
* aria-label of the horizontal ScrollBar end (right) arrow button.
|
|
53
|
+
* 横 ScrollBar 終端 (右) 矢印ボタンの aria-label。
|
|
54
|
+
*/
|
|
55
|
+
readonly scrollRight: string
|
|
56
|
+
/**
|
|
57
|
+
* Text of the VirtualScroll scroll-to-top pill, shown when
|
|
58
|
+
* `scrollBarOptions.enableScrollToTopBottomButtons` is on.
|
|
59
|
+
* `scrollBarOptions.enableScrollToTopBottomButtons` 有効時に表示される VirtualScroll 先頭移動ピルの文言。
|
|
60
|
+
*/
|
|
61
|
+
readonly scrollToTop: string
|
|
62
|
+
/**
|
|
63
|
+
* Text of the VirtualScroll scroll-to-bottom pill, shown when
|
|
64
|
+
* `scrollBarOptions.enableScrollToTopBottomButtons` is on.
|
|
65
|
+
* `scrollBarOptions.enableScrollToTopBottomButtons` 有効時に表示される VirtualScroll 末尾移動ピルの文言。
|
|
66
|
+
*/
|
|
67
|
+
readonly scrollToBottom: string
|
|
68
|
+
/**
|
|
69
|
+
* VirtualScroll empty-state text shown when `itemCount === 0`. VirtualGrid shows it too, for
|
|
70
|
+
* 0 rows, an all-frozen row set and degenerate bands (its embedded VirtualScroll gets 0 items).
|
|
71
|
+
* `itemCount === 0` 時の VirtualScroll 空状態文言。VirtualGrid も 0 行・全行固定・退化帯で表示
|
|
72
|
+
* (内蔵 VirtualScroll の項目数が 0 になるため)。
|
|
73
|
+
*/
|
|
74
|
+
readonly noItems: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Partial per-key overrides laid over the catalog of the chosen locale. An `undefined` value keeps
|
|
79
|
+
* the catalog value; any present value must be a string with non-whitespace content.
|
|
80
|
+
* 選択ロケールのカタログへ重ねるキー単位の部分上書き。`undefined` 値はカタログ値を維持し、
|
|
81
|
+
* 値を与える場合は空白以外の文字を含む文字列であることが必須。
|
|
82
|
+
*/
|
|
83
|
+
export type VirtualScrollLabelOverrides = { readonly [K in keyof VirtualScrollLabels]?: VirtualScrollLabels[K] }
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Every key of {@link VirtualScrollLabels}, in catalog order. This is the closed key set that
|
|
87
|
+
* overrides are validated against.
|
|
88
|
+
* {@link VirtualScrollLabels} の全キー (カタログ順)。上書き検証に用いる閉じたキー集合。
|
|
89
|
+
*/
|
|
90
|
+
export const VIRTUAL_SCROLL_LABEL_KEYS = ["scrollUp", "scrollDown", "scrollLeft", "scrollRight", "scrollToTop", "scrollToBottom", "noItems"] as const satisfies readonly (keyof VirtualScrollLabels)[]
|
|
91
|
+
|
|
92
|
+
// satisfies は部分集合しか保証しないため、キー追加時の登録漏れを型で検出する
|
|
93
|
+
const keysCoverEveryLabel: [Exclude<keyof VirtualScrollLabels, (typeof VIRTUAL_SCROLL_LABEL_KEYS)[number]>] extends [never] ? true : never = true
|
|
94
|
+
void keysCoverEveryLabel
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Frozen built-in catalogs per locale. `en` is the package's historical English wording.
|
|
98
|
+
* ロケールごとの凍結済み内蔵カタログ。`en` はパッケージ従来の英語文言。
|
|
99
|
+
*/
|
|
100
|
+
export const VIRTUAL_SCROLL_LABEL_CATALOGS: Readonly<Record<VirtualScrollLocale, VirtualScrollLabels>> = Object.freeze({
|
|
101
|
+
en: Object.freeze({
|
|
102
|
+
scrollUp: "Scroll up",
|
|
103
|
+
scrollDown: "Scroll down",
|
|
104
|
+
scrollLeft: "Scroll left",
|
|
105
|
+
scrollRight: "Scroll right",
|
|
106
|
+
scrollToTop: "Top",
|
|
107
|
+
scrollToBottom: "Bottom",
|
|
108
|
+
noItems: "No items",
|
|
109
|
+
}),
|
|
110
|
+
ja: Object.freeze({
|
|
111
|
+
scrollUp: "上へスクロール",
|
|
112
|
+
scrollDown: "下へスクロール",
|
|
113
|
+
scrollLeft: "左へスクロール",
|
|
114
|
+
scrollRight: "右へスクロール",
|
|
115
|
+
scrollToTop: "先頭へ",
|
|
116
|
+
scrollToBottom: "末尾へ",
|
|
117
|
+
noItems: "項目がありません",
|
|
118
|
+
}),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Type guard: whether `value` is a member of {@link VIRTUAL_SCROLL_LOCALES}.
|
|
123
|
+
* `value` が {@link VIRTUAL_SCROLL_LOCALES} の要素かどうかの型ガード。
|
|
124
|
+
*
|
|
125
|
+
* @param value - Candidate value / 判定対象の値
|
|
126
|
+
* @returns `true` when `value` is a supported locale / 対応ロケールなら `true`
|
|
127
|
+
*/
|
|
128
|
+
const isVirtualScrollLocale = (value: unknown): value is VirtualScrollLocale => (VIRTUAL_SCROLL_LOCALES as readonly unknown[]).includes(value)
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Describes a rejected input for an error message without ever throwing: JSON for strings,
|
|
132
|
+
* `"null"` for `null`, otherwise the `typeof` tag. Never calls `String()` / `JSON.stringify` on
|
|
133
|
+
* arbitrary values, so BigInt, Symbol and null-prototype inputs still produce the documented
|
|
134
|
+
* RangeError with an accurate description.
|
|
135
|
+
* 拒否した入力をエラーメッセージ用に説明 (例外を投げない)。文字列は JSON 表記、`null` は `"null"`、
|
|
136
|
+
* それ以外は `typeof` のタグ。任意値へ `String()` / `JSON.stringify` を適用しないため、BigInt・Symbol・
|
|
137
|
+
* null プロトタイプの入力でも文書どおりの RangeError と正確な説明を維持。
|
|
138
|
+
*
|
|
139
|
+
* @param value - Rejected input / 拒否した入力
|
|
140
|
+
* @returns A short, non-throwing description of `value` / `value` の短い説明 (例外なし)
|
|
141
|
+
*/
|
|
142
|
+
const describeRejectedValue = (value: unknown): string => {
|
|
143
|
+
if (typeof value === "string") {
|
|
144
|
+
return JSON.stringify(value)
|
|
145
|
+
}
|
|
146
|
+
if (value === null) {
|
|
147
|
+
return "null"
|
|
148
|
+
}
|
|
149
|
+
return typeof value
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolves the UI chrome locale. `undefined` (an absent prop) maps to the default `"en"`; a member
|
|
154
|
+
* of {@link VIRTUAL_SCROLL_LOCALES} is returned as-is. This is the single place the default lives.
|
|
155
|
+
* UI クロームロケールの解決。`undefined` (prop 未指定) は既定の `"en"`、
|
|
156
|
+
* {@link VIRTUAL_SCROLL_LOCALES} の要素はそのまま返却。既定値を持つ唯一の場所。
|
|
157
|
+
*
|
|
158
|
+
* @param locale - Requested locale, or `undefined` for the default / 要求ロケール (`undefined` で既定)
|
|
159
|
+
* @returns The resolved supported locale / 解決済みの対応ロケール
|
|
160
|
+
* @throws {RangeError} When `locale` is neither `undefined` nor a supported locale (`null`, `""`,
|
|
161
|
+
* `"EN"`, `"ja-JP"`, `"fr"`, numbers, ...) — no language negotiation /
|
|
162
|
+
* `undefined` でも対応ロケールでもない場合 (`null`、`""`、`"EN"`、`"ja-JP"`、`"fr"`、数値など)。言語ネゴシエーションなし
|
|
163
|
+
*/
|
|
164
|
+
export const resolveVirtualScrollLocale = (locale: VirtualScrollLocale | undefined): VirtualScrollLocale => {
|
|
165
|
+
if (locale === undefined) {
|
|
166
|
+
return "en"
|
|
167
|
+
}
|
|
168
|
+
if (isVirtualScrollLocale(locale)) {
|
|
169
|
+
return locale
|
|
170
|
+
}
|
|
171
|
+
const allowed = VIRTUAL_SCROLL_LOCALES.map((candidate) => JSON.stringify(candidate)).join(", ")
|
|
172
|
+
throw new RangeError(`[virtualscroll] locale must be one of ${allowed}; got ${describeRejectedValue(locale)}`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Type guard: whether `key` is a member of {@link VIRTUAL_SCROLL_LABEL_KEYS}.
|
|
177
|
+
* `key` が {@link VIRTUAL_SCROLL_LABEL_KEYS} の要素かどうかの型ガード。
|
|
178
|
+
*
|
|
179
|
+
* @param key - Candidate own key of an overrides object / 上書きオブジェクトの自身のキー候補
|
|
180
|
+
* @returns `true` when `key` is a label key / ラベルキーなら `true`
|
|
181
|
+
*/
|
|
182
|
+
const isVirtualScrollLabelKey = (key: string): key is keyof VirtualScrollLabels => (VIRTUAL_SCROLL_LABEL_KEYS as readonly string[]).includes(key)
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolves the effective labels: the catalog of the resolved locale overlaid by `overrides`.
|
|
186
|
+
* Returns the frozen catalog itself (same identity) when `overrides` is `undefined`, otherwise a
|
|
187
|
+
* new frozen object. Inputs are never mutated.
|
|
188
|
+
* 実効ラベルの解決。解決済みロケールのカタログへ `overrides` を重ねた結果。`overrides` が
|
|
189
|
+
* `undefined` なら凍結カタログそのもの (同一参照)、それ以外は新しい凍結オブジェクトを返却。入力は不変。
|
|
190
|
+
*
|
|
191
|
+
* @param locale - Requested locale, or `undefined` for the default / 要求ロケール (`undefined` で既定)
|
|
192
|
+
* @param overrides - Per-key overrides, or `undefined` for none / キー単位の上書き (`undefined` で上書きなし)
|
|
193
|
+
* @returns The resolved, frozen labels / 解決済みの凍結ラベル
|
|
194
|
+
* @throws {RangeError} When the locale is unsupported, `overrides` is `null` or not an object,
|
|
195
|
+
* `overrides` is not a plain object (its prototype is neither `Object.prototype` nor `null`, e.g.
|
|
196
|
+
* an array or a class instance), an own key is not a label key, or a present own value is not a
|
|
197
|
+
* string with non-whitespace content. Only own properties are read /
|
|
198
|
+
* ロケールが非対応、`overrides` が `null` または非オブジェクト、`overrides` が素のオブジェクトでない
|
|
199
|
+
* (プロトタイプが `Object.prototype` でも `null` でもない。配列やクラスインスタンスなど)、自身のキーが
|
|
200
|
+
* ラベルキー外、または与えられた自身の値が空白以外の文字を含む文字列でない場合。読むのは自身の
|
|
201
|
+
* プロパティのみ
|
|
202
|
+
*/
|
|
203
|
+
export const resolveVirtualScrollLabels = (locale: VirtualScrollLocale | undefined, overrides: VirtualScrollLabelOverrides | undefined): VirtualScrollLabels => {
|
|
204
|
+
const catalog = VIRTUAL_SCROLL_LABEL_CATALOGS[resolveVirtualScrollLocale(locale)]
|
|
205
|
+
if (overrides === undefined) {
|
|
206
|
+
return catalog
|
|
207
|
+
}
|
|
208
|
+
if (overrides === null || typeof overrides !== "object") {
|
|
209
|
+
throw new RangeError(`[virtualscroll] labels must be an object; got ${describeRejectedValue(overrides)}`)
|
|
210
|
+
}
|
|
211
|
+
// 既知キー検査は自身のキーしか見ないため、継承経路を持つ入力 (配列・クラスインスタンス・
|
|
212
|
+
// 他オブジェクト継承) を許すと継承側の綴り誤りが無言で無視される
|
|
213
|
+
const prototype: unknown = Object.getPrototypeOf(overrides)
|
|
214
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
215
|
+
throw new RangeError(`[virtualscroll] labels must be a plain object (prototype Object.prototype or null); got ${Array.isArray(overrides) ? "an array" : "an object with a non-plain prototype"}`)
|
|
216
|
+
}
|
|
217
|
+
const ownKeys = new Set(Object.keys(overrides))
|
|
218
|
+
for (const key of ownKeys) {
|
|
219
|
+
if (!isVirtualScrollLabelKey(key)) {
|
|
220
|
+
throw new RangeError(`[virtualscroll] labels.${key} is not a known label key; expected one of ${VIRTUAL_SCROLL_LABEL_KEYS.join(", ")}`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const resolved: Record<keyof VirtualScrollLabels, string> = { ...catalog }
|
|
224
|
+
for (const key of VIRTUAL_SCROLL_LABEL_KEYS) {
|
|
225
|
+
// 検査したのと同じ自身のキー集合だけを読み、Object.prototype 汚染で継承されたラベルキーを拾わない
|
|
226
|
+
const value: unknown = ownKeys.has(key) ? overrides[key] : undefined
|
|
227
|
+
if (value === undefined) {
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
230
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
231
|
+
throw new RangeError(`[virtualscroll] labels.${key} must be a non-blank string; got ${describeRejectedValue(value)}`)
|
|
232
|
+
}
|
|
233
|
+
resolved[key] = value
|
|
234
|
+
}
|
|
235
|
+
return Object.freeze(resolved)
|
|
236
|
+
}
|
package/src/useGridTapScroll.ts
CHANGED
|
@@ -63,7 +63,7 @@ export type UseGridTapScrollParams = {
|
|
|
63
63
|
getMaxHx: () => number
|
|
64
64
|
/** 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 — 素のハンドル素通しではない)。 */
|
|
65
65
|
applyVy: (delta: number) => number
|
|
66
|
-
/** Synchronous-fresh vy read. / vy
|
|
66
|
+
/** 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` を過大化し残差を負へ落とすヨーヨー)。 */
|
|
67
67
|
getVy: () => number
|
|
68
68
|
/** y extent read through the embedded handle's freshness channel (never re-derived). / 埋め込みハンドルの鮮度チャネル経由の y 延長 (再導出禁止)。 */
|
|
69
69
|
getMaxVy: () => number
|
|
@@ -142,7 +142,7 @@ export const useGridTapScroll = (params: UseGridTapScrollParams): UseGridTapScro
|
|
|
142
142
|
* @param drive Axis bookkeeping (mutated in place). / 軸別内部状態 (この場で更新)。
|
|
143
143
|
* @param velocity Signed axis velocity (px/s). / 符号付き軸速度 (px/s)。
|
|
144
144
|
* @param dt Frame delta (s). / フレーム時間 (秒)。
|
|
145
|
-
* @param getPos Fresh position read. /
|
|
145
|
+
* @param getPos Fresh position read from the SAME authority `applyDelta` resolves against (the residual feedback `residual -= applied − before` is only exact on one shared authority — v3.6.1). / `applyDelta` が解決するのと同一権威からの位置最新読み (残差フィードバック `residual -= applied − before` は権威共有時のみ正確 — v3.6.1)。
|
|
146
146
|
* @param getMax Fresh extent read. / 延長の最新読み。
|
|
147
147
|
* @param applyDelta Applies a delta and returns the applied position. / 差分を適用し適用後位置を返す。
|
|
148
148
|
* @returns Applied delta and the per-frame boundary flag. / 適用デルタと毎フレーム境界フラグ。
|