@aiquants/virtualscroll 3.5.0 → 3.6.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 +47 -0
- package/README.md +11 -1
- package/dist/ScrollBar.d.cts +24 -1
- package/dist/ScrollBar.d.ts +24 -1
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/TapScrollCircle.d.cts +11 -4
- package/dist/TapScrollCircle.d.ts +11 -4
- package/dist/TapScrollCircle.d.ts.map +1 -1
- package/dist/VirtualGrid.d.ts.map +1 -1
- package/dist/computeTapScrollVelocity.d.cts +120 -0
- package/dist/computeTapScrollVelocity.d.ts +121 -0
- package/dist/computeTapScrollVelocity.d.ts.map +1 -0
- 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 +2209 -2016
- package/dist/styles/virtualscroll.css +1 -1
- package/dist/styles/virtualscroll.standalone.css +1 -1
- package/dist/useGridTapScroll.d.cts +83 -0
- package/dist/useGridTapScroll.d.ts +84 -0
- package/dist/useGridTapScroll.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ScrollBar.tsx +16 -4
- package/src/ScrollPane.tsx +1 -0
- package/src/TapScrollCircle.tsx +41 -9
- package/src/VirtualGrid.tsx +155 -8
- package/src/computeTapScrollVelocity.ts +150 -0
- package/src/index.ts +1 -0
- package/src/styles/virtualscroll.css +31 -0
- package/src/useGridTapScroll.ts +370 -0
|
@@ -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[data-self-measured=true]{height:100%}.aqvs-scroll-pane-content{box-sizing:border-box;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:1px solid #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{box-sizing:border-box;background-color:#ffffffd9;border:1px solid #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;-webkit-tap-highlight-color:transparent;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[data-visible=false],.aqvs-scrollbar-thumb-wrapper[data-visible=false]{opacity:0;pointer-events:none}.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;letter-spacing:normal;background-color:#e0e0e0;border:0 solid;justify-content:center;align-items:center;padding:0;font-family:system-ui,sans-serif;font-size:.75rem;font-weight:400;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;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border:0 solid;border-radius:9999px;padding:.25rem 3rem;font-family:inherit;font-size:10px;font-weight:500;line-height:1.5;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:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scroll-to-edge-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.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-overlay[data-visible=false] .aqvs-scroll-to-edge-button{pointer-events:none}.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}.aqvs-live-region{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid{grid-template-rows:minmax(0,1fr) auto;width:100%;height:100%;display:grid;position:relative;overflow:hidden}.aqvs-grid-has-frozen-rows{grid-template-rows:auto minmax(0,1fr) auto}.aqvs-grid-has-trailing-rows{grid-template-rows:minmax(0,1fr) auto auto}.aqvs-grid-has-frozen-rows.aqvs-grid-has-trailing-rows{grid-template-rows:auto minmax(0,1fr) auto auto}.aqvs-grid-frozen-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-frozen-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-trailing-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-trailing-rows-inner{position:absolute;bottom:0;left:0;right:0}.aqvs-grid-trailing-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-main{min-width:0;min-height:0;position:relative}.aqvs-grid-row{width:100%;height:100%;transform:translateX(var(--aqvs-grid-hx-residual,0px));position:relative;overflow:visible}.aqvs-grid-cell{height:100%;position:absolute;top:0;overflow:hidden}.aqvs-grid-row.aqvs-grid-row-frozen-host{transform:none}.aqvs-grid-row-scroll{height:100%;top:0;left:var(--aqvs-grid-frozen-width,0px);right:var(--aqvs-grid-trailing-width,0px);position:absolute;overflow:hidden}.aqvs-grid-row-scroll-inner{width:100%;height:100%;transform:translateX(calc(var(--aqvs-grid-hx-residual,0px) - var(--aqvs-grid-frozen-width,0px)));position:absolute;top:0;left:0}.aqvs-grid-row-trailing{height:100%;top:0;left:max(var(--aqvs-grid-frozen-width,0px), calc(100% - var(--aqvs-grid-trailing-width,0px)));position:absolute;right:0;overflow:hidden}.aqvs-grid-row-trailing-inner{height:100%;width:var(--aqvs-grid-trailing-width,0px);position:absolute;top:0;right:0}.aqvs-grid-hbar-strip{min-width:0;padding-left:var(--aqvs-grid-frozen-width,0px);position:relative}.aqvs-grid-live-region{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}}
|
|
2
|
+
@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane[data-self-measured=true]{height:100%}.aqvs-scroll-pane-content{box-sizing:border-box;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:1px solid #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{box-sizing:border-box;background-color:#ffffffd9;border:1px solid #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;-webkit-tap-highlight-color:transparent;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[data-visible=false],.aqvs-scrollbar-thumb-wrapper[data-visible=false]{opacity:0;pointer-events:none}.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;letter-spacing:normal;background-color:#e0e0e0;border:0 solid;justify-content:center;align-items:center;padding:0;font-family:system-ui,sans-serif;font-size:.75rem;font-weight:400;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;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border:0 solid;border-radius:9999px;padding:.25rem 3rem;font-family:inherit;font-size:10px;font-weight:500;line-height:1.5;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:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scroll-to-edge-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.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-overlay[data-visible=false] .aqvs-scroll-to-edge-button{pointer-events:none}.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}.aqvs-live-region{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid{grid-template-rows:minmax(0,1fr) auto;width:100%;height:100%;display:grid;position:relative;overflow:hidden}.aqvs-grid-has-frozen-rows{grid-template-rows:auto minmax(0,1fr) auto}.aqvs-grid-has-trailing-rows{grid-template-rows:minmax(0,1fr) auto auto}.aqvs-grid-has-frozen-rows.aqvs-grid-has-trailing-rows{grid-template-rows:auto minmax(0,1fr) auto auto}.aqvs-grid-frozen-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-frozen-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-trailing-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-trailing-rows-inner{position:absolute;bottom:0;left:0;right:0}.aqvs-grid-trailing-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-main{min-width:0;min-height:0;position:relative}.aqvs-grid-row{width:100%;height:100%;transform:translateX(var(--aqvs-grid-hx-residual,0px));position:relative;overflow:visible}.aqvs-grid-cell{height:100%;position:absolute;top:0;overflow:hidden}.aqvs-grid-row.aqvs-grid-row-frozen-host{transform:none}.aqvs-grid-row-scroll{height:100%;top:0;left:var(--aqvs-grid-frozen-width,0px);right:var(--aqvs-grid-trailing-width,0px);position:absolute;overflow:hidden}.aqvs-grid-row-scroll-inner{width:100%;height:100%;transform:translateX(calc(var(--aqvs-grid-hx-residual,0px) - var(--aqvs-grid-frozen-width,0px)));position:absolute;top:0;left:0}.aqvs-grid-row-trailing{height:100%;top:0;left:max(var(--aqvs-grid-frozen-width,0px), calc(100% - var(--aqvs-grid-trailing-width,0px)));position:absolute;right:0;overflow:hidden}.aqvs-grid-row-trailing-inner{height:100%;width:var(--aqvs-grid-trailing-width,0px);position:absolute;top:0;right:0}.aqvs-grid-hbar-strip{min-width:0;padding-left:var(--aqvs-grid-frozen-width,0px);position:relative}.aqvs-grid-live-region{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid-tap-circle-wrapper{z-index:50;pointer-events:auto;position:absolute}.aqvs-grid-tap-circle{transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (forced-colors:active){.aqvs-tap-scroll-circle{outline-offset:-1px;border-radius:50%;outline:1px solid canvastext}}}
|
|
@@ -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 theme,base;@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane[data-self-measured=true]{height:100%}.aqvs-scroll-pane-content{box-sizing:border-box;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:1px solid #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{box-sizing:border-box;background-color:#ffffffd9;border:1px solid #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;-webkit-tap-highlight-color:transparent;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[data-visible=false],.aqvs-scrollbar-thumb-wrapper[data-visible=false]{opacity:0;pointer-events:none}.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;letter-spacing:normal;background-color:#e0e0e0;border:0 solid;justify-content:center;align-items:center;padding:0;font-family:system-ui,sans-serif;font-size:.75rem;font-weight:400;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;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border:0 solid;border-radius:9999px;padding:.25rem 3rem;font-family:inherit;font-size:10px;font-weight:500;line-height:1.5;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:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scroll-to-edge-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.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-overlay[data-visible=false] .aqvs-scroll-to-edge-button{pointer-events:none}.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}.aqvs-live-region{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid{grid-template-rows:minmax(0,1fr) auto;width:100%;height:100%;display:grid;position:relative;overflow:hidden}.aqvs-grid-has-frozen-rows{grid-template-rows:auto minmax(0,1fr) auto}.aqvs-grid-has-trailing-rows{grid-template-rows:minmax(0,1fr) auto auto}.aqvs-grid-has-frozen-rows.aqvs-grid-has-trailing-rows{grid-template-rows:auto minmax(0,1fr) auto auto}.aqvs-grid-frozen-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-frozen-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-trailing-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-trailing-rows-inner{position:absolute;bottom:0;left:0;right:0}.aqvs-grid-trailing-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-main{min-width:0;min-height:0;position:relative}.aqvs-grid-row{width:100%;height:100%;transform:translateX(var(--aqvs-grid-hx-residual,0px));position:relative;overflow:visible}.aqvs-grid-cell{height:100%;position:absolute;top:0;overflow:hidden}.aqvs-grid-row.aqvs-grid-row-frozen-host{transform:none}.aqvs-grid-row-scroll{height:100%;top:0;left:var(--aqvs-grid-frozen-width,0px);right:var(--aqvs-grid-trailing-width,0px);position:absolute;overflow:hidden}.aqvs-grid-row-scroll-inner{width:100%;height:100%;transform:translateX(calc(var(--aqvs-grid-hx-residual,0px) - var(--aqvs-grid-frozen-width,0px)));position:absolute;top:0;left:0}.aqvs-grid-row-trailing{height:100%;top:0;left:max(var(--aqvs-grid-frozen-width,0px), calc(100% - var(--aqvs-grid-trailing-width,0px)));position:absolute;right:0;overflow:hidden}.aqvs-grid-row-trailing-inner{height:100%;width:var(--aqvs-grid-trailing-width,0px);position:absolute;top:0;right:0}.aqvs-grid-hbar-strip{min-width:0;padding-left:var(--aqvs-grid-frozen-width,0px);position:relative}.aqvs-grid-live-region{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}}@layer utilities;
|
|
3
|
+
@layer theme,base;@layer components{.aqvs-scroll-pane{display:flex;position:relative}.aqvs-scroll-pane[data-self-measured=true]{height:100%}.aqvs-scroll-pane-content{box-sizing:border-box;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:1px solid #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{box-sizing:border-box;background-color:#ffffffd9;border:1px solid #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;-webkit-tap-highlight-color:transparent;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[data-visible=false],.aqvs-scrollbar-thumb-wrapper[data-visible=false]{opacity:0;pointer-events:none}.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;letter-spacing:normal;background-color:#e0e0e0;border:0 solid;justify-content:center;align-items:center;padding:0;font-family:system-ui,sans-serif;font-size:.75rem;font-weight:400;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;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;text-transform:uppercase;letter-spacing:.05em;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background-color:#1f2937cc;border:0 solid;border-radius:9999px;padding:.25rem 3rem;font-family:inherit;font-size:10px;font-weight:500;line-height:1.5;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:focus{outline-offset:2px;outline:2px solid #0000}.aqvs-scroll-to-edge-button:focus-visible{outline-offset:1px;outline:2px solid #60a5fa}.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-overlay[data-visible=false] .aqvs-scroll-to-edge-button{pointer-events:none}.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}.aqvs-live-region{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid{grid-template-rows:minmax(0,1fr) auto;width:100%;height:100%;display:grid;position:relative;overflow:hidden}.aqvs-grid-has-frozen-rows{grid-template-rows:auto minmax(0,1fr) auto}.aqvs-grid-has-trailing-rows{grid-template-rows:minmax(0,1fr) auto auto}.aqvs-grid-has-frozen-rows.aqvs-grid-has-trailing-rows{grid-template-rows:auto minmax(0,1fr) auto auto}.aqvs-grid-frozen-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-frozen-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-trailing-rows{min-width:0;position:relative;overflow:hidden}.aqvs-grid-trailing-rows-inner{position:absolute;bottom:0;left:0;right:0}.aqvs-grid-trailing-band-row{position:absolute;left:0;overflow:hidden}.aqvs-grid-main{min-width:0;min-height:0;position:relative}.aqvs-grid-row{width:100%;height:100%;transform:translateX(var(--aqvs-grid-hx-residual,0px));position:relative;overflow:visible}.aqvs-grid-cell{height:100%;position:absolute;top:0;overflow:hidden}.aqvs-grid-row.aqvs-grid-row-frozen-host{transform:none}.aqvs-grid-row-scroll{height:100%;top:0;left:var(--aqvs-grid-frozen-width,0px);right:var(--aqvs-grid-trailing-width,0px);position:absolute;overflow:hidden}.aqvs-grid-row-scroll-inner{width:100%;height:100%;transform:translateX(calc(var(--aqvs-grid-hx-residual,0px) - var(--aqvs-grid-frozen-width,0px)));position:absolute;top:0;left:0}.aqvs-grid-row-trailing{height:100%;top:0;left:max(var(--aqvs-grid-frozen-width,0px), calc(100% - var(--aqvs-grid-trailing-width,0px)));position:absolute;right:0;overflow:hidden}.aqvs-grid-row-trailing-inner{height:100%;width:var(--aqvs-grid-trailing-width,0px);position:absolute;top:0;right:0}.aqvs-grid-hbar-strip{min-width:0;padding-left:var(--aqvs-grid-frozen-width,0px);position:relative}.aqvs-grid-live-region{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.aqvs-grid-tap-circle-wrapper{z-index:50;pointer-events:auto;position:absolute}.aqvs-grid-tap-circle{transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (forced-colors:active){.aqvs-tap-scroll-circle{outline-offset:-1px;border-radius:50%;outline:1px solid canvastext}}}@layer utilities;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { TapScrollAxisSpeedParams } from './computeTapScrollVelocity.cjs';
|
|
2
|
+
import { TapScrollCircleDragState, TapScrollCircleHandle } from './TapScrollCircle.cjs';
|
|
3
|
+
/**
|
|
4
|
+
* Default corner-relative offset (px) for BOTH grid tap-circle axes — the reachability arm (R)
|
|
5
|
+
* of the three-arm placement law (plan §5). Derivation: full speed needs
|
|
6
|
+
* `maxVisualDistance = 240 px` of advancing pull toward the far screen edge, which is a hard
|
|
7
|
+
* stop on touch (`touch-action: none`). With `off = −200` the disc center sits
|
|
8
|
+
* `sbw − off − size/2 = 12 + 200 − 20 = 192 px` inboard of each far root edge, giving a
|
|
9
|
+
* worst-case (fullscreen, flush) reachable pull fraction `n = 192/240 = 0.80` (`0.66` with the
|
|
10
|
+
* ~34 px OS gesture-navigation zone) ≈ 78 % / 63 % of full-pull speed under the default curve
|
|
11
|
+
* (`n^1.1`). K = 120 (= −200 − (−80)) is half the pull range — the smallest K keeping the
|
|
12
|
+
* nav-zone envelope ≥ ~2/3. Deliberately a LITERAL: it must NOT track a consumer-supplied
|
|
13
|
+
* `maxVisualDistance` (coupling two knobs would make one silently move the other), and
|
|
14
|
+
* shrinking `maxVisualDistance` instead is forbidden (it changes the speed law's `maxDistance`
|
|
15
|
+
* input and breaks the T1 identity anchor).
|
|
16
|
+
* グリッドタップサークル両軸のコーナー相対既定オフセット (px) — 3 アーム配置則の到達性アーム
|
|
17
|
+
* (R)。導出: 全速には前進方向へ 240 px の引きが要り、タッチではスクリーン端がハードストップ。
|
|
18
|
+
* −200 で中心は各遠端から 192 px 内側 = 最悪ケース引き分率 0.80 (ナビ帯込み 0.66)。意図的な
|
|
19
|
+
* リテラル — 消費側 `maxVisualDistance` へ連動させない (ノブ連動の登記済み禁止)。
|
|
20
|
+
*/
|
|
21
|
+
export declare const GRID_TAP_CIRCLE_DEFAULT_OFFSET = -200;
|
|
22
|
+
/** Parameters for `useGridTapScroll` (read live through a params ref — identity-stability is not required of callers). / `useGridTapScroll` のパラメータ (params ref 経由で毎フレーム最新を読む — 呼出し側に identity 安定を要求しない)。 */
|
|
23
|
+
export type UseGridTapScrollParams = {
|
|
24
|
+
/** Whether the unified circle is live (resolved `enabled` ∧ `gridScrollable`). / 統合サークルが生きているか (解決済み `enabled` ∧ `gridScrollable`)。 */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Shared pull range (= `max(maxVisualDistance, 1)`). / 共有引き範囲 (= `max(maxVisualDistance, 1)`)。 */
|
|
27
|
+
maxDistance: number;
|
|
28
|
+
/** Horizontal speed parameters (§4.1 Pₓ). / 横軸速度パラメータ (§4.1 Pₓ)。 */
|
|
29
|
+
xSpeedParams: TapScrollAxisSpeedParams;
|
|
30
|
+
/** Vertical speed parameters (§4.1 P_y). / 縦軸速度パラメータ (§4.1 P_y)。 */
|
|
31
|
+
ySpeedParams: TapScrollAxisSpeedParams;
|
|
32
|
+
/** The grid's x apply seam (absolute position in, clamped applied position out). / グリッドの x 適用シーム (絶対位置 → クランプ後位置)。 */
|
|
33
|
+
applyHxRef: {
|
|
34
|
+
readonly current: (next: number) => number;
|
|
35
|
+
};
|
|
36
|
+
/** Synchronous-fresh hx read. / hx の同期・最新読み。 */
|
|
37
|
+
getHx: () => number;
|
|
38
|
+
/** The ONE extracted x clamp law (`getMaxHx` SSOT — §3.5-7). / 唯一抽出の x クランプ則 (`getMaxHx` SSOT — §3.5-7)。 */
|
|
39
|
+
getMaxHx: () => number;
|
|
40
|
+
/** 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 — 素のハンドル素通しではない)。 */
|
|
41
|
+
applyVy: (delta: number) => number;
|
|
42
|
+
/** Synchronous-fresh vy read. / vy の同期・最新読み。 */
|
|
43
|
+
getVy: () => number;
|
|
44
|
+
/** y extent read through the embedded handle's freshness channel (never re-derived). / 埋め込みハンドルの鮮度チャネル経由の y 延長 (再導出禁止)。 */
|
|
45
|
+
getMaxVy: () => number;
|
|
46
|
+
/** Pending column anchor — cleared ONLY on frames whose APPLIED hx delta ≠ 0 (§3.5-5). / 保留列アンカー — 適用済み hx デルタ ≠ 0 のフレームのみ解除 (§3.5-5)。 */
|
|
47
|
+
pendingColAnchorRef: {
|
|
48
|
+
current: unknown;
|
|
49
|
+
};
|
|
50
|
+
/** x extent-growth freshness key (columnWindow / total width — §3.5-3). / x 延長成長の鮮度キー (columnWindow / 総幅 — §3.5-3)。 */
|
|
51
|
+
xExtentFreshness: unknown;
|
|
52
|
+
/** y extent-growth freshness key (the vertical range state — §3.5-3). / y 延長成長の鮮度キー (縦レンジ state — §3.5-3)。 */
|
|
53
|
+
yExtentFreshness: unknown;
|
|
54
|
+
};
|
|
55
|
+
/** Result of `useGridTapScroll`. / `useGridTapScroll` の返り値。 */
|
|
56
|
+
export type UseGridTapScrollResult = {
|
|
57
|
+
/** Whether a tap drag is engaged (drives the opacity law). / タップドラッグ係合中か (opacity 則を駆動)。 */
|
|
58
|
+
isTapActive: boolean;
|
|
59
|
+
/** onDragChange handler for the grid's TapScrollCircle. / グリッドの TapScrollCircle へ渡す onDragChange。 */
|
|
60
|
+
handleTapCircleDragChange: (state: TapScrollCircleDragState) => void;
|
|
61
|
+
/** Handle ref for the grid's TapScrollCircle (reset + containment reads). / グリッドの TapScrollCircle 用ハンドル ref (リセットと内包判定)。 */
|
|
62
|
+
tapCircleHandleRef: React.RefObject<TapScrollCircleHandle | null>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Owns the unified tap circle's drag state and the two-axis rAF integration loop.
|
|
66
|
+
* 統合タップサークルのドラッグ状態と 2 軸 rAF 積分ループを所有するフック。
|
|
67
|
+
*
|
|
68
|
+
* Per-frame semantics (plan §8.3 — implemented verbatim):
|
|
69
|
+
*
|
|
70
|
+
* ```text
|
|
71
|
+
* dt = min(max((t − last)/1000, 0), TAP_SCROLL_MAX_FRAME_DELTA_SECONDS); skip if dt ≤ 0
|
|
72
|
+
* (vx, vy) = computeTapScrollVelocity({ offsetX, offsetY, maxDistance, x: Pₓ, y: P_y })
|
|
73
|
+
* x: request = vx·dt → residual-apply via applyHx; applied hx delta ≠ 0 clears the col anchor
|
|
74
|
+
* y: request = vy·dt → residual-apply via applyVy (scrollBy + vyRef sync)
|
|
75
|
+
* immobile_a = (maxExtent_a ≤ 0) || atBoundary_a || (o_a === 0) // atBoundary_a from THIS
|
|
76
|
+
* // frame's apply — never latched
|
|
77
|
+
* stop iff !active || direction === 0 || (immobile_x && immobile_y)
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @param params Injected seams (read live through a ref). / 注入シーム (ref 経由で最新を読む)。
|
|
81
|
+
* @returns Drag-state wiring for the grid's circle. / グリッドサークル用のドラッグ結線。
|
|
82
|
+
*/
|
|
83
|
+
export declare const useGridTapScroll: (params: UseGridTapScrollParams) => UseGridTapScrollResult;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { TapScrollAxisSpeedParams } from './computeTapScrollVelocity.ts';
|
|
2
|
+
import { TapScrollCircleDragState, TapScrollCircleHandle } from './TapScrollCircle.tsx';
|
|
3
|
+
/**
|
|
4
|
+
* Default corner-relative offset (px) for BOTH grid tap-circle axes — the reachability arm (R)
|
|
5
|
+
* of the three-arm placement law (plan §5). Derivation: full speed needs
|
|
6
|
+
* `maxVisualDistance = 240 px` of advancing pull toward the far screen edge, which is a hard
|
|
7
|
+
* stop on touch (`touch-action: none`). With `off = −200` the disc center sits
|
|
8
|
+
* `sbw − off − size/2 = 12 + 200 − 20 = 192 px` inboard of each far root edge, giving a
|
|
9
|
+
* worst-case (fullscreen, flush) reachable pull fraction `n = 192/240 = 0.80` (`0.66` with the
|
|
10
|
+
* ~34 px OS gesture-navigation zone) ≈ 78 % / 63 % of full-pull speed under the default curve
|
|
11
|
+
* (`n^1.1`). K = 120 (= −200 − (−80)) is half the pull range — the smallest K keeping the
|
|
12
|
+
* nav-zone envelope ≥ ~2/3. Deliberately a LITERAL: it must NOT track a consumer-supplied
|
|
13
|
+
* `maxVisualDistance` (coupling two knobs would make one silently move the other), and
|
|
14
|
+
* shrinking `maxVisualDistance` instead is forbidden (it changes the speed law's `maxDistance`
|
|
15
|
+
* input and breaks the T1 identity anchor).
|
|
16
|
+
* グリッドタップサークル両軸のコーナー相対既定オフセット (px) — 3 アーム配置則の到達性アーム
|
|
17
|
+
* (R)。導出: 全速には前進方向へ 240 px の引きが要り、タッチではスクリーン端がハードストップ。
|
|
18
|
+
* −200 で中心は各遠端から 192 px 内側 = 最悪ケース引き分率 0.80 (ナビ帯込み 0.66)。意図的な
|
|
19
|
+
* リテラル — 消費側 `maxVisualDistance` へ連動させない (ノブ連動の登記済み禁止)。
|
|
20
|
+
*/
|
|
21
|
+
export declare const GRID_TAP_CIRCLE_DEFAULT_OFFSET = -200;
|
|
22
|
+
/** Parameters for `useGridTapScroll` (read live through a params ref — identity-stability is not required of callers). / `useGridTapScroll` のパラメータ (params ref 経由で毎フレーム最新を読む — 呼出し側に identity 安定を要求しない)。 */
|
|
23
|
+
export type UseGridTapScrollParams = {
|
|
24
|
+
/** Whether the unified circle is live (resolved `enabled` ∧ `gridScrollable`). / 統合サークルが生きているか (解決済み `enabled` ∧ `gridScrollable`)。 */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Shared pull range (= `max(maxVisualDistance, 1)`). / 共有引き範囲 (= `max(maxVisualDistance, 1)`)。 */
|
|
27
|
+
maxDistance: number;
|
|
28
|
+
/** Horizontal speed parameters (§4.1 Pₓ). / 横軸速度パラメータ (§4.1 Pₓ)。 */
|
|
29
|
+
xSpeedParams: TapScrollAxisSpeedParams;
|
|
30
|
+
/** Vertical speed parameters (§4.1 P_y). / 縦軸速度パラメータ (§4.1 P_y)。 */
|
|
31
|
+
ySpeedParams: TapScrollAxisSpeedParams;
|
|
32
|
+
/** The grid's x apply seam (absolute position in, clamped applied position out). / グリッドの x 適用シーム (絶対位置 → クランプ後位置)。 */
|
|
33
|
+
applyHxRef: {
|
|
34
|
+
readonly current: (next: number) => number;
|
|
35
|
+
};
|
|
36
|
+
/** Synchronous-fresh hx read. / hx の同期・最新読み。 */
|
|
37
|
+
getHx: () => number;
|
|
38
|
+
/** The ONE extracted x clamp law (`getMaxHx` SSOT — §3.5-7). / 唯一抽出の x クランプ則 (`getMaxHx` SSOT — §3.5-7)。 */
|
|
39
|
+
getMaxHx: () => number;
|
|
40
|
+
/** 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 — 素のハンドル素通しではない)。 */
|
|
41
|
+
applyVy: (delta: number) => number;
|
|
42
|
+
/** Synchronous-fresh vy read. / vy の同期・最新読み。 */
|
|
43
|
+
getVy: () => number;
|
|
44
|
+
/** y extent read through the embedded handle's freshness channel (never re-derived). / 埋め込みハンドルの鮮度チャネル経由の y 延長 (再導出禁止)。 */
|
|
45
|
+
getMaxVy: () => number;
|
|
46
|
+
/** Pending column anchor — cleared ONLY on frames whose APPLIED hx delta ≠ 0 (§3.5-5). / 保留列アンカー — 適用済み hx デルタ ≠ 0 のフレームのみ解除 (§3.5-5)。 */
|
|
47
|
+
pendingColAnchorRef: {
|
|
48
|
+
current: unknown;
|
|
49
|
+
};
|
|
50
|
+
/** x extent-growth freshness key (columnWindow / total width — §3.5-3). / x 延長成長の鮮度キー (columnWindow / 総幅 — §3.5-3)。 */
|
|
51
|
+
xExtentFreshness: unknown;
|
|
52
|
+
/** y extent-growth freshness key (the vertical range state — §3.5-3). / y 延長成長の鮮度キー (縦レンジ state — §3.5-3)。 */
|
|
53
|
+
yExtentFreshness: unknown;
|
|
54
|
+
};
|
|
55
|
+
/** Result of `useGridTapScroll`. / `useGridTapScroll` の返り値。 */
|
|
56
|
+
export type UseGridTapScrollResult = {
|
|
57
|
+
/** Whether a tap drag is engaged (drives the opacity law). / タップドラッグ係合中か (opacity 則を駆動)。 */
|
|
58
|
+
isTapActive: boolean;
|
|
59
|
+
/** onDragChange handler for the grid's TapScrollCircle. / グリッドの TapScrollCircle へ渡す onDragChange。 */
|
|
60
|
+
handleTapCircleDragChange: (state: TapScrollCircleDragState) => void;
|
|
61
|
+
/** Handle ref for the grid's TapScrollCircle (reset + containment reads). / グリッドの TapScrollCircle 用ハンドル ref (リセットと内包判定)。 */
|
|
62
|
+
tapCircleHandleRef: React.RefObject<TapScrollCircleHandle | null>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Owns the unified tap circle's drag state and the two-axis rAF integration loop.
|
|
66
|
+
* 統合タップサークルのドラッグ状態と 2 軸 rAF 積分ループを所有するフック。
|
|
67
|
+
*
|
|
68
|
+
* Per-frame semantics (plan §8.3 — implemented verbatim):
|
|
69
|
+
*
|
|
70
|
+
* ```text
|
|
71
|
+
* dt = min(max((t − last)/1000, 0), TAP_SCROLL_MAX_FRAME_DELTA_SECONDS); skip if dt ≤ 0
|
|
72
|
+
* (vx, vy) = computeTapScrollVelocity({ offsetX, offsetY, maxDistance, x: Pₓ, y: P_y })
|
|
73
|
+
* x: request = vx·dt → residual-apply via applyHx; applied hx delta ≠ 0 clears the col anchor
|
|
74
|
+
* y: request = vy·dt → residual-apply via applyVy (scrollBy + vyRef sync)
|
|
75
|
+
* immobile_a = (maxExtent_a ≤ 0) || atBoundary_a || (o_a === 0) // atBoundary_a from THIS
|
|
76
|
+
* // frame's apply — never latched
|
|
77
|
+
* stop iff !active || direction === 0 || (immobile_x && immobile_y)
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @param params Injected seams (read live through a ref). / 注入シーム (ref 経由で最新を読む)。
|
|
81
|
+
* @returns Drag-state wiring for the grid's circle. / グリッドサークル用のドラッグ結線。
|
|
82
|
+
*/
|
|
83
|
+
export declare const useGridTapScroll: (params: UseGridTapScrollParams) => UseGridTapScrollResult;
|
|
84
|
+
//# sourceMappingURL=useGridTapScroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useGridTapScroll.d.ts","sourceRoot":"","sources":["../src/useGridTapScroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAA4B,KAAK,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAEvG,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAG5F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAalD,4MAA4M;AAC5M,MAAM,MAAM,sBAAsB,GAAG;IACjC,uIAAuI;IACvI,OAAO,EAAE,OAAO,CAAA;IAChB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,YAAY,EAAE,wBAAwB,CAAA;IACtC,oEAAoE;IACpE,YAAY,EAAE,wBAAwB,CAAA;IACtC,sHAAsH;IACtH,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAA;IAC1D,gDAAgD;IAChD,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,4GAA4G;IAC5G,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,4OAA4O;IAC5O,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,gDAAgD;IAChD,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,2HAA2H;IAC3H,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,0IAA0I;IAC1I,mBAAmB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;IACzC,uHAAuH;IACvH,gBAAgB,EAAE,OAAO,CAAA;IACzB,8GAA8G;IAC9G,gBAAgB,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAAG;IACjC,4FAA4F;IAC5F,WAAW,EAAE,OAAO,CAAA;IACpB,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACpE,4HAA4H;IAC5H,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;CACpE,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,sBAAsB,KAAG,sBAuQjE,CAAA"}
|
package/package.json
CHANGED
package/src/ScrollBar.tsx
CHANGED
|
@@ -169,6 +169,14 @@ export type ScrollBarProps = {
|
|
|
169
169
|
* 不変。VirtualGrid が兆列到達性のために有効化する、v3.2.0)。縦バーには影響しない。
|
|
170
170
|
*/
|
|
171
171
|
enableHorizontalTapCircle?: boolean
|
|
172
|
+
/**
|
|
173
|
+
* Skip the inline main-axis size on the bar root and let the flex host's stretch own it.
|
|
174
|
+
* バールートのインライン主軸寸を書かず、flex ホストの stretch に寸法を委ねる指定。
|
|
175
|
+
* 自己計測 ScrollPane 専用のシーム (in-flow 兄弟バーの固定高がホストの min-content 床になる
|
|
176
|
+
* 一方向ラチェットの遮断)。縦バー限定 — 横バーの主軸は flex 主軸で stretch が寸法を与えられ
|
|
177
|
+
* ないため、horizontal では無視して書込みを維持する。
|
|
178
|
+
*/
|
|
179
|
+
stretchMainSize?: boolean
|
|
172
180
|
/** The width of the scrollbar. / スクロールバーの幅。 */
|
|
173
181
|
scrollBarWidth?: number
|
|
174
182
|
/** Additional class names for the component. / コンポーネントの追加のクラス名。 */
|
|
@@ -218,9 +226,9 @@ const TAP_SCROLL_AUTO_SPEED_MAX_MULTIPLIER = 120
|
|
|
218
226
|
// teleport on resume, while still letting realistic low frame rates (down to ~10fps)
|
|
219
227
|
// advance at the configured px/second speed instead of being throttled to 60fps.
|
|
220
228
|
// バックグラウンド復帰時の飛びを防ぎつつ、低フレームレート環境でも設定速度(px/秒)を維持するための上限。
|
|
221
|
-
const TAP_SCROLL_MAX_FRAME_DELTA_SECONDS = 1 / 10
|
|
229
|
+
export const TAP_SCROLL_MAX_FRAME_DELTA_SECONDS = 1 / 10
|
|
222
230
|
|
|
223
|
-
const DEFAULT_TAP_SCROLL_CIRCLE_OPTIONS: ResolvedTapScrollCircleOptions = {
|
|
231
|
+
export const DEFAULT_TAP_SCROLL_CIRCLE_OPTIONS: ResolvedTapScrollCircleOptions = {
|
|
224
232
|
enabled: true,
|
|
225
233
|
size: 40,
|
|
226
234
|
offsetX: -80,
|
|
@@ -272,7 +280,7 @@ const createOrientationConfig = (horizontal: boolean): OrientationConfig => {
|
|
|
272
280
|
*
|
|
273
281
|
* ユーザー指定のタップスクロールサークル設定を内部デフォルトで補完。
|
|
274
282
|
*/
|
|
275
|
-
const resolveTapScrollCircleOptions = (options: ScrollBarTapCircleOptions | undefined, itemCount?: number): ResolvedTapScrollCircleOptions => {
|
|
283
|
+
export const resolveTapScrollCircleOptions = (options: ScrollBarTapCircleOptions | undefined, itemCount?: number): ResolvedTapScrollCircleOptions => {
|
|
276
284
|
const manualMaxSpeedMultiplier = options?.maxSpeedMultiplier
|
|
277
285
|
const maxSpeedMultiplier = typeof manualMaxSpeedMultiplier === "number" ? manualMaxSpeedMultiplier : computeAutoTapScrollMaxSpeedMultiplier(itemCount)
|
|
278
286
|
|
|
@@ -591,6 +599,7 @@ export const ScrollBar = ({
|
|
|
591
599
|
enableArrowButtons = true,
|
|
592
600
|
horizontal = false,
|
|
593
601
|
enableHorizontalTapCircle = false,
|
|
602
|
+
stretchMainSize = false,
|
|
594
603
|
scrollBarWidth = 12,
|
|
595
604
|
className,
|
|
596
605
|
ariaControls,
|
|
@@ -833,6 +842,7 @@ export const ScrollBar = ({
|
|
|
833
842
|
*/
|
|
834
843
|
const stepAutoScroll = useCallback(
|
|
835
844
|
(timestamp: number) => {
|
|
845
|
+
// この rAF ループ意味論は useGridTapScroll (グリッド 2 軸ドライバ) と双子 — 片方を直したらもう片方も直すこと (ADR-27)
|
|
836
846
|
const state = tapDragStateRef.current
|
|
837
847
|
const metrics = latestMetricsRef.current
|
|
838
848
|
if (!state.active || state.direction === 0) {
|
|
@@ -1475,7 +1485,9 @@ export const ScrollBar = ({
|
|
|
1475
1485
|
<div
|
|
1476
1486
|
className={twMerge("aqvs-scrollbar", directionClass, className)}
|
|
1477
1487
|
style={{
|
|
1478
|
-
|
|
1488
|
+
// 自己計測ペインの縦バーはインライン主軸寸を書かない (ラチェット遮断 — ペイン仕様 §2.5)。
|
|
1489
|
+
// 横バーは stretch が主軸を寸法決めできないため常に書く
|
|
1490
|
+
...(stretchMainSize && !horizontal ? {} : { [mainSizeKey]: viewportSize }),
|
|
1479
1491
|
[crossSizeKey]: scrollBarWidth,
|
|
1480
1492
|
}}
|
|
1481
1493
|
// 非表示はパッケージ CSS が担う。Tailwind ユーティリティ (opacity-0 等) に委ねると
|
package/src/ScrollPane.tsx
CHANGED
|
@@ -1625,6 +1625,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1625
1625
|
key="scrollbar"
|
|
1626
1626
|
contentSize={contentSize}
|
|
1627
1627
|
viewportSize={viewportSize}
|
|
1628
|
+
stretchMainSize={isSelfMeasuringViewport}
|
|
1628
1629
|
scrollPosition={scrollPositionRef.current}
|
|
1629
1630
|
onScroll={handleScrollBarScroll}
|
|
1630
1631
|
enableThumbDrag={enableThumbDrag}
|
package/src/TapScrollCircle.tsx
CHANGED
|
@@ -24,11 +24,18 @@ export type TapScrollCircleDragState = {
|
|
|
24
24
|
|
|
25
25
|
export type TapScrollCircleProps = {
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Projection subspace the direction/distance/dead-zone derivation reads (default "y" —
|
|
28
|
+
* vertical bars). The configured axis selects `A ⊆ R²` (`span(e_x)`, `span(e_y)`, or `R²`);
|
|
29
|
+
* `distance = ‖P_A(offset)‖₂` and `rawDirection` is the sign of the projection where a sign
|
|
30
|
+
* exists — `"xy"` has none, so `direction` degrades to the radial engagement flag `{0, 1}`
|
|
31
|
+
* and 2-D orientation lives in the always-carried `offsetX`/`offsetY` (v3.6.0). Horizontal
|
|
32
|
+
* bars pass "x" (v3.2.0).
|
|
33
|
+
* 方向・距離・デッドゾーン導出が読む射影部分空間 (既定 "y" — 縦バー)。設定軸が `A ⊆ R²` を
|
|
34
|
+
* 選び、`distance = ‖P_A(offset)‖₂`、`rawDirection` は射影の符号 (符号が存在する場合)。
|
|
35
|
+
* `"xy"` に符号は無く `direction` は径方向係合 `{0, 1}` へ退化し、向きは無条件搬送の
|
|
36
|
+
* `offsetX`/`offsetY` が運ぶ (v3.6.0)。横バーは "x" を渡す (v3.2.0)。
|
|
30
37
|
*/
|
|
31
|
-
axis?: "x" | "y"
|
|
38
|
+
axis?: "x" | "y" | "xy"
|
|
32
39
|
/** Called whenever the drag state updates. / ドラッグ状態が更新された際に呼び出されるコールバック。 */
|
|
33
40
|
onDragChange: (state: TapScrollCircleDragState) => void
|
|
34
41
|
/** Additional class names for styling. / 追加のスタイル用クラス名。 */
|
|
@@ -150,11 +157,21 @@ export const TapScrollCircle = memo(
|
|
|
150
157
|
const { x, y } = centerRef.current
|
|
151
158
|
const offsetX = clientX - x
|
|
152
159
|
const offsetY = clientY - y
|
|
153
|
-
//
|
|
160
|
+
// 方向・距離・デッドゾーンの導出 = 射影部分空間 (v3.6.0): 1 次元軸は従来式
|
|
161
|
+
// そのまま (バイト恒等)、"xy" は径距離 + 径方向係合 {0, 1}。rawDirection が
|
|
162
|
+
// {0, 1} に退化すると反転分岐 (raw !== prev && prev !== 0) は到達不能になり、
|
|
163
|
+
// 中立維持分岐が係合ヒステリシスを与える — 新定数ゼロ。
|
|
154
164
|
// 導出以外 (ヒステリシス・視覚) は軸非依存のまま
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
let distance: number
|
|
166
|
+
let rawDirection: -1 | 0 | 1
|
|
167
|
+
if (axis === "xy") {
|
|
168
|
+
distance = Math.hypot(offsetX, offsetY)
|
|
169
|
+
rawDirection = distance < DEAD_ZONE ? 0 : 1
|
|
170
|
+
} else {
|
|
171
|
+
const primaryOffset = axis === "x" ? offsetX : offsetY
|
|
172
|
+
distance = Math.abs(primaryOffset)
|
|
173
|
+
rawDirection = distance < DEAD_ZONE ? 0 : primaryOffset < 0 ? -1 : 1
|
|
174
|
+
}
|
|
158
175
|
const previousDirection = lastStableDirectionRef.current
|
|
159
176
|
let direction: -1 | 0 | 1 = rawDirection
|
|
160
177
|
|
|
@@ -345,6 +362,21 @@ export const TapScrollCircle = memo(
|
|
|
345
362
|
const sizeScale = size / 64
|
|
346
363
|
const normalized = Math.min(dragState.distance, maxVisualDistance) / maxVisualDistance
|
|
347
364
|
const pullOffset = dragState.direction * normalized * 10 * sizeScale
|
|
365
|
+
// 引き transform の発出は軸キー (v3.6.0): "y" はリテラル translateY を維持 (バイト恒等)、
|
|
366
|
+
// "x" は translateX (右ドラッグで下へ動く出荷済み quirk の修正 — 裸バー opt-in 経由のみ到達)、
|
|
367
|
+
// "xy" は 2 次元 translate — (dx, dy) = pullOffset · (ox, oy)/r、r = 0 は単位ベクトルが
|
|
368
|
+
// 定義できないため (0, 0) で静止
|
|
369
|
+
let pullTransform: string
|
|
370
|
+
if (axis === "xy") {
|
|
371
|
+
const radial = dragState.distance
|
|
372
|
+
const pullDx = radial === 0 ? 0 : (pullOffset * dragState.offsetX) / radial
|
|
373
|
+
const pullDy = radial === 0 ? 0 : (pullOffset * dragState.offsetY) / radial
|
|
374
|
+
pullTransform = `translate(${pullDx}px, ${pullDy}px)`
|
|
375
|
+
} else if (axis === "x") {
|
|
376
|
+
pullTransform = `translateX(${pullOffset}px)`
|
|
377
|
+
} else {
|
|
378
|
+
pullTransform = `translateY(${pullOffset}px)`
|
|
379
|
+
}
|
|
348
380
|
const visualRenderer = renderVisual ?? defaultRenderVisual
|
|
349
381
|
const visualProps: TapScrollCircleRenderProps = {
|
|
350
382
|
dragState,
|
|
@@ -357,7 +389,7 @@ export const TapScrollCircle = memo(
|
|
|
357
389
|
...style,
|
|
358
390
|
width: size,
|
|
359
391
|
height: size,
|
|
360
|
-
transform:
|
|
392
|
+
transform: pullTransform,
|
|
361
393
|
}
|
|
362
394
|
rootStyle.opacity = clampedOpacity
|
|
363
395
|
return (
|