@aiquants/virtualscroll 3.1.0 → 3.4.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 +137 -0
- package/README.md +77 -3
- package/dist/ScrollBar.d.cts +9 -1
- package/dist/ScrollBar.d.ts +9 -1
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.cts +13 -0
- package/dist/ScrollPane.d.ts +13 -0
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/TapScrollCircle.d.cts +6 -0
- package/dist/TapScrollCircle.d.ts +6 -0
- package/dist/TapScrollCircle.d.ts.map +1 -1
- package/dist/VirtualGrid.d.cts +370 -0
- package/dist/VirtualGrid.d.ts +371 -0
- package/dist/VirtualGrid.d.ts.map +1 -0
- package/dist/VirtualScroll.d.cts +47 -0
- package/dist/VirtualScroll.d.ts +47 -0
- 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 +2369 -1848
- package/dist/styles/virtualscroll.css +1 -1
- package/dist/styles/virtualscroll.standalone.css +1 -1
- package/dist/useFenwickMapTree.d.cts +7 -4
- package/dist/useFenwickMapTree.d.ts +7 -4
- package/dist/useFenwickMapTree.d.ts.map +1 -1
- package/dist/utils.d.cts +15 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ScrollBar.tsx +18 -33
- package/src/ScrollPane.tsx +62 -4
- package/src/TapScrollCircle.tsx +13 -4
- package/src/VirtualGrid.tsx +1486 -0
- package/src/VirtualScroll.tsx +30 -6
- package/src/index.ts +1 -0
- package/src/styles/virtualscroll.css +120 -0
- package/src/useFenwickMapTree.ts +34 -12
- package/src/utils.ts +42 -0
package/src/VirtualScroll.tsx
CHANGED
|
@@ -338,6 +338,20 @@ export type VirtualScrollProps<T> = {
|
|
|
338
338
|
* ここへ流れる。名前は 2.x で公開済みのため据え置くが、意味は「横軸が N px 動いた。横軸はあなたの所有物である」。
|
|
339
339
|
*/
|
|
340
340
|
onWheelHorizontal?: (deltaX: number) => void
|
|
341
|
+
/**
|
|
342
|
+
* Delegates the horizontal component of a pointer pan (touch / pen drag) upstream — the pan
|
|
343
|
+
* twin of `onWheelHorizontal`, same sign convention (positive = scroll right), per-move
|
|
344
|
+
* increments divided by the press-time x-axis ancestor scale (v3.2.0 VirtualGrid seam).
|
|
345
|
+
* ❗ Belongs to the SAME horizontal-seam family as `onWheelHorizontal` and is deliberately a
|
|
346
|
+
* top-level prop: wrappers that seal the horizontal axis with `Omit<>` (e.g.
|
|
347
|
+
* `@aiquants/directory-tree`) must add this name to their seal list on the 3.2.0 bump
|
|
348
|
+
* (the pass-through review their seal guard mandates).
|
|
349
|
+
* ポインタパン (タッチ / ペン) の横成分を上流へ委譲する — `onWheelHorizontal` のパン対
|
|
350
|
+
* (符号同一・per-move 増分・押下時横 scale 除算、v3.2.0 VirtualGrid シーム)。横シーム
|
|
351
|
+
* ファミリの一員として意図的にトップレベル: `Omit<>` で横軸を封じるラッパーは 3.2.0
|
|
352
|
+
* 更新時に本名を封印リストへ追加すること (封印ガードが義務化する pass-through review)。
|
|
353
|
+
*/
|
|
354
|
+
onPanHorizontal?: (deltaX: number) => void
|
|
341
355
|
/**
|
|
342
356
|
* Keyboard gestures that emit a horizontal scroll delta through `onWheelHorizontal` (default: none).
|
|
343
357
|
* `onWheelHorizontal` へ横スクロール量を流すキーボード操作の種別 (既定: 無効)。
|
|
@@ -453,20 +467,28 @@ const toPanePositionWithInset = (logical: number, top: number) => (logical <= 0
|
|
|
453
467
|
|
|
454
468
|
/**
|
|
455
469
|
* Maximum run length of consecutive zero-height rows scanned linearly before attempting an
|
|
456
|
-
* O(log n) jump to the next non-zero row via the Fenwick tree.
|
|
470
|
+
* O(log n) jump to the next non-zero row via the Fenwick tree. Module-level export (NOT in the
|
|
471
|
+
* package barrel) so VirtualGrid's column axis shares the SAME constant instead of duplicating
|
|
472
|
+
* the value (the §5 no-copy rule — same treatment as ANCHOR_REBASE_DISTANCE).
|
|
457
473
|
*
|
|
458
|
-
* 高さ 0 行の連続をこの件数まで線形走査し、超えたら Fenwick 木で次の非 0 行へ O(log n)
|
|
474
|
+
* 高さ 0 行の連続をこの件数まで線形走査し、超えたら Fenwick 木で次の非 0 行へ O(log n) ジャンプ
|
|
475
|
+
* する閾値。モジュールレベル export (バレル非公開) — VirtualGrid 列軸が値の複製でなく同一定数を
|
|
476
|
+
* 共有するため (§5 複製禁止規範 — ANCHOR_REBASE_DISTANCE と同じ扱い)。
|
|
459
477
|
*/
|
|
460
|
-
const ZERO_HEIGHT_RUN_LIMIT = 1000
|
|
478
|
+
export const ZERO_HEIGHT_RUN_LIMIT = 1000
|
|
461
479
|
|
|
462
480
|
/**
|
|
463
481
|
* Safety cap for the number of item nodes materialized in a single render pass.
|
|
464
482
|
* Guards against pathological rendering ranges (e.g. a huge contiguous run of zero-height rows).
|
|
483
|
+
* Module-level export (NOT in the package barrel) — VirtualGrid's rendered-row estimator caps
|
|
484
|
+
* its materialized-row walk with the SAME constant (zero-height rows DO materialize as DOM rows).
|
|
465
485
|
*
|
|
466
486
|
* 1 回のレンダーで具現化するアイテムノード数の安全上限。
|
|
467
|
-
* 病的な描画範囲 (巨大な高さ 0 行の連続など)
|
|
487
|
+
* 病的な描画範囲 (巨大な高さ 0 行の連続など) からの防御。モジュールレベル export (バレル非公開) —
|
|
488
|
+
* VirtualGrid の描画行見積りが具現化行ウォークの上限として同一定数を共有する
|
|
489
|
+
* (高さ 0 行も DOM 行として具現化されるため)。
|
|
468
490
|
*/
|
|
469
|
-
const MAX_RENDERED_ITEMS = 2000
|
|
491
|
+
export const MAX_RENDERED_ITEMS = 2000
|
|
470
492
|
|
|
471
493
|
/**
|
|
472
494
|
* Distance (px) between the rendering window start and the current render anchor beyond which
|
|
@@ -477,7 +499,7 @@ const MAX_RENDERED_ITEMS = 2000
|
|
|
477
499
|
* 行 top とラッパー translateY をブラウザのレイアウト座標上限 (LayoutUnit 約 2^25 px) と
|
|
478
500
|
* compositor の f32 精度内に収めるための量子化距離。
|
|
479
501
|
*/
|
|
480
|
-
const ANCHOR_REBASE_DISTANCE = 1_048_576 // 2^20 px
|
|
502
|
+
export const ANCHOR_REBASE_DISTANCE = 1_048_576 // 2^20 px — VirtualGrid の横アンカーが共有 import する (パッケージバレルへは非公開)
|
|
481
503
|
|
|
482
504
|
/**
|
|
483
505
|
* Pixels emitted per horizontal arrow key press. Matches the ~40px browsers scroll for an arrow key.
|
|
@@ -996,6 +1018,7 @@ const VirtualScrollInner = <T,>(
|
|
|
996
1018
|
scrollBarOptions,
|
|
997
1019
|
behaviorOptions,
|
|
998
1020
|
onWheelHorizontal,
|
|
1021
|
+
onPanHorizontal,
|
|
999
1022
|
horizontalKeyInputs,
|
|
1000
1023
|
horizontalKeyStep = DEFAULT_HORIZONTAL_KEY_STEP,
|
|
1001
1024
|
contentProps,
|
|
@@ -2559,6 +2582,7 @@ const VirtualScrollInner = <T,>(
|
|
|
2559
2582
|
renderThumbOverlay={renderThumbOverlay}
|
|
2560
2583
|
wheelSpeedMultiplier={wheelSpeedMultiplier}
|
|
2561
2584
|
onWheelHorizontal={onWheelHorizontal}
|
|
2585
|
+
onPanHorizontal={onPanHorizontal}
|
|
2562
2586
|
contentInsets={resolvedInsets}
|
|
2563
2587
|
contentProps={contentProps}
|
|
2564
2588
|
visibleStartIndex={visibleStartIndex}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { useHeightCache } from "./useHeightCache.ts"
|
|
|
15
15
|
export { useLruCache } from "./useLruCache.ts"
|
|
16
16
|
export { useWheelBridge, type WheelBridgeOptions, type WheelBridgeTarget } from "./useWheelBridge.ts"
|
|
17
17
|
export { minmax } from "./utils.ts"
|
|
18
|
+
export { MAX_FROZEN_LEADING_COLS, MAX_FROZEN_LEADING_ROWS, MAX_RENDERED_CELLS, MAX_TRACK_SIZE, VirtualGrid, type VirtualGridBehaviorOptions, type VirtualGridHandle, type VirtualGridLiveRegionOptions, type VirtualGridProps, type VirtualGridRange } from "./VirtualGrid.tsx"
|
|
18
19
|
export {
|
|
19
20
|
VirtualScroll,
|
|
20
21
|
type VirtualScrollBehaviorOptions,
|
|
@@ -348,3 +348,123 @@
|
|
|
348
348
|
clip-path: inset(50%);
|
|
349
349
|
white-space: nowrap;
|
|
350
350
|
}
|
|
351
|
+
|
|
352
|
+
/* ============================================================
|
|
353
|
+
* VirtualGrid (v3.2.0) — 汎用 2D 仮想化。横軸は残差 transform
|
|
354
|
+
* (--aqvs-grid-hx-residual — 書き手は VirtualGrid の root 直書きのみ。
|
|
355
|
+
* 消費者は .aqvs-grid-row の translateX、凍結モード (v3.3.0) では
|
|
356
|
+
* .aqvs-grid-row-scroll-inner の calc(残差 − W_F))。
|
|
357
|
+
* grid-template-rows の minmax(0, 1fr) は必須 — 素の 1fr は暗黙
|
|
358
|
+
* min-content 下限で縮小追随が壊れる (既知クラスの再発防止)。
|
|
359
|
+
* ============================================================ */
|
|
360
|
+
.aqvs-grid {
|
|
361
|
+
position: relative;
|
|
362
|
+
display: grid;
|
|
363
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
364
|
+
width: 100%;
|
|
365
|
+
height: 100%;
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* 凍結行帯 (行凍結設計 §4-2): R > 0 のときだけルートへ行を 1 本足す (auto = H_F)。
|
|
370
|
+
* R = 0 はクラスも帯要素も出力されず従来テンプレートと恒等 (F = 0 恒等契約の行版)。 */
|
|
371
|
+
.aqvs-grid-has-frozen-rows {
|
|
372
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.aqvs-grid-frozen-rows {
|
|
376
|
+
position: relative;
|
|
377
|
+
min-width: 0;
|
|
378
|
+
overflow: hidden;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* 帯行の中身 (.aqvs-grid-row — height 100%) は帯行枠へそのまま追随する (VirtualScroll の
|
|
382
|
+
* item-container 内と同じ前提が成立するため追加宣言は不要)。 */
|
|
383
|
+
.aqvs-grid-frozen-band-row {
|
|
384
|
+
position: absolute;
|
|
385
|
+
left: 0;
|
|
386
|
+
/* right はインライン (= scrollBarWidth — 縦バーコーナーの予約。ペインのクリップ箱と一致させ、
|
|
387
|
+
* 帯行のセル / 消費側背景がバー上へはみ出さない)。overflow はその実クリップ器。 */
|
|
388
|
+
overflow: hidden;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.aqvs-grid-main {
|
|
392
|
+
position: relative;
|
|
393
|
+
min-width: 0;
|
|
394
|
+
min-height: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* 行ラッパー — セルの containing block、かつ横残差の消費者。
|
|
398
|
+
* 変数は残差のみ (生 hx は配らない — 深部の f32/LayoutUnit 破綻の根治)。
|
|
399
|
+
* ❗ 消費者はグリッド所有のこのクラスに限る: contentProps className 経由でペインの
|
|
400
|
+
* コンテンツ要素へ載せる方式は、ScrollPane が contentProps を構造クラスより後に
|
|
401
|
+
* 展開するため `aqvs-scroll-pane-content` (overflow/position/flex) を破壊する。
|
|
402
|
+
* クリップはペインのコンテンツ要素 (overflow: hidden) が従来どおり担う。
|
|
403
|
+
* 入れ子の素の VirtualScroll / 内側の VirtualGrid には波及しない (このクラスは
|
|
404
|
+
* VirtualGrid の renderRow だけが出力し、変数はそれぞれの root でスコープされる)。 */
|
|
405
|
+
.aqvs-grid-row {
|
|
406
|
+
position: relative;
|
|
407
|
+
width: 100%;
|
|
408
|
+
height: 100%;
|
|
409
|
+
overflow: visible;
|
|
410
|
+
transform: translateX(var(--aqvs-grid-hx-residual, 0px));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* セル — left は「列絶対座標 − 列アンカー」(常に有界)。 */
|
|
414
|
+
.aqvs-grid-cell {
|
|
415
|
+
position: absolute;
|
|
416
|
+
top: 0;
|
|
417
|
+
height: 100%;
|
|
418
|
+
overflow: hidden;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* 凍結モード (v3.3.0 — 設計 §8): 行は静的化する (残差はスクロール帯 inner が消費)。
|
|
422
|
+
* 複合セレクタ (0,2,0) は意図的 — .aqvs-grid-row (0,1,0) の translateX をソース順序に
|
|
423
|
+
* 依存せず常に上書きする (ルール並べ替えで沈黙退行しない特異度設計)。 */
|
|
424
|
+
.aqvs-grid-row.aqvs-grid-row-frozen-host {
|
|
425
|
+
transform: none;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* スクロール帯の静的クリップ (§8-b/c): 左端 = W_F。transform を持つ要素は自分のクリップ箱ごと
|
|
429
|
+
* 動くため、クリップ (この要素) と残差 translate (子の inner) は必ず分離する。 */
|
|
430
|
+
.aqvs-grid-row-scroll {
|
|
431
|
+
position: absolute;
|
|
432
|
+
top: 0;
|
|
433
|
+
height: 100%;
|
|
434
|
+
left: var(--aqvs-grid-frozen-width, 0px);
|
|
435
|
+
right: 0;
|
|
436
|
+
overflow: hidden;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* スクロール帯の座標ホルダー — 残差 − W_F の原点シフト項 (§8-b)。クリップ原点が W_F にあるため
|
|
440
|
+
* セル最終位置は (列絶対 − colAnchor) + (colAnchor − hx − W_F) + W_F = 列絶対 − hx で不変。 */
|
|
441
|
+
.aqvs-grid-row-scroll-inner {
|
|
442
|
+
position: absolute;
|
|
443
|
+
top: 0;
|
|
444
|
+
height: 100%;
|
|
445
|
+
left: 0;
|
|
446
|
+
width: 100%;
|
|
447
|
+
transform: translateX(calc(var(--aqvs-grid-hx-residual, 0px) - var(--aqvs-grid-frozen-width, 0px)));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* 横バー帯。 */
|
|
451
|
+
.aqvs-grid-hbar-strip {
|
|
452
|
+
position: relative;
|
|
453
|
+
min-width: 0;
|
|
454
|
+
/* 凍結列帯の整列 (実証是正): トラック原点をスクロール帯開始 (W_F) へ。F = 0 は変数不在で
|
|
455
|
+
* 0px — DOM / 描画とも従来恒等 (インライン属性を足さないため §8 の恒等契約を保つ)。 */
|
|
456
|
+
padding-left: var(--aqvs-grid-frozen-width, 0px);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/* 読み上げ用リージョン (視覚非表示 — sr-only 相当)。 */
|
|
460
|
+
.aqvs-grid-live-region {
|
|
461
|
+
position: absolute;
|
|
462
|
+
width: 1px;
|
|
463
|
+
height: 1px;
|
|
464
|
+
margin: -1px;
|
|
465
|
+
padding: 0;
|
|
466
|
+
overflow: hidden;
|
|
467
|
+
clip: rect(0 0 0 0);
|
|
468
|
+
white-space: nowrap;
|
|
469
|
+
border: 0;
|
|
470
|
+
}
|
package/src/useFenwickMapTree.ts
CHANGED
|
@@ -160,9 +160,9 @@ export class FenwickMapTree {
|
|
|
160
160
|
* @description Fenwick Tree の初期化。
|
|
161
161
|
* @param {number} size - The total number of items.
|
|
162
162
|
* @param {number | ((index: number) => number)} valueOrFn - The value for all elements, or a function to generate values.
|
|
163
|
-
* @param {{ sampleRange?: { from: number; to: number }, materialize?: boolean }} [options] - Optional settings for initialization.
|
|
163
|
+
* @param {{ sampleRange?: { from: number; to: number }, materialize?: boolean, baseValue?: number }} [options] - Optional settings for initialization (`baseValue` skips sampling — see reset). / 初期化オプション (`baseValue` はサンプリング省略 — reset 参照)。
|
|
164
164
|
*/
|
|
165
|
-
constructor(size: number, valueOrFn: number | ((index: number) => number), options?: { sampleRange?: { from: number; to: number }; materialize?: boolean }) {
|
|
165
|
+
constructor(size: number, valueOrFn: number | ((index: number) => number), options?: { sampleRange?: { from: number; to: number }; materialize?: boolean; baseValue?: number }) {
|
|
166
166
|
this.reset(size, valueOrFn, options)
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -179,9 +179,9 @@ export class FenwickMapTree {
|
|
|
179
179
|
* throw しつつ木を半構築で残さず直前の状態を無傷に保つ方針。
|
|
180
180
|
* @param {number} size - The total number of items.
|
|
181
181
|
* @param {number | ((index: number) => number)} valueOrFn - The value for all elements, or a function to generate values.
|
|
182
|
-
* @param {{ sampleRange?: { from: number; to: number }, materialize?: boolean }} [options] - Optional settings for initialization.
|
|
182
|
+
* @param {{ sampleRange?: { from: number; to: number }, materialize?: boolean, baseValue?: number }} [options] - Optional settings for initialization. `baseValue` (finite, >= 0 — RangeError otherwise) skips all sampling and fixes the uniform estimate for unmaterialized items (v3.2.0, the VirtualGrid defaultColWidth path). / 初期化オプション。`baseValue` (有限かつ 0 以上 — 違反は RangeError) は全サンプリングを省略し未具現化アイテムの見積りを固定する (v3.2.0、VirtualGrid defaultColWidth 経路)。
|
|
183
183
|
*/
|
|
184
|
-
reset(size: number, valueOrFn: number | ((index: number) => number), options?: { sampleRange?: { from: number; to: number }; materialize?: boolean }) {
|
|
184
|
+
reset(size: number, valueOrFn: number | ((index: number) => number), options?: { sampleRange?: { from: number; to: number }; materialize?: boolean; baseValue?: number }) {
|
|
185
185
|
// size を正規化する: 非有限 (NaN/Infinity)・非正は 0、小数は切り捨て。
|
|
186
186
|
// NaN size は `this.size > 0` 系の判定をすり抜けて total を NaN 汚染するため入口で遮断する。
|
|
187
187
|
const safeSize = Number.isFinite(size) && size > 0 ? Math.trunc(size) : 0
|
|
@@ -201,7 +201,27 @@ export class FenwickMapTree {
|
|
|
201
201
|
// (半構築状態を残さない)。検証済みの値だけを後段のコミットフェーズで木へ流し込む。
|
|
202
202
|
let baseValue = 0
|
|
203
203
|
let seed: { from: number; values: number[] } | undefined
|
|
204
|
-
|
|
204
|
+
// 明示 baseValue (v3.2.0): サンプリングを丸ごと省略し、未具現化アイテムの見積りを
|
|
205
|
+
// 呼び出し側が確定させる (VirtualGrid の defaultColWidth 経路)。非有限・負は fail-fast
|
|
206
|
+
// (黙って 0 やサンプリングへ読み替えない)
|
|
207
|
+
const explicitBase = options?.baseValue
|
|
208
|
+
if (explicitBase !== undefined && !(Number.isFinite(explicitBase) && explicitBase >= 0)) {
|
|
209
|
+
throw new RangeError(`[FenwickMapTree] options.baseValue must be a finite number >= 0, received ${explicitBase}.`)
|
|
210
|
+
}
|
|
211
|
+
if (safeSize > 0 && explicitBase !== undefined) {
|
|
212
|
+
baseValue = explicitBase
|
|
213
|
+
// 具現化要求は従来どおり初期可視ウィンドウのみ対象 (正確性の維持)
|
|
214
|
+
if (options?.materialize) {
|
|
215
|
+
const range = options?.sampleRange ?? { from: 0, to: Math.min(SAMPLE_COUNT - 1, safeSize - 1) }
|
|
216
|
+
let from = Math.max(0, Math.trunc(range.from))
|
|
217
|
+
let to = Math.min(Math.trunc(range.to), safeSize - 1)
|
|
218
|
+
if (from > to) {
|
|
219
|
+
from = 0
|
|
220
|
+
to = Math.min(SAMPLE_COUNT - 1, safeSize - 1)
|
|
221
|
+
}
|
|
222
|
+
seed = { from, values: FenwickMapTree._collectValidatedValues(valueOrFn, from, to) }
|
|
223
|
+
}
|
|
224
|
+
} else if (safeSize > 0) {
|
|
205
225
|
// 初期可視ウィンドウ (= 具現化対象) の範囲を決定する。
|
|
206
226
|
// baseValue の推定範囲とは分離する: 具現化は利用者が最初に見る領域だけ正確化すればよいが、
|
|
207
227
|
// baseValue は未具現化の全アイテムの総高さ推定に効くため、リスト全体を代表する値が必要。
|
|
@@ -1358,12 +1378,12 @@ const sampleRangeChanged = (a?: { sampleRange?: { from: number; to: number } },
|
|
|
1358
1378
|
* @param {number} size - The total number of items.
|
|
1359
1379
|
* @param {number | ((index: number) => number)} valueOrFn - The value for all elements, or a function to generate values.
|
|
1360
1380
|
* @param {number | ((index: number) => number)} valueOrFn - 全要素の均一な値、または値を生成する関数。不要なツリーの再作成を防ぐため、この関数は `useCallback` でメモ化すること。
|
|
1361
|
-
* @param {{ sampleRange?: { from: number; to: number }, resetOnValueFnChange?: boolean, debug?: boolean }} [options] - Optional settings.
|
|
1362
|
-
* @param {{ sampleRange?: { from: number; to: number } }} [options] -
|
|
1381
|
+
* @param {{ sampleRange?: { from: number; to: number }, resetOnValueFnChange?: boolean, debug?: boolean, baseValue?: number }} [options] - Optional settings (`baseValue` skips sampling — see FenwickMapTree.reset). / オプション (`baseValue` はサンプリング省略 — FenwickMapTree.reset 参照)。
|
|
1382
|
+
* @param {{ sampleRange?: { from: number; to: number }, resetOnValueFnChange?: boolean, debug?: boolean, baseValue?: number }} [options] - 初期化時のオプション設定 (`baseValue` はサンプリング省略 — FenwickMapTree.reset 参照)。不要なツリーの再作成を防ぐため、このオブジェクトは `useMemo` でメモ化すること。
|
|
1363
1383
|
* @returns {FenwickMapTree} The FenwickMapTree instance.
|
|
1364
1384
|
* @returns {FenwickMapTree} FenwickMapTree インスタンス。
|
|
1365
1385
|
*/
|
|
1366
|
-
export const useFenwickMapTree = (size: number, valueOrFn: number | ((index: number) => number), options?: { sampleRange?: { from: number; to: number }; debug?: boolean; resetOnValueFnChange?: boolean }): FenwickMapTree => {
|
|
1386
|
+
export const useFenwickMapTree = (size: number, valueOrFn: number | ((index: number) => number), options?: { sampleRange?: { from: number; to: number }; debug?: boolean; resetOnValueFnChange?: boolean; baseValue?: number }): FenwickMapTree => {
|
|
1367
1387
|
// size を正規化する: 非有限 (NaN は Math.max(0, NaN) を素通りする)・非正は 0、小数は切り捨て。
|
|
1368
1388
|
// reset / changeSize へ渡る前にここで遮断し、木の内部を NaN 汚染させない (reset と同一規則)。
|
|
1369
1389
|
const validSize = Number.isFinite(size) && size > 0 ? Math.trunc(size) : 0
|
|
@@ -1380,10 +1400,12 @@ export const useFenwickMapTree = (size: number, valueOrFn: number | ((index: num
|
|
|
1380
1400
|
// Detect changes
|
|
1381
1401
|
const sizeChanged = prev.size !== validSize
|
|
1382
1402
|
const valueOrFnChanged = prev.valueOrFn !== valueOrFn
|
|
1383
|
-
// options は参照比較ではなく sampleRange の値で比較する。
|
|
1384
|
-
// 非メモ化のインライン options
|
|
1385
|
-
// (計測済み delta を毎レンダーで破棄しないため)。
|
|
1386
|
-
|
|
1403
|
+
// options は参照比較ではなく sampleRange / baseValue の値で比較する。
|
|
1404
|
+
// 非メモ化のインライン options でも値が同一なら reset を起こさない
|
|
1405
|
+
// (計測済み delta を毎レンダーで破棄しないため)。baseValue の値変化は未具現化アイテムの
|
|
1406
|
+
// 見積り総和そのものを変えるため reset (新 base での再構築) を要する — 黙って旧 base の
|
|
1407
|
+
// まま握り潰すと総和・バー写像・深部着地が stale 化する。
|
|
1408
|
+
const optionsChanged = sampleRangeChanged(prev.options, options) || prev.options?.baseValue !== options?.baseValue
|
|
1387
1409
|
|
|
1388
1410
|
// 旧 size が 0 の木は baseValue が未確立 (0) のまま。増分伸長 (changeSize) では
|
|
1389
1411
|
// total = 0 * N = 0 で固着するため、0 -> N の伸長は resetOnValueFnChange の設定に
|
package/src/utils.ts
CHANGED
|
@@ -14,3 +14,45 @@
|
|
|
14
14
|
export const minmax = (value: number, min: number, max: number): number => {
|
|
15
15
|
return Math.max(min, Math.min(max, value))
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Computes an element's CSS transform scale along one axis (visual px / layout px).
|
|
20
|
+
*
|
|
21
|
+
* 要素の指定軸方向 CSS transform スケール (視覚px / レイアウトpx) を算出。
|
|
22
|
+
* 祖先に transform: scale(...) があると client 座標 (視覚px) がレイアウトpx とズレるため、
|
|
23
|
+
* その比率でドラッグ量・クリック座標を補正する。分母には整数丸めされる offsetWidth/offsetHeight
|
|
24
|
+
* ではなく、transform の影響を受けず小数精度を持つ getComputedStyle のレイアウト寸法
|
|
25
|
+
* (content + padding + border) を優先して使い、transform なしなら厳密に 1 を返す。
|
|
26
|
+
* 取得不能時 (jsdom 等) は offset 寸法へフォールバックし、それも 0 なら 1 を返す。
|
|
27
|
+
*
|
|
28
|
+
* @param element The element to measure. / 計測対象要素。
|
|
29
|
+
* @param axis The axis to measure along ("x" = width, "y" = height). / 計測軸。
|
|
30
|
+
* @returns The visual/layout scale ratio (1 when unmeasurable). / 視覚/レイアウト比 (計測不能時 1)。
|
|
31
|
+
*/
|
|
32
|
+
export const getAxisScale = (element: HTMLElement, axis: "x" | "y"): number => {
|
|
33
|
+
const isX = axis === "x"
|
|
34
|
+
const rect = element.getBoundingClientRect()
|
|
35
|
+
const visualSize = isX ? rect.width : rect.height
|
|
36
|
+
// computed style の値 (px 文字列) を数値化する。解釈不能なら 0 とみなす。
|
|
37
|
+
const parseSize = (value: string) => {
|
|
38
|
+
const parsed = Number.parseFloat(value)
|
|
39
|
+
return Number.isFinite(parsed) ? parsed : 0
|
|
40
|
+
}
|
|
41
|
+
const computed = window.getComputedStyle(element)
|
|
42
|
+
const contentSize = parseSize(isX ? computed.width : computed.height)
|
|
43
|
+
// rect (border-box) と同系になるよう content + padding + border を合成する。
|
|
44
|
+
const computedLayoutSize =
|
|
45
|
+
contentSize > 0
|
|
46
|
+
? contentSize +
|
|
47
|
+
parseSize(isX ? computed.paddingLeft : computed.paddingTop) +
|
|
48
|
+
parseSize(isX ? computed.paddingRight : computed.paddingBottom) +
|
|
49
|
+
parseSize(isX ? computed.borderLeftWidth : computed.borderTopWidth) +
|
|
50
|
+
parseSize(isX ? computed.borderRightWidth : computed.borderBottomWidth)
|
|
51
|
+
: 0
|
|
52
|
+
// computed が取れない環境 (jsdom 等) では従来の offset 寸法へフォールバックする。
|
|
53
|
+
const layoutSize = computedLayoutSize > 0 ? computedLayoutSize : isX ? element.offsetWidth : element.offsetHeight
|
|
54
|
+
if (layoutSize <= 0 || visualSize <= 0) {
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
return visualSize / layoutSize
|
|
58
|
+
}
|