@aiquants/virtualscroll 1.20.0 → 1.22.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 +5 -3
- package/dist/ScrollBar.d.cts +123 -0
- package/dist/ScrollBar.d.ts +123 -0
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.cts +16 -0
- package/dist/ScrollPane.d.ts +16 -0
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/VirtualScroll.d.cts +9 -0
- package/dist/VirtualScroll.d.ts +9 -0
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/_headFenwick.d.cts +450 -0
- package/dist/_headFenwick.d.ts +451 -0
- package/dist/_headFenwick.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1468 -1360
- package/dist/styles/virtualscroll.css +1 -1
- package/dist/styles/virtualscroll.standalone.css +1 -1
- package/dist/tapScrollCircleSampleVisual.d.ts.map +1 -1
- package/dist/useFenwickMapTree.d.cts +43 -0
- package/dist/useFenwickMapTree.d.ts +43 -0
- package/dist/useFenwickMapTree.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/ScrollBar.tsx +211 -33
- package/src/ScrollPane.tsx +232 -19
- package/src/VirtualScroll.tsx +11 -1
- package/src/index.ts +1 -1
- package/src/styles/virtualscroll.css +3 -3
- package/src/tapScrollCircleSampleVisual.tsx +22 -5
- package/src/useFenwickMapTree.ts +95 -13
- package/src/ScrollBar.spec.tsx +0 -620
- package/src/ScrollPane.spec.tsx +0 -496
- package/src/TapScrollCircle.spec.tsx +0 -275
- package/src/VirtualScroll.spec.ts +0 -641
- package/src/cli.server.spec.ts +0 -137
- package/src/logger.spec.ts +0 -128
- package/src/useFenwickMapTree.huge.spec.ts +0 -388
- package/src/useFenwickMapTree.spec.ts +0 -1518
- package/src/useLruCache.spec.ts +0 -382
- package/src/utils.spec.ts +0 -39
package/src/useFenwickMapTree.ts
CHANGED
|
@@ -1006,6 +1006,11 @@ export class FenwickMapTree {
|
|
|
1006
1006
|
* @method calculateAccumulatedError
|
|
1007
1007
|
* @description Compares the cached total sum with a theoretical total calculated directly from the source values (`deltas` and `baseValue`, or `valueFn`). This helps detect any discrepancy in the tree's cached state, which might be caused by floating-point errors or other inconsistencies.
|
|
1008
1008
|
* @description キャッシュされている合計値と、元の値 (`deltas` と `baseValue`、または `valueFn`) から直接計算した理論上の合計値とを比較します。これにより、浮動小数点数の累積誤差やその他の不整合によって生じる可能性のある、ツリーのキャッシュ状態の不一致を検出できます。
|
|
1009
|
+
*
|
|
1010
|
+
* ❗ 集計対象は `[0, size)` の範囲内にある delta だけです。`changeSize(0)` は
|
|
1011
|
+
* ロード中の一時クリアとみなして delta を温存する (再伸長時にキャッシュを再利用するため) ので、
|
|
1012
|
+
* 範囲外の delta まで数えると**木は健全なのに空リストの間ずっと非ゼロ**が返り、
|
|
1013
|
+
* この値を破損検知に使う利用者が毎回不要な復旧処理を走らせてしまいます。
|
|
1009
1014
|
* @returns {number} The difference between the cached total and the theoretical total.
|
|
1010
1015
|
*/
|
|
1011
1016
|
calculateAccumulatedError(): number {
|
|
@@ -1014,10 +1019,12 @@ export class FenwickMapTree {
|
|
|
1014
1019
|
return 0
|
|
1015
1020
|
}
|
|
1016
1021
|
|
|
1017
|
-
//
|
|
1022
|
+
// 理論上の合計値を計算する。範囲外へ退避している delta は木に載っていないので数えない
|
|
1018
1023
|
let theoreticalTotal = this.baseValue * this.size
|
|
1019
|
-
for (const delta of this.deltas
|
|
1020
|
-
|
|
1024
|
+
for (const [index, delta] of this.deltas) {
|
|
1025
|
+
if (index < this.size) {
|
|
1026
|
+
theoreticalTotal += delta
|
|
1027
|
+
}
|
|
1021
1028
|
}
|
|
1022
1029
|
|
|
1023
1030
|
// キャッシュされている合計値との差を返す
|
|
@@ -1038,6 +1045,18 @@ export class FenwickMapTree {
|
|
|
1038
1045
|
* サイズ差分のみを受け取りインデックスの対応表は受け取らないため、要素は末尾に追加/末尾から削除
|
|
1039
1046
|
* されると仮定する。既存行は index↔高さ の対応を保つ。したがって中間挿入/削除は表現できず、その
|
|
1040
1047
|
* ような構造変更の後は index↔高さ の対応が保存されず、木の delta が誤った行へ適用されてしまう。
|
|
1048
|
+
* ❗ `changeSize(0)` は例外的に delta を消去しない。ロード中に一覧が一瞬空になるフローで
|
|
1049
|
+
* 実測済みの行高を捨てないための特例で、`changeSize(N > 0)` で再伸長したときに範囲内の delta が
|
|
1050
|
+
* そのまま復元される (範囲外の delta はそこで刈られる)。size が 0 に留まっている間、
|
|
1051
|
+
* `get()` / `getTotal()` は 0 を返し、`calculateAccumulatedError()` は範囲内 delta だけを
|
|
1052
|
+
* 集計するので 0 のままになる。**別データセットへ差し替える場合は温存が害になる**ため、
|
|
1053
|
+
* `reset()` を呼ぶか新しい `key` で作り直すこと。
|
|
1054
|
+
* ❗ `changeSize(0)` intentionally keeps its deltas so a list that briefly empties while loading
|
|
1055
|
+
* does not lose measured row heights; growing back restores the in-range ones (out-of-range
|
|
1056
|
+
* deltas are pruned then). While the size stays 0, `get()` / `getTotal()` return 0 and
|
|
1057
|
+
* `calculateAccumulatedError()` stays 0 because it only sums in-range deltas. Replacing the
|
|
1058
|
+
* dataset instead of reloading the same one must go through `reset()` or a fresh instance.
|
|
1059
|
+
*
|
|
1041
1060
|
* 中間で要素を挿入/削除する消費側は、代わりに木をリセットするか (`useFenwickMapTree` の
|
|
1042
1061
|
* `resetOnValueFnChange`。VirtualScroll では `resetOnGetItemHeightChange` として公開) 、新しい
|
|
1043
1062
|
* `key` でコンポーネントを再マウントすること。
|
|
@@ -1062,16 +1081,40 @@ export class FenwickMapTree {
|
|
|
1062
1081
|
}
|
|
1063
1082
|
|
|
1064
1083
|
if (targetSize > oldSize) {
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
// (
|
|
1069
|
-
|
|
1084
|
+
// ❗ oldSize === 0 からの伸長は増分では表現できない。
|
|
1085
|
+
// `_minCoveredLowBound(0, N)` は 0 を返すため `candidateCount = 0 - 0 = 0` となり、
|
|
1086
|
+
// 候補ループが 1 度も回らず**温存した delta が木へ 1 件も伝播しない**。
|
|
1087
|
+
// その結果 `get()` は delta 込み・`prefixSum()` / `getTotal()` は base のみ、という
|
|
1088
|
+
// 恒久的な乖離が生じ (末尾の整合検査は木由来の 2 値を比べるだけなので検知しない)、
|
|
1089
|
+
// 行の描画高さとスクロール位置の解決が食い違う。木が空なら全再構築で作り直す。
|
|
1090
|
+
if (oldSize === 0 && this.deltas.size > 0) {
|
|
1091
|
+
// 一時クリアで温存した delta のうち、新しいサイズの範囲外にあるものは捨てる。
|
|
1092
|
+
// 残すと、後でさらに伸長したときに**別データの行高**として復活してしまう。
|
|
1093
|
+
// 「伸長後は deltas ⊆ [0, size)」を不変条件として維持する
|
|
1094
|
+
// (`size` に留まったままの範囲外 delta は `calculateAccumulatedError` が集計対象外にする)。
|
|
1095
|
+
for (const index of this.deltas.keys()) {
|
|
1096
|
+
if (index >= targetSize) {
|
|
1097
|
+
this.deltas.delete(index)
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
this.size = targetSize
|
|
1101
|
+
this.rebuildTree()
|
|
1102
|
+
} else {
|
|
1103
|
+
// 末尾への伸長は増分更新で済ませる (全再構築 O(D log n) を回避)。
|
|
1104
|
+
// 既存ノード (treeIndex <= oldSize) は不変であり、新規要素は delta 0 なので、
|
|
1105
|
+
// oldSize で伝播が止まっていた既存 delta を新たに有効化されたノード
|
|
1106
|
+
// (oldSize, targetSize] にのみ伝播すればよい。
|
|
1107
|
+
this._growSize(oldSize, targetSize)
|
|
1108
|
+
}
|
|
1070
1109
|
} else {
|
|
1071
|
-
// サイズが小さくなる場合、範囲外の delta
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1110
|
+
// サイズが小さくなる場合、範囲外の delta を削除してからツリーを再構築する。
|
|
1111
|
+
// ただし targetSize が 0 の場合は、ロード中の空配列への一時的クリアとみなし、
|
|
1112
|
+
// キャッシュ温存のため deltas を消去せずに残す。
|
|
1113
|
+
if (targetSize > 0) {
|
|
1114
|
+
for (const index of this.deltas.keys()) {
|
|
1115
|
+
if (index >= targetSize) {
|
|
1116
|
+
this.deltas.delete(index)
|
|
1117
|
+
}
|
|
1075
1118
|
}
|
|
1076
1119
|
}
|
|
1077
1120
|
this.size = targetSize
|
|
@@ -1199,6 +1242,35 @@ export class FenwickMapTree {
|
|
|
1199
1242
|
return this.size
|
|
1200
1243
|
}
|
|
1201
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* @description Gets the base value of the tree — the height every row starts from before
|
|
1247
|
+
* its own delta is applied. `useFenwickMapTree` reads it to decide whether a `0 → N` growth
|
|
1248
|
+
* should re-sample from scratch (`reset`) or reuse the measured heights kept across the
|
|
1249
|
+
* empty state (`changeSize`), so removing or renaming it silently disables that cache
|
|
1250
|
+
* preservation.
|
|
1251
|
+
*
|
|
1252
|
+
* ❗ This is NOT a "has the tree warmed up?" flag. `0` is a legitimate base — it is what a
|
|
1253
|
+
* tree constructed with `new FenwickMapTree(n, 0)` has, and what sampling produces when the
|
|
1254
|
+
* value function returns 0 for not-yet-measured rows (a common virtualization pattern).
|
|
1255
|
+
* Such a tree can hold plenty of measured deltas while `base` stays `0`. The hook's
|
|
1256
|
+
* `0 → N` guard therefore trades cache preservation for correctness in exactly that case:
|
|
1257
|
+
* a base of `0` cannot be distinguished from "never established", and growing incrementally
|
|
1258
|
+
* from a zero base would pin `total` at `0 × N = 0`.
|
|
1259
|
+
* @description ツリーの基準値 (各行が自身の delta を適用される前の高さ) を取得。
|
|
1260
|
+
* `useFenwickMapTree` は `0 → N` の伸長を再サンプリング (`reset`) と実測高さの温存 (`changeSize`) の
|
|
1261
|
+
* どちらへ倒すかの判定にこの値を読むため、削除・改名するとキャッシュ温存が黙って無効化される。
|
|
1262
|
+
*
|
|
1263
|
+
* ❗ **「木が温まったか」のフラグではない。** `0` は正当な基準値であり、
|
|
1264
|
+
* `new FenwickMapTree(n, 0)` や「未計測行に 0 を返す `getItemHeight`」(仮想化では定番) の
|
|
1265
|
+
* サンプリング結果として普通に発生する。その木は実測 delta を大量に持っていても `base` は `0` のまま。
|
|
1266
|
+
* フックの `0 → N` ガードは、まさにこの場合にキャッシュ温存より整合を優先する
|
|
1267
|
+
* (基準値 0 は「未確立」と区別できず、0 のまま増分伸長すると `total = 0 × N = 0` で固着するため)。
|
|
1268
|
+
* @returns {number} The base value. 基準値。
|
|
1269
|
+
*/
|
|
1270
|
+
get base(): number {
|
|
1271
|
+
return this.baseValue
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1202
1274
|
/**
|
|
1203
1275
|
* @method findIndexAtOrAfter
|
|
1204
1276
|
* @description Finds the first index where the cumulative sum is greater than or equal to a target value.
|
|
@@ -1316,7 +1388,17 @@ export const useFenwickMapTree = (size: number, valueOrFn: number | ((index: num
|
|
|
1316
1388
|
// 旧 size が 0 の木は baseValue が未確立 (0) のまま。増分伸長 (changeSize) では
|
|
1317
1389
|
// total = 0 * N = 0 で固着するため、0 -> N の伸長は resetOnValueFnChange の設定に
|
|
1318
1390
|
// 関わらず常に reset (サンプリング込み) へ倒す。sizeChanged のみの分岐と前提を統一する。
|
|
1319
|
-
|
|
1391
|
+
// ただし、以前に baseValue が確立されている(base > 0)かつ deltas が維持されている場合は、
|
|
1392
|
+
// reset を回避して changeSize (増分伸長) に倒し、蓄積された高さキャッシュを再利用する。
|
|
1393
|
+
//
|
|
1394
|
+
// ❗ **温存は「同じデータが読み込み中に一瞬 0 件になった」場合を想定している。**
|
|
1395
|
+
// N -> 0 -> M が**別データセットへの差し替え**だった場合、baseValue も実測 delta も
|
|
1396
|
+
// 旧データのものが引き継がれる (描画ウィンドウ内の行は次レンダーの高さ照合で
|
|
1397
|
+
// getItemHeight へ巻き戻るが、ウィンドウ外の行は巻き戻らないため総高さ・つまみ長・
|
|
1398
|
+
// scrollToIndex の着地点が食い違う)。行高が可変で、かつ 0 件を経由して中身が入れ替わる
|
|
1399
|
+
// 消費側は `resetOnValueFnChange` (VirtualScroll では `resetOnGetItemHeightChange`) を
|
|
1400
|
+
// 有効にするか、新しい `key` で再マウントすること。
|
|
1401
|
+
const growingFromZero = prev.size === 0 && validSize > 0 && (!treeRef.current || treeRef.current.base === 0)
|
|
1320
1402
|
|
|
1321
1403
|
if (valueOrFnChanged || optionsChanged) {
|
|
1322
1404
|
const resetRequested = options?.resetOnValueFnChange ?? true
|