@aiquants/virtualscroll 1.20.0 → 1.22.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +5 -3
  3. package/dist/ScrollBar.d.cts +123 -0
  4. package/dist/ScrollBar.d.ts +123 -0
  5. package/dist/ScrollBar.d.ts.map +1 -1
  6. package/dist/ScrollPane.d.cts +16 -0
  7. package/dist/ScrollPane.d.ts +16 -0
  8. package/dist/ScrollPane.d.ts.map +1 -1
  9. package/dist/TapScrollCircle.d.ts.map +1 -1
  10. package/dist/VirtualScroll.d.cts +9 -0
  11. package/dist/VirtualScroll.d.ts +9 -0
  12. package/dist/VirtualScroll.d.ts.map +1 -1
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1558 -1436
  18. package/dist/styles/virtualscroll.css +1 -1
  19. package/dist/styles/virtualscroll.standalone.css +1 -1
  20. package/dist/tapScrollCircleSampleVisual.d.ts.map +1 -1
  21. package/dist/useFenwickMapTree.d.cts +43 -0
  22. package/dist/useFenwickMapTree.d.ts +43 -0
  23. package/dist/useFenwickMapTree.d.ts.map +1 -1
  24. package/package.json +4 -1
  25. package/src/ScrollBar.tsx +218 -33
  26. package/src/ScrollPane.tsx +366 -24
  27. package/src/TapScrollCircle.tsx +10 -0
  28. package/src/VirtualScroll.tsx +11 -1
  29. package/src/index.ts +1 -1
  30. package/src/styles/virtualscroll.css +3 -3
  31. package/src/tapScrollCircleSampleVisual.tsx +22 -5
  32. package/src/useFenwickMapTree.ts +95 -13
  33. package/src/ScrollBar.spec.tsx +0 -620
  34. package/src/ScrollPane.spec.tsx +0 -496
  35. package/src/TapScrollCircle.spec.tsx +0 -275
  36. package/src/VirtualScroll.spec.ts +0 -641
  37. package/src/cli.server.spec.ts +0 -137
  38. package/src/logger.spec.ts +0 -128
  39. package/src/useFenwickMapTree.huge.spec.ts +0 -388
  40. package/src/useFenwickMapTree.spec.ts +0 -1518
  41. package/src/useLruCache.spec.ts +0 -382
  42. package/src/utils.spec.ts +0 -39
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
- @layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane-content{flex:1;height:100%;position:relative;overflow:hidden}.aqvs-tap-scroll-circle{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;transition-property:transform;transition-duration:.1s;transition-timing-function:cubic-bezier(0,0,.2,1);display:flex;position:relative}.aqvs-tap-scroll-circle-visual-outer{border-radius:9999px;position:absolute;inset:0}.aqvs-tap-scroll-circle-gradient{background:linear-gradient(to bottom right,#1d4ed899,#60a5fa8c,#bfdbfe66);border-width:1px;border-color:#fff6;border-radius:9999px;position:absolute;inset:0;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-tap-scroll-circle-inner{border-radius:9999px;position:absolute;inset:18%}.aqvs-sample-visual-rod{background-color:#ffffffd9;border-width:1px;border-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-pupil{background-color:#fffc;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-highlight{background-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-scrollbar{z-index:50;cursor:default;-webkit-user-select:none;user-select:none;touch-action:none;background-color:#fff;position:relative}.aqvs-scrollbar-horizontal{flex-direction:row;align-items:stretch;display:flex}.aqvs-scrollbar-vertical{flex-direction:column;align-items:stretch;display:flex}.aqvs-scrollbar-tap-circle-wrapper{pointer-events:auto;transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);position:absolute}.aqvs-scrollbar-arrow-button{color:#313131;background-color:#e0e0e0;justify-content:center;align-items:center;font-size:.75rem;line-height:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.aqvs-scrollbar-arrow-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scrollbar-arrow-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scrollbar-arrow-button:disabled{cursor:not-allowed;opacity:.5}.aqvs-scrollbar-arrow-button:enabled:hover{background-color:#d4d4d4}.aqvs-scrollbar-track{background-color:#f5f5f5;flex:1;position:relative}.aqvs-scrollbar-overlay{pointer-events:none;position:absolute;inset:0}.aqvs-scrollbar-thumb-wrapper{touch-action:none;position:absolute}.aqvs-scrollbar-thumb{transform-origin:50%;background-color:#7f7f7f;transition:transform 80ms ease-out;position:absolute}.aqvs-scrollbar-thumb[data-thumb-state=disabled]{transition:none;transform:none}.aqvs-scrollbar-thumb[data-thumb-state=hover]{background-color:#5f5f5f}.aqvs-scrollbar-thumb[data-thumb-state=dragging]{background-color:#4f4f4f;transition:transform 60ms ease-out}.aqvs-scrollbar-thumb-horizontal{inset:1.5px 0}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=hover]{top:-.5px;bottom:-.5px;transform:scaleY(1.06)}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=dragging]{top:-.5px;bottom:-.5px;transform:scaleY(1.12)}.aqvs-scrollbar-thumb-vertical{inset:0 1.5px}.aqvs-scrollbar-thumb-vertical[data-thumb-state=hover]{left:-.5px;right:-.5px;transform:scaleX(1.06)}.aqvs-scrollbar-thumb-vertical[data-thumb-state=dragging]{left:-.5px;right:-.5px;transform:scaleX(1.12)}.aqvs-scroll-to-edge-button{pointer-events:auto;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border-radius:9999px;padding:.25rem 3rem;font-size:10px;font-weight:500;transition-property:transform,background-color;transition-duration:.15s;transform:none;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-scroll-to-edge-button:hover{background-color:#374151}.aqvs-scroll-to-edge-button:active{transform:scale(.95)}.aqvs-scroll-to-edge-overlay{pointer-events:none;z-index:10;transition-property:opacity;transition-duration:.5s;position:absolute;inset:0}.aqvs-scroll-to-edge-overlay[data-visible=true]{opacity:1}.aqvs-scroll-to-edge-overlay[data-visible=false]{opacity:0}.aqvs-scroll-to-edge-button-container{justify-content:center;display:flex;position:absolute;left:0;right:0}.aqvs-scroll-to-edge-button-container-top{top:.5rem}.aqvs-scroll-to-edge-button-container-bottom{bottom:.5rem}.aqvs-no-items-container{width:100%;position:absolute;top:0}.aqvs-no-items-text{text-align:center;color:#6b7280}.aqvs-item-container,.aqvs-bottom-inset,.aqvs-items-wrapper{width:100%;position:absolute}}
2
+ @layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane-content{flex:1;height:100%;position:relative;overflow:hidden}.aqvs-tap-scroll-circle{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;transition-property:transform;transition-duration:.1s;transition-timing-function:cubic-bezier(0,0,.2,1);display:flex;position:relative}.aqvs-tap-scroll-circle-visual-outer{border-radius:9999px;position:absolute;inset:0}.aqvs-tap-scroll-circle-gradient{background:linear-gradient(to bottom right,#1d4ed899,#60a5fa8c,#bfdbfe66);border-width:1px;border-color:#fff6;border-radius:9999px;position:absolute;inset:0;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-tap-scroll-circle-inner{border-radius:9999px;position:absolute;inset:18%}.aqvs-sample-visual-pupil{background-color:#ffffffd9;border-width:1px;border-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-highlight{background-color:#fffc;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-rod{background-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-scrollbar{z-index:50;cursor:default;-webkit-user-select:none;user-select:none;touch-action:none;background-color:#fff;position:relative}.aqvs-scrollbar-horizontal{flex-direction:row;align-items:stretch;display:flex}.aqvs-scrollbar-vertical{flex-direction:column;align-items:stretch;display:flex}.aqvs-scrollbar-tap-circle-wrapper{pointer-events:auto;transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);position:absolute}.aqvs-scrollbar-arrow-button{color:#313131;background-color:#e0e0e0;justify-content:center;align-items:center;font-size:.75rem;line-height:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.aqvs-scrollbar-arrow-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scrollbar-arrow-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scrollbar-arrow-button:disabled{cursor:not-allowed;opacity:.5}.aqvs-scrollbar-arrow-button:enabled:hover{background-color:#d4d4d4}.aqvs-scrollbar-track{background-color:#f5f5f5;flex:1;position:relative}.aqvs-scrollbar-overlay{pointer-events:none;position:absolute;inset:0}.aqvs-scrollbar-thumb-wrapper{touch-action:none;position:absolute}.aqvs-scrollbar-thumb{transform-origin:50%;background-color:#7f7f7f;transition:transform 80ms ease-out;position:absolute}.aqvs-scrollbar-thumb[data-thumb-state=disabled]{transition:none;transform:none}.aqvs-scrollbar-thumb[data-thumb-state=hover]{background-color:#5f5f5f}.aqvs-scrollbar-thumb[data-thumb-state=dragging]{background-color:#4f4f4f;transition:transform 60ms ease-out}.aqvs-scrollbar-thumb-horizontal{inset:1.5px 0}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=hover]{top:-.5px;bottom:-.5px;transform:scaleY(1.06)}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=dragging]{top:-.5px;bottom:-.5px;transform:scaleY(1.12)}.aqvs-scrollbar-thumb-vertical{inset:0 1.5px}.aqvs-scrollbar-thumb-vertical[data-thumb-state=hover]{left:-.5px;right:-.5px;transform:scaleX(1.06)}.aqvs-scrollbar-thumb-vertical[data-thumb-state=dragging]{left:-.5px;right:-.5px;transform:scaleX(1.12)}.aqvs-scroll-to-edge-button{pointer-events:auto;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border-radius:9999px;padding:.25rem 3rem;font-size:10px;font-weight:500;transition-property:transform,background-color;transition-duration:.15s;transform:none;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-scroll-to-edge-button:hover{background-color:#374151}.aqvs-scroll-to-edge-button:active{transform:scale(.95)}.aqvs-scroll-to-edge-overlay{pointer-events:none;z-index:10;transition-property:opacity;transition-duration:.5s;position:absolute;inset:0}.aqvs-scroll-to-edge-overlay[data-visible=true]{opacity:1}.aqvs-scroll-to-edge-overlay[data-visible=false]{opacity:0}.aqvs-scroll-to-edge-button-container{justify-content:center;display:flex;position:absolute;left:0;right:0}.aqvs-scroll-to-edge-button-container-top{top:.5rem}.aqvs-scroll-to-edge-button-container-bottom{bottom:.5rem}.aqvs-no-items-container{width:100%;position:absolute;top:0}.aqvs-no-items-text{text-align:center;color:#6b7280}.aqvs-item-container,.aqvs-bottom-inset,.aqvs-items-wrapper{width:100%;position:absolute}}
@@ -1,3 +1,3 @@
1
1
  /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
2
  /*! @aiquants/virtualscroll standalone CSS — 非 Tailwind ホスト専用。ホストの Tailwind ビルドと混在させないこと */
3
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--spacing:.25rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base;@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane-content{flex:1;height:100%;position:relative;overflow:hidden}.aqvs-tap-scroll-circle{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;transition-property:transform;transition-duration:.1s;transition-timing-function:cubic-bezier(0,0,.2,1);display:flex;position:relative}.aqvs-tap-scroll-circle-visual-outer{border-radius:9999px;position:absolute;inset:0}.aqvs-tap-scroll-circle-gradient{background:linear-gradient(to bottom right,#1d4ed899,#60a5fa8c,#bfdbfe66);border-width:1px;border-color:#fff6;border-radius:9999px;position:absolute;inset:0;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-tap-scroll-circle-inner{border-radius:9999px;position:absolute;inset:18%}.aqvs-sample-visual-rod{background-color:#ffffffd9;border-width:1px;border-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-pupil{background-color:#fffc;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-highlight{background-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-scrollbar{z-index:50;cursor:default;-webkit-user-select:none;user-select:none;touch-action:none;background-color:#fff;position:relative}.aqvs-scrollbar-horizontal{flex-direction:row;align-items:stretch;display:flex}.aqvs-scrollbar-vertical{flex-direction:column;align-items:stretch;display:flex}.aqvs-scrollbar-tap-circle-wrapper{pointer-events:auto;transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);position:absolute}.aqvs-scrollbar-arrow-button{color:#313131;background-color:#e0e0e0;justify-content:center;align-items:center;font-size:.75rem;line-height:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.aqvs-scrollbar-arrow-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scrollbar-arrow-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scrollbar-arrow-button:disabled{cursor:not-allowed;opacity:.5}.aqvs-scrollbar-arrow-button:enabled:hover{background-color:#d4d4d4}.aqvs-scrollbar-track{background-color:#f5f5f5;flex:1;position:relative}.aqvs-scrollbar-overlay{pointer-events:none;position:absolute;inset:0}.aqvs-scrollbar-thumb-wrapper{touch-action:none;position:absolute}.aqvs-scrollbar-thumb{transform-origin:50%;background-color:#7f7f7f;transition:transform 80ms ease-out;position:absolute}.aqvs-scrollbar-thumb[data-thumb-state=disabled]{transition:none;transform:none}.aqvs-scrollbar-thumb[data-thumb-state=hover]{background-color:#5f5f5f}.aqvs-scrollbar-thumb[data-thumb-state=dragging]{background-color:#4f4f4f;transition:transform 60ms ease-out}.aqvs-scrollbar-thumb-horizontal{inset:1.5px 0}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=hover]{top:-.5px;bottom:-.5px;transform:scaleY(1.06)}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=dragging]{top:-.5px;bottom:-.5px;transform:scaleY(1.12)}.aqvs-scrollbar-thumb-vertical{inset:0 1.5px}.aqvs-scrollbar-thumb-vertical[data-thumb-state=hover]{left:-.5px;right:-.5px;transform:scaleX(1.06)}.aqvs-scrollbar-thumb-vertical[data-thumb-state=dragging]{left:-.5px;right:-.5px;transform:scaleX(1.12)}.aqvs-scroll-to-edge-button{pointer-events:auto;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border-radius:9999px;padding:.25rem 3rem;font-size:10px;font-weight:500;transition-property:transform,background-color;transition-duration:.15s;transform:none;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-scroll-to-edge-button:hover{background-color:#374151}.aqvs-scroll-to-edge-button:active{transform:scale(.95)}.aqvs-scroll-to-edge-overlay{pointer-events:none;z-index:10;transition-property:opacity;transition-duration:.5s;position:absolute;inset:0}.aqvs-scroll-to-edge-overlay[data-visible=true]{opacity:1}.aqvs-scroll-to-edge-overlay[data-visible=false]{opacity:0}.aqvs-scroll-to-edge-button-container{justify-content:center;display:flex;position:absolute;left:0;right:0}.aqvs-scroll-to-edge-button-container-top{top:.5rem}.aqvs-scroll-to-edge-button-container-bottom{bottom:.5rem}.aqvs-no-items-container{width:100%;position:absolute;top:0}.aqvs-no-items-text{text-align:center;color:#6b7280}.aqvs-item-container,.aqvs-bottom-inset,.aqvs-items-wrapper{width:100%;position:absolute}}@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.hidden{display:none}.inline{display:inline}.size-1{width:calc(var(--spacing) * 1);height:calc(var(--spacing) * 1)}.shrink{flex-shrink:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.border{border-style:var(--tw-border-style);border-width:1px}.opacity-0{opacity:0}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
3
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--spacing:.25rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base;@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane-content{flex:1;height:100%;position:relative;overflow:hidden}.aqvs-tap-scroll-circle{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;transition-property:transform;transition-duration:.1s;transition-timing-function:cubic-bezier(0,0,.2,1);display:flex;position:relative}.aqvs-tap-scroll-circle-visual-outer{border-radius:9999px;position:absolute;inset:0}.aqvs-tap-scroll-circle-gradient{background:linear-gradient(to bottom right,#1d4ed899,#60a5fa8c,#bfdbfe66);border-width:1px;border-color:#fff6;border-radius:9999px;position:absolute;inset:0;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-tap-scroll-circle-inner{border-radius:9999px;position:absolute;inset:18%}.aqvs-sample-visual-pupil{background-color:#ffffffd9;border-width:1px;border-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-highlight{background-color:#fffc;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-sample-visual-rod{background-color:#ffffff80;border-radius:9999px;position:absolute;top:50%;left:50%}.aqvs-scrollbar{z-index:50;cursor:default;-webkit-user-select:none;user-select:none;touch-action:none;background-color:#fff;position:relative}.aqvs-scrollbar-horizontal{flex-direction:row;align-items:stretch;display:flex}.aqvs-scrollbar-vertical{flex-direction:column;align-items:stretch;display:flex}.aqvs-scrollbar-tap-circle-wrapper{pointer-events:auto;transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);position:absolute}.aqvs-scrollbar-arrow-button{color:#313131;background-color:#e0e0e0;justify-content:center;align-items:center;font-size:.75rem;line-height:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.aqvs-scrollbar-arrow-button:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scrollbar-arrow-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.aqvs-scrollbar-arrow-button:disabled{cursor:not-allowed;opacity:.5}.aqvs-scrollbar-arrow-button:enabled:hover{background-color:#d4d4d4}.aqvs-scrollbar-track{background-color:#f5f5f5;flex:1;position:relative}.aqvs-scrollbar-overlay{pointer-events:none;position:absolute;inset:0}.aqvs-scrollbar-thumb-wrapper{touch-action:none;position:absolute}.aqvs-scrollbar-thumb{transform-origin:50%;background-color:#7f7f7f;transition:transform 80ms ease-out;position:absolute}.aqvs-scrollbar-thumb[data-thumb-state=disabled]{transition:none;transform:none}.aqvs-scrollbar-thumb[data-thumb-state=hover]{background-color:#5f5f5f}.aqvs-scrollbar-thumb[data-thumb-state=dragging]{background-color:#4f4f4f;transition:transform 60ms ease-out}.aqvs-scrollbar-thumb-horizontal{inset:1.5px 0}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=hover]{top:-.5px;bottom:-.5px;transform:scaleY(1.06)}.aqvs-scrollbar-thumb-horizontal[data-thumb-state=dragging]{top:-.5px;bottom:-.5px;transform:scaleY(1.12)}.aqvs-scrollbar-thumb-vertical{inset:0 1.5px}.aqvs-scrollbar-thumb-vertical[data-thumb-state=hover]{left:-.5px;right:-.5px;transform:scaleX(1.06)}.aqvs-scrollbar-thumb-vertical[data-thumb-state=dragging]{left:-.5px;right:-.5px;transform:scaleX(1.12)}.aqvs-scroll-to-edge-button{pointer-events:auto;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border-radius:9999px;padding:.25rem 3rem;font-size:10px;font-weight:500;transition-property:transform,background-color;transition-duration:.15s;transform:none;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.aqvs-scroll-to-edge-button:hover{background-color:#374151}.aqvs-scroll-to-edge-button:active{transform:scale(.95)}.aqvs-scroll-to-edge-overlay{pointer-events:none;z-index:10;transition-property:opacity;transition-duration:.5s;position:absolute;inset:0}.aqvs-scroll-to-edge-overlay[data-visible=true]{opacity:1}.aqvs-scroll-to-edge-overlay[data-visible=false]{opacity:0}.aqvs-scroll-to-edge-button-container{justify-content:center;display:flex;position:absolute;left:0;right:0}.aqvs-scroll-to-edge-button-container-top{top:.5rem}.aqvs-scroll-to-edge-button-container-bottom{bottom:.5rem}.aqvs-no-items-container{width:100%;position:absolute;top:0}.aqvs-no-items-text{text-align:center;color:#6b7280}.aqvs-item-container,.aqvs-bottom-inset,.aqvs-items-wrapper{width:100%;position:absolute}}@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.hidden{display:none}.inline{display:inline}.size-1{width:calc(var(--spacing) * 1);height:calc(var(--spacing) * 1)}.shrink{flex-shrink:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.border{border-style:var(--tw-border-style);border-width:1px}.opacity-0{opacity:0}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -1 +1 @@
1
- {"version":3,"file":"tapScrollCircleSampleVisual.d.ts","sourceRoot":"","sources":["../src/tapScrollCircleSampleVisual.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAA8B,MAAM,uBAAuB,CAAA;AAI7F,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAgEzF,CAAA"}
1
+ {"version":3,"file":"tapScrollCircleSampleVisual.d.ts","sourceRoot":"","sources":["../src/tapScrollCircleSampleVisual.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAA8B,MAAM,uBAAuB,CAAA;AAI7F,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAgEzF,CAAA"}
@@ -298,6 +298,11 @@ export declare class FenwickMapTree {
298
298
  * @method calculateAccumulatedError
299
299
  * @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.
300
300
  * @description キャッシュされている合計値と、元の値 (`deltas` と `baseValue`、または `valueFn`) から直接計算した理論上の合計値とを比較します。これにより、浮動小数点数の累積誤差やその他の不整合によって生じる可能性のある、ツリーのキャッシュ状態の不一致を検出できます。
301
+ *
302
+ * ❗ 集計対象は `[0, size)` の範囲内にある delta だけです。`changeSize(0)` は
303
+ * ロード中の一時クリアとみなして delta を温存する (再伸長時にキャッシュを再利用するため) ので、
304
+ * 範囲外の delta まで数えると**木は健全なのに空リストの間ずっと非ゼロ**が返り、
305
+ * この値を破損検知に使う利用者が毎回不要な復旧処理を走らせてしまいます。
301
306
  * @returns {number} The difference between the cached total and the theoretical total.
302
307
  */
303
308
  calculateAccumulatedError(): number;
@@ -315,6 +320,18 @@ export declare class FenwickMapTree {
315
320
  * サイズ差分のみを受け取りインデックスの対応表は受け取らないため、要素は末尾に追加/末尾から削除
316
321
  * されると仮定する。既存行は index↔高さ の対応を保つ。したがって中間挿入/削除は表現できず、その
317
322
  * ような構造変更の後は index↔高さ の対応が保存されず、木の delta が誤った行へ適用されてしまう。
323
+ * ❗ `changeSize(0)` は例外的に delta を消去しない。ロード中に一覧が一瞬空になるフローで
324
+ * 実測済みの行高を捨てないための特例で、`changeSize(N > 0)` で再伸長したときに範囲内の delta が
325
+ * そのまま復元される (範囲外の delta はそこで刈られる)。size が 0 に留まっている間、
326
+ * `get()` / `getTotal()` は 0 を返し、`calculateAccumulatedError()` は範囲内 delta だけを
327
+ * 集計するので 0 のままになる。**別データセットへ差し替える場合は温存が害になる**ため、
328
+ * `reset()` を呼ぶか新しい `key` で作り直すこと。
329
+ * ❗ `changeSize(0)` intentionally keeps its deltas so a list that briefly empties while loading
330
+ * does not lose measured row heights; growing back restores the in-range ones (out-of-range
331
+ * deltas are pruned then). While the size stays 0, `get()` / `getTotal()` return 0 and
332
+ * `calculateAccumulatedError()` stays 0 because it only sums in-range deltas. Replacing the
333
+ * dataset instead of reloading the same one must go through `reset()` or a fresh instance.
334
+ *
318
335
  * 中間で要素を挿入/削除する消費側は、代わりに木をリセットするか (`useFenwickMapTree` の
319
336
  * `resetOnValueFnChange`。VirtualScroll では `resetOnGetItemHeightChange` として公開) 、新しい
320
337
  * `key` でコンポーネントを再マウントすること。
@@ -363,6 +380,32 @@ export declare class FenwickMapTree {
363
380
  * @returns {number} The total number of items.
364
381
  */
365
382
  getSize(): number;
383
+ /**
384
+ * @description Gets the base value of the tree — the height every row starts from before
385
+ * its own delta is applied. `useFenwickMapTree` reads it to decide whether a `0 → N` growth
386
+ * should re-sample from scratch (`reset`) or reuse the measured heights kept across the
387
+ * empty state (`changeSize`), so removing or renaming it silently disables that cache
388
+ * preservation.
389
+ *
390
+ * ❗ This is NOT a "has the tree warmed up?" flag. `0` is a legitimate base — it is what a
391
+ * tree constructed with `new FenwickMapTree(n, 0)` has, and what sampling produces when the
392
+ * value function returns 0 for not-yet-measured rows (a common virtualization pattern).
393
+ * Such a tree can hold plenty of measured deltas while `base` stays `0`. The hook's
394
+ * `0 → N` guard therefore trades cache preservation for correctness in exactly that case:
395
+ * a base of `0` cannot be distinguished from "never established", and growing incrementally
396
+ * from a zero base would pin `total` at `0 × N = 0`.
397
+ * @description ツリーの基準値 (各行が自身の delta を適用される前の高さ) を取得。
398
+ * `useFenwickMapTree` は `0 → N` の伸長を再サンプリング (`reset`) と実測高さの温存 (`changeSize`) の
399
+ * どちらへ倒すかの判定にこの値を読むため、削除・改名するとキャッシュ温存が黙って無効化される。
400
+ *
401
+ * ❗ **「木が温まったか」のフラグではない。** `0` は正当な基準値であり、
402
+ * `new FenwickMapTree(n, 0)` や「未計測行に 0 を返す `getItemHeight`」(仮想化では定番) の
403
+ * サンプリング結果として普通に発生する。その木は実測 delta を大量に持っていても `base` は `0` のまま。
404
+ * フックの `0 → N` ガードは、まさにこの場合にキャッシュ温存より整合を優先する
405
+ * (基準値 0 は「未確立」と区別できず、0 のまま増分伸長すると `total = 0 × N = 0` で固着するため)。
406
+ * @returns {number} The base value. 基準値。
407
+ */
408
+ get base(): number;
366
409
  /**
367
410
  * @method findIndexAtOrAfter
368
411
  * @description Finds the first index where the cumulative sum is greater than or equal to a target value.
@@ -298,6 +298,11 @@ export declare class FenwickMapTree {
298
298
  * @method calculateAccumulatedError
299
299
  * @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.
300
300
  * @description キャッシュされている合計値と、元の値 (`deltas` と `baseValue`、または `valueFn`) から直接計算した理論上の合計値とを比較します。これにより、浮動小数点数の累積誤差やその他の不整合によって生じる可能性のある、ツリーのキャッシュ状態の不一致を検出できます。
301
+ *
302
+ * ❗ 集計対象は `[0, size)` の範囲内にある delta だけです。`changeSize(0)` は
303
+ * ロード中の一時クリアとみなして delta を温存する (再伸長時にキャッシュを再利用するため) ので、
304
+ * 範囲外の delta まで数えると**木は健全なのに空リストの間ずっと非ゼロ**が返り、
305
+ * この値を破損検知に使う利用者が毎回不要な復旧処理を走らせてしまいます。
301
306
  * @returns {number} The difference between the cached total and the theoretical total.
302
307
  */
303
308
  calculateAccumulatedError(): number;
@@ -315,6 +320,18 @@ export declare class FenwickMapTree {
315
320
  * サイズ差分のみを受け取りインデックスの対応表は受け取らないため、要素は末尾に追加/末尾から削除
316
321
  * されると仮定する。既存行は index↔高さ の対応を保つ。したがって中間挿入/削除は表現できず、その
317
322
  * ような構造変更の後は index↔高さ の対応が保存されず、木の delta が誤った行へ適用されてしまう。
323
+ * ❗ `changeSize(0)` は例外的に delta を消去しない。ロード中に一覧が一瞬空になるフローで
324
+ * 実測済みの行高を捨てないための特例で、`changeSize(N > 0)` で再伸長したときに範囲内の delta が
325
+ * そのまま復元される (範囲外の delta はそこで刈られる)。size が 0 に留まっている間、
326
+ * `get()` / `getTotal()` は 0 を返し、`calculateAccumulatedError()` は範囲内 delta だけを
327
+ * 集計するので 0 のままになる。**別データセットへ差し替える場合は温存が害になる**ため、
328
+ * `reset()` を呼ぶか新しい `key` で作り直すこと。
329
+ * ❗ `changeSize(0)` intentionally keeps its deltas so a list that briefly empties while loading
330
+ * does not lose measured row heights; growing back restores the in-range ones (out-of-range
331
+ * deltas are pruned then). While the size stays 0, `get()` / `getTotal()` return 0 and
332
+ * `calculateAccumulatedError()` stays 0 because it only sums in-range deltas. Replacing the
333
+ * dataset instead of reloading the same one must go through `reset()` or a fresh instance.
334
+ *
318
335
  * 中間で要素を挿入/削除する消費側は、代わりに木をリセットするか (`useFenwickMapTree` の
319
336
  * `resetOnValueFnChange`。VirtualScroll では `resetOnGetItemHeightChange` として公開) 、新しい
320
337
  * `key` でコンポーネントを再マウントすること。
@@ -363,6 +380,32 @@ export declare class FenwickMapTree {
363
380
  * @returns {number} The total number of items.
364
381
  */
365
382
  getSize(): number;
383
+ /**
384
+ * @description Gets the base value of the tree — the height every row starts from before
385
+ * its own delta is applied. `useFenwickMapTree` reads it to decide whether a `0 → N` growth
386
+ * should re-sample from scratch (`reset`) or reuse the measured heights kept across the
387
+ * empty state (`changeSize`), so removing or renaming it silently disables that cache
388
+ * preservation.
389
+ *
390
+ * ❗ This is NOT a "has the tree warmed up?" flag. `0` is a legitimate base — it is what a
391
+ * tree constructed with `new FenwickMapTree(n, 0)` has, and what sampling produces when the
392
+ * value function returns 0 for not-yet-measured rows (a common virtualization pattern).
393
+ * Such a tree can hold plenty of measured deltas while `base` stays `0`. The hook's
394
+ * `0 → N` guard therefore trades cache preservation for correctness in exactly that case:
395
+ * a base of `0` cannot be distinguished from "never established", and growing incrementally
396
+ * from a zero base would pin `total` at `0 × N = 0`.
397
+ * @description ツリーの基準値 (各行が自身の delta を適用される前の高さ) を取得。
398
+ * `useFenwickMapTree` は `0 → N` の伸長を再サンプリング (`reset`) と実測高さの温存 (`changeSize`) の
399
+ * どちらへ倒すかの判定にこの値を読むため、削除・改名するとキャッシュ温存が黙って無効化される。
400
+ *
401
+ * ❗ **「木が温まったか」のフラグではない。** `0` は正当な基準値であり、
402
+ * `new FenwickMapTree(n, 0)` や「未計測行に 0 を返す `getItemHeight`」(仮想化では定番) の
403
+ * サンプリング結果として普通に発生する。その木は実測 delta を大量に持っていても `base` は `0` のまま。
404
+ * フックの `0 → N` ガードは、まさにこの場合にキャッシュ温存より整合を優先する
405
+ * (基準値 0 は「未確立」と区別できず、0 のまま増分伸長すると `total = 0 × N = 0` で固着するため)。
406
+ * @returns {number} The base value. 基準値。
407
+ */
408
+ get base(): number;
366
409
  /**
367
410
  * @method findIndexAtOrAfter
368
411
  * @description Finds the first index where the cumulative sum is greater than or equal to a target value.
@@ -1 +1 @@
1
- {"version":3,"file":"useFenwickMapTree.d.ts","sourceRoot":"","sources":["../src/useFenwickMapTree.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useFenwickMapTree.d.ts","sourceRoot":"","sources":["../src/useFenwickMapTree.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/virtualscroll",
3
- "version": "1.20.0",
3
+ "version": "1.22.1",
4
4
  "description": "High-performance virtual scrolling component for React with variable item heights",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -27,7 +27,10 @@
27
27
  "files": [
28
28
  "dist",
29
29
  "src",
30
+ "!src/**/*.spec.ts",
31
+ "!src/**/*.spec.tsx",
30
32
  "README.md",
33
+ "CHANGELOG.md",
31
34
  "LICENSE"
32
35
  ],
33
36
  "bin": {
package/src/ScrollBar.tsx CHANGED
@@ -185,7 +185,22 @@ const ARROW_HOLD_DELAY = 250
185
185
  const ARROW_HOLD_INTERVAL = 60
186
186
  const MIN_ARROW_STEP = 20
187
187
  const ARROW_STEP_DIVISOR = 20
188
- const TAP_SCROLL_MAX_DISTANCE = 240
188
+ /**
189
+ * Defaults `computeTapScrollSpeed` callers need to reproduce the shipped speed mapping.
190
+ * `computeTapScrollSpeed` の呼び出し側が出荷時の速度写像を再現するために必要な既定値。
191
+ *
192
+ * ❗ これらは `ScrollBar` が `tapScrollCircleOptions` を解決するときに使う値そのもの。
193
+ * 公開しないと、`computeTapScrollSpeed` を渡された消費側が「自分の設定で実際に何 px/s 出るか」を
194
+ * 事前に検証できない (必須フィールドの大半が内部リゾルバの出力になってしまう)。
195
+ */
196
+ export const TAP_SCROLL_SPEED_DEFAULTS = {
197
+ /** 速度が最大に達する引き距離 (px)。`maxVisualDistance` の既定値。 */
198
+ maxDistance: 240,
199
+ /** 最小速度倍率 (ビューポート比)。 */
200
+ minSpeedMultiplier: 0.2,
201
+ } as const
202
+
203
+ const TAP_SCROLL_MAX_DISTANCE = TAP_SCROLL_SPEED_DEFAULTS.maxDistance
189
204
  const TAP_SCROLL_INITIAL_STATE: TapScrollCircleDragState = { active: false, offsetX: 0, offsetY: 0, distance: 0, direction: 0, pointerId: null }
190
205
 
191
206
  const TAP_SCROLL_AUTO_SPEED_BASE_MULTIPLIER = 2.2
@@ -205,7 +220,7 @@ const DEFAULT_TAP_SCROLL_CIRCLE_OPTIONS: ResolvedTapScrollCircleOptions = {
205
220
  className: undefined,
206
221
  maxVisualDistance: TAP_SCROLL_MAX_DISTANCE,
207
222
  maxSpeedMultiplier: TAP_SCROLL_AUTO_SPEED_BASE_MULTIPLIER,
208
- minSpeedMultiplier: 0.2,
223
+ minSpeedMultiplier: TAP_SCROLL_SPEED_DEFAULTS.minSpeedMultiplier,
209
224
  opacity: 0.9,
210
225
  renderVisual: undefined,
211
226
  maxSpeedCurve: undefined,
@@ -377,12 +392,172 @@ const useArrowAutoRepeat = ({ canUseArrowButtons, enableArrowButtons, resetTapSc
377
392
  return { handleArrowPointerDown, handleArrowPointerUp, handleArrowKeyDown }
378
393
  }
379
394
 
395
+ /**
396
+ * Upper bound applied to `exponentialSteepness` before it reaches `Math.expm1`.
397
+ * `exponentialSteepness` を `Math.expm1` へ渡す前に掛ける上限。
398
+ *
399
+ * ❗ `Math.expm1(x)` は x >= 709.783 で `Infinity` になる。分母が `Infinity` になると
400
+ * 引ききった瞬間だけ `Infinity / Infinity = NaN` になる**部分故障**が起きるため、
401
+ * 有限が保証される範囲へ丸める。この値でも形状は実質ステップ関数なので実害は無い。
402
+ */
403
+ const MAX_EXPONENTIAL_STEEPNESS = 700
404
+
405
+ /**
406
+ * Returns the value when it is a finite number, otherwise the fallback.
407
+ * 有限数ならその値を、そうでなければ代替値を返す処理。
408
+ *
409
+ * @param value The candidate number. 検査対象の数値。
410
+ * @param fallback Value used when the candidate is not finite. 非有限だった場合の代替値。
411
+ * @returns A finite number. 有限数。
412
+ */
413
+ const finiteOr = (value: number | undefined, fallback: number): number => (typeof value === "number" && Number.isFinite(value) ? value : fallback)
414
+
415
+ /**
416
+ * Input for `computeTapScrollSpeed`.
417
+ * `computeTapScrollSpeed` の入力。
418
+ */
419
+ export type TapScrollSpeedInput = {
420
+ /** サークルを引いた距離 (px)。 */
421
+ distance: number
422
+ /** 速度が最大に達する引き距離 (px)。 */
423
+ maxDistance: number
424
+ /** ビューポートの長さ (px)。速度倍率の基準。 */
425
+ viewportSize: number
426
+ /** 最小速度倍率 (ビューポート比)。 */
427
+ minSpeedMultiplier: number
428
+ /** 最大速度倍率 (ビューポート比)。 */
429
+ maxSpeedMultiplier: number
430
+ /** `maxSpeedMultiplier` が消費側から明示されたか (件数由来の自動値ではないか)。 */
431
+ hasCustomMaxSpeedMultiplier: boolean
432
+ /** 速度の立ち上がり形状。未指定なら `normalized ** 1.1` の既定形状。 */
433
+ curve?: { exponentialSteepness: number; exponentialScale?: number; easedOffset?: number }
434
+ }
435
+
436
+ /**
437
+ * Maps the tap-circle drag distance onto a scroll speed (px/s).
438
+ * タップサークルの引き距離をスクロール速度 (px/s) へ写像する処理。
439
+ *
440
+ * ❗ **曲線は「速度の上限」ではなく「立ち上がりの形状」を決める。**
441
+ * かつては指数カーブを上限 (`maxSpeed`) 側へ適用し、その `maxSpeed` へさらに
442
+ * イージングを掛けていた (二重適用)。その結果、指数側の上限が最小速度を下回る区間で
443
+ * `maxSpeed === minSpeed` へ潰れ、**引いても速度がまったく変わらない死角**が生まれていた。
444
+ * 死角の境界は `shape(n) = minSpeed / (viewportSize × exponentialScale)` を満たす点で、
445
+ * その**幅を支配するのは `exponentialSteepness`** である (比が同じ 0.2/14 でも
446
+ * steepness 1 なら引き幅の 2.4%、3 なら 8.0%、7.5 なら 43.9%、12 なら 64.6%)。
447
+ * `easedOffset` も同区間では完全に無効化されていた。
448
+ *
449
+ * そこで曲線は形状にのみ適用し、上限は距離に依存しない定数として扱う。
450
+ * これにより**縮退設定を除いて**速度は引き距離に対して狭義単調になり、平坦域が発生しない。
451
+ *
452
+ * 縮退設定 (意図的に定速へ倒す設定であり、欠陥ではない)。いずれも `maxSpeed === minSpeed`
453
+ * または `offset === 1` に潰れるため、引き距離に依らず定速になる:
454
+ *
455
+ * 1. `minSpeedMultiplier >= maxSpeedMultiplier` → 全域で最小速度に固定。
456
+ * `ScrollBar.spec.tsx` の `FIXED_SPEED_OPTIONS` がこれを利用している。
457
+ * 2. `easedOffset === 1` → 全域で最高速に固定。
458
+ * 3. `exponentialScale <= max(minSpeedMultiplier, 40 / viewportSize)` → 曲線側の上限が
459
+ * 最小速度以下に潰れるため全域で最小速度に固定。**旧実装の死角と同種の現象**だが、
460
+ * こちらは距離に依存しないので「一部区間だけ平坦」にはならず、全域が定速になる。
461
+ * 曲線を使うなら `exponentialScale > minSpeedMultiplier` を満たすこと。
462
+ *
463
+ * 各パラメータ:
464
+ *
465
+ * - `exponentialSteepness`: 立ち上がりの鋭さ。大きいほど低速域が長い。0 で線形。
466
+ * 内部で `[0, 700]` へ丸める (`Math.expm1` が `Infinity` になるのを避けるため)。
467
+ * - `exponentialScale`: 曲線側の上限 (ビューポート比)。省略時は `maxSpeedMultiplier`。
468
+ * 実効上限は `min(viewportSize × exponentialScale, linearMaxSpeed)` (いずれも `minSpeed` で下支え)。
469
+ * ❗ `maxSpeedMultiplier` を**明示した**場合は `linearMaxSpeed = viewportSize × maxSpeedMultiplier`
470
+ * なので「実効上限は `min(exponentialScale, maxSpeedMultiplier)` 倍」と読んでよいが、
471
+ * **省略した場合はそうならない**。自動倍率には 1,200 px/s の下限が掛かるため、
472
+ * 実効上限は `min(viewportSize × exponentialScale, max(viewportSize × 自動倍率, 1200))` になる
473
+ * (例: ビューポート 200・自動倍率 0.1・scale 0.5 なら、倍率比では 0.1 倍のはずが実際は 100 px/s)。
474
+ * - `easedOffset`: **速度レンジ全体 `[minSpeed, maxSpeed]` に対する比率**として、
475
+ * 引き距離 0 の時点で履かせる下駄 (0〜1)。
476
+ * ❗ **「微速の下駄」ではない。** `maxSpeedMultiplier` (特に件数由来の自動値) が
477
+ * 大きいほど絶対速度も比例して大きくなる。例: ビューポート 800px・実効上限 8 倍で
478
+ * `easedOffset: 0.18` なら引き距離 0 の時点で 1,283 px/s (160px 行なら毎秒 8 行) 出る。
479
+ * 低速域の分解能をその分だけ失うので、微速の下限を上げたいだけなら
480
+ * `minSpeedMultiplier` を上げる方が意図に忠実。
481
+ * ❗ 旧実装では下駄が `shape` への加算だったうえ、低速域は死角に飲まれて実質無効だった。
482
+ * 同じ値でも意味と絶対速度が変わるため、旧実装から値を持ち越さないこと。
483
+ *
484
+ * `maxSpeedMultiplier` を省略した場合の下限 1,200 px/s はビューポート非依存の絶対値で、
485
+ * 小さいビューポートほどビューポート比では速くなる (200px なら 6 vp/s、800px なら 1.5 vp/s)。
486
+ * 既存挙動との互換のため意図的に据え置いている。
487
+ *
488
+ * すべての数値入力は非有限 (NaN / ±Infinity) を代替値へ落として消毒する。
489
+ * 速度が NaN になると `scrollTo` を経てスクロール位置そのものが NaN で汚染され、
490
+ * 以後の相対移動が連鎖的に壊れて「スクロールが完全に効かなくなる」復旧不能な状態になるため。
491
+ *
492
+ * 外部から呼ぶときは `TAP_SCROLL_SPEED_DEFAULTS` と `computeAutoTapScrollMaxSpeedMultiplier()` を
493
+ * 併せて使うと、`ScrollBar` が実際に解決する値を再現できる:
494
+ *
495
+ * ```ts
496
+ * const options = { maxSpeedCurve: { exponentialSteepness: 3, exponentialScale: 8 } }
497
+ * const speed = computeTapScrollSpeed({
498
+ * distance,
499
+ * maxDistance: options.maxVisualDistance ?? TAP_SCROLL_SPEED_DEFAULTS.maxDistance,
500
+ * viewportSize,
501
+ * minSpeedMultiplier: options.minSpeedMultiplier ?? TAP_SCROLL_SPEED_DEFAULTS.minSpeedMultiplier,
502
+ * maxSpeedMultiplier: options.maxSpeedMultiplier ?? computeAutoTapScrollMaxSpeedMultiplier(itemCount),
503
+ * hasCustomMaxSpeedMultiplier: options.maxSpeedMultiplier !== undefined,
504
+ * curve: options.maxSpeedCurve,
505
+ * })
506
+ * ```
507
+ *
508
+ * @param input Drag distance and the resolved speed options. 引き距離と解決済みの速度設定。
509
+ * @returns Scroll speed in px/s (always finite, >= 40). px/s 単位のスクロール速度 (常に有限かつ 40 以上)。
510
+ */
511
+ export const computeTapScrollSpeed = ({ distance, maxDistance, viewportSize, minSpeedMultiplier, maxSpeedMultiplier, hasCustomMaxSpeedMultiplier, curve }: TapScrollSpeedInput): number => {
512
+ // ❗ 非有限を 1 つでも素通しさせると戻り値が NaN になり、スクロール位置が恒久的に壊れる。
513
+ // 呼び出し側 (`resolveTapScrollCircleOptions`) の `Math.max(x, 0)` は NaN を通すため、
514
+ // ここが唯一の防波堤である。`maxDistance` が 0 以下 (0 除算) の場合は最大速度側へ倒す。
515
+ const safeDistance = Math.max(finiteOr(distance, 0), 0)
516
+ const safeMaxDistance = finiteOr(maxDistance, 0)
517
+ const normalized = safeMaxDistance > 0 ? Math.min(safeDistance, safeMaxDistance) / safeMaxDistance : 1
518
+
519
+ const safeViewportSize = Math.max(finiteOr(viewportSize, 0), 0)
520
+ const safeMinMultiplier = Math.max(finiteOr(minSpeedMultiplier, 0), 0)
521
+ const safeMaxMultiplier = Math.max(finiteOr(maxSpeedMultiplier, 0), 0)
522
+
523
+ const minSpeed = Math.max(safeViewportSize * safeMinMultiplier, 40)
524
+ // 自動倍率のときだけ下限を持ち上げる (件数由来の倍率が小さすぎても実用速度を確保するため)
525
+ const maxSpeedFloor = hasCustomMaxSpeedMultiplier ? minSpeed : 1200
526
+ const linearMaxSpeed = Math.max(safeViewportSize * safeMaxMultiplier, maxSpeedFloor)
527
+
528
+ // 上限は距離に依存しない定数。曲線があれば scale で頭打ちにする
529
+ const curveScale = curve ? Math.max(finiteOr(curve.exponentialScale, safeMaxMultiplier), 0) : 0
530
+ const ceiling = curve ? Math.min(linearMaxSpeed, Math.max(safeViewportSize * curveScale, minSpeed)) : linearMaxSpeed
531
+ const maxSpeed = Math.max(ceiling, minSpeed)
532
+
533
+ // 形状。曲線が無ければ従来どおり normalized ** 1.1
534
+ let shape = normalized ** 1.1
535
+ if (curve) {
536
+ const steepness = Math.min(Math.max(finiteOr(curve.exponentialSteepness, 0), 0), MAX_EXPONENTIAL_STEEPNESS)
537
+ if (steepness === 0) {
538
+ shape = normalized
539
+ } else {
540
+ const denominator = Math.expm1(steepness) || 1
541
+ shape = Math.min(Math.max(Math.expm1(steepness * normalized) / denominator, 0), 1)
542
+ }
543
+ }
544
+
545
+ // ❗ 下駄は加算ではなく**再写像**する。`min(1, shape + offset)` にすると
546
+ // shape が 1 - offset に達した時点で頭打ちになり、今度は**上端に平坦域**ができる
547
+ // (引ききる手前から速度が変わらなくなる)。[offset, 1] へ写せば端から端まで単調を保てる。
548
+ const offset = Math.min(Math.max(finiteOr(curve?.easedOffset, 0), 0), 1)
549
+ return minSpeed + (maxSpeed - minSpeed) * (offset + (1 - offset) * shape)
550
+ }
551
+
380
552
  /**
381
553
  * Computes the auto-scroll speed multiplier based on item count.
382
554
  *
383
555
  * アイテム数に応じた自動スクロール速度倍率を算出。
556
+ *
557
+ * @param itemCount Total number of items. アイテムの総数。
558
+ * @returns Speed multiplier relative to the viewport size. ビューポート比の速度倍率。
384
559
  */
385
- const computeAutoTapScrollMaxSpeedMultiplier = (itemCount?: number) => {
560
+ export const computeAutoTapScrollMaxSpeedMultiplier = (itemCount?: number) => {
386
561
  if (!itemCount || itemCount <= 0) {
387
562
  return TAP_SCROLL_AUTO_SPEED_BASE_MULTIPLIER
388
563
  }
@@ -668,36 +843,15 @@ export const ScrollBar = ({
668
843
  return
669
844
  }
670
845
 
671
- // Calculate normalized distance (0.0 to 1.0)
672
- // 正規化された距離を計算 (0.0 から 1.0)
673
- const normalized = Math.min(state.distance, metrics.effectiveTapMaxDistance) / metrics.effectiveTapMaxDistance
674
- // Apply base non-linear easing
675
- // 基準の非線形イージングを適用
676
- const eased = normalized ** 1.1
677
- const hasCustomMaxSpeedMultiplier = typeof metrics.tapScrollCircleOptions?.maxSpeedMultiplier === "number"
678
- const minSpeed = Math.max(metrics.viewportSize * metrics.tapCircleMinSpeedMultiplier, 40)
679
- const maxSpeedFloor = hasCustomMaxSpeedMultiplier ? minSpeed : 1200
680
- const linearMaxSpeed = Math.max(metrics.viewportSize * metrics.tapCircleMaxSpeedMultiplier, maxSpeedFloor)
681
-
682
- let cappedMaxSpeed = linearMaxSpeed
683
- const curve = metrics.tapCircleMaxSpeedCurve
684
-
685
- // Apply exponential curve scaling if configured
686
- // 設定されている場合、指数関数曲線スケーリングを適用します
687
- if (curve) {
688
- const steepness = Math.max(curve.exponentialSteepness, 0)
689
- const scale = Math.max(curve.exponentialScale ?? metrics.tapCircleMaxSpeedMultiplier, 0)
690
- const expNumerator = steepness === 0 ? normalized : Math.expm1(steepness * normalized)
691
- const expDenominator = steepness === 0 ? 1 : Math.expm1(steepness) || 1
692
- const exponentialRatio = expDenominator === 0 ? normalized : Math.min(Math.max(expNumerator / expDenominator, 0), 1)
693
- const exponentialMaxSpeed = metrics.viewportSize * scale * exponentialRatio
694
- cappedMaxSpeed = Math.min(cappedMaxSpeed, Math.max(exponentialMaxSpeed, minSpeed))
695
- }
696
-
697
- const maxSpeed = Math.max(cappedMaxSpeed, minSpeed)
698
- const easedOffset = Math.max(curve?.easedOffset ?? 0, 0)
699
- const effectiveEased = Math.min(1, eased + easedOffset)
700
- const speed = minSpeed + (maxSpeed - minSpeed) * effectiveEased
846
+ const speed = computeTapScrollSpeed({
847
+ distance: state.distance,
848
+ maxDistance: metrics.effectiveTapMaxDistance,
849
+ viewportSize: metrics.viewportSize,
850
+ minSpeedMultiplier: metrics.tapCircleMinSpeedMultiplier,
851
+ maxSpeedMultiplier: metrics.tapCircleMaxSpeedMultiplier,
852
+ hasCustomMaxSpeedMultiplier: typeof metrics.tapScrollCircleOptions?.maxSpeedMultiplier === "number",
853
+ curve: metrics.tapCircleMaxSpeedCurve,
854
+ })
701
855
 
702
856
  // 方向が転換したら逆向きの残差を破棄する。持ち越すと逆向き残差を燃焼し切るまで
703
857
  // スクロール反転が固着する (タップサークルはヒステリシス幅を超える 1 イベントで +1→-1 に反転し得る)。
@@ -733,6 +887,28 @@ export const ScrollBar = ({
733
887
  }
734
888
  }, [stepAutoScroll])
735
889
 
890
+ /**
891
+ * Re-arms the tap auto-scroll loop when the scrollable extent changes under a held finger.
892
+ * サークルを掴んだまま可動域が変わったときに自動スクロールを張り直す副作用。
893
+ *
894
+ * ❗ ループは境界到達 (およびスクロール不能) で自分を止め、`startAutoScroll()` は
895
+ * タップサークルのポインタイベントからしか呼ばれない。そのため、**末端まで送った状態で
896
+ * 指を離さずにいるとコンテンツが増えてもスクロールが再開しない** (無限スクロールや
897
+ * ストリーミング取得の一覧では、指を一度離すまで動かない状態に見える)。
898
+ * `startAutoScroll()` は既にフレームが予約済みなら何もしないので、多重起動にはならない。
899
+ *
900
+ * 目的: 可動域が広がったら止まっているループを再開する。
901
+ * 依存関係: [contentSize, viewportSize, startAutoScroll]
902
+ * クリーンアップ: 不要 (停止はループ自身とアンマウント時の副作用が担う)。
903
+ */
904
+ useEffect(() => {
905
+ const state = tapDragStateRef.current
906
+ if (!state.active || state.direction === 0) {
907
+ return
908
+ }
909
+ startAutoScroll()
910
+ }, [contentSize, viewportSize, startAutoScroll])
911
+
736
912
  /**
737
913
  * Cleanup auto-scroll on unmount.
738
914
  *
@@ -1234,6 +1410,13 @@ export const ScrollBar = ({
1234
1410
  * pointerup を待たずにドラッグ状態を破棄し、stale な pointerId の残留を防ぐ。
1235
1411
  */
1236
1412
  const handleLostPointerCaptureOnTrack = (event: React.PointerEvent<HTMLDivElement>) => {
1413
+ // ❗ 発生源がトラック自身のときだけ反応する。`lostpointercapture` はバブリングするため、
1414
+ // 子孫であるつまみやオーバーレイのキャプチャ遷移まで拾ってしまう。タッチ・ペンでは仕様
1415
+ // (暗黙のポインタキャプチャ) により着地点の子孫が先にキャプチャを持つので、この取り違えは
1416
+ // 実際に起こり得る。トラックのドラッグ状態を壊してよいのはトラック自身の喪失だけ。
1417
+ if (event.target !== event.currentTarget) {
1418
+ return
1419
+ }
1237
1420
  if (trackDragStateRef.current.pointerId !== event.pointerId) {
1238
1421
  return
1239
1422
  }
@@ -1373,6 +1556,8 @@ export const ScrollBar = ({
1373
1556
  key="thumb"
1374
1557
  ref={thumbRef}
1375
1558
  className={twMerge("aqvs-scrollbar-thumb", horizontal ? "aqvs-scrollbar-thumb-horizontal" : "aqvs-scrollbar-thumb-vertical")}
1559
+ // DOM フックはクラスセレクタでなく data-* 属性を使う (AGENTS.md 準拠)
1560
+ data-testid="aqvs-scrollbar-thumb"
1376
1561
  data-thumb-state={thumbVisualState}
1377
1562
  style={{
1378
1563
  borderRadius: scrollBarWidth - 1,