@aiquants/virtualscroll 1.22.1 → 1.24.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 +22 -0
- package/README.md +36 -0
- package/dist/ScrollPane.d.cts +24 -0
- package/dist/ScrollPane.d.ts +24 -0
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/VirtualScroll.d.cts +25 -0
- package/dist/VirtualScroll.d.ts +25 -0
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1319 -1308
- package/dist/styles/virtualscroll.standalone.css +1 -1
- package/package.json +1 -1
- package/src/ScrollPane.tsx +30 -2
- package/src/VirtualScroll.tsx +47 -3
|
@@ -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-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}
|
|
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}.row-0{grid-row:0}.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}}.grid{display:grid}.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}
|
package/package.json
CHANGED
package/src/ScrollPane.tsx
CHANGED
|
@@ -92,6 +92,27 @@ export type ScrollPaneProps = {
|
|
|
92
92
|
renderOverlay?: () => React.ReactNode
|
|
93
93
|
/** Initial scroll position. / 初期のスクロール位置。 */
|
|
94
94
|
initialScrollPosition?: number
|
|
95
|
+
/**
|
|
96
|
+
* ARIA / identity attributes applied to the scrollable CONTENT element — the element that
|
|
97
|
+
* directly owns the rendered items and excludes the scrollbar and overlay chrome.
|
|
98
|
+
*
|
|
99
|
+
* This is the correct host for a composite widget role (`listbox` / `tree` / `grid`): the outer
|
|
100
|
+
* root also contains the scrollbar, whose `role="scrollbar"` would become an invalid owned child
|
|
101
|
+
* of that role. Supplying `id` here also overrides the generated one, so the scrollbar's
|
|
102
|
+
* `aria-controls` keeps pointing at the same element.
|
|
103
|
+
*
|
|
104
|
+
* The type is deliberately narrow: `className`, `style` and `ref` are not expressible, so a
|
|
105
|
+
* consumer cannot break the pane's layout through this seam.
|
|
106
|
+
*
|
|
107
|
+
* スクロール可能な「コンテンツ要素」へ適用する ARIA / 識別属性。行だけを直接所有し、
|
|
108
|
+
* スクロールバーとオーバーレイを含まない要素であり、合成ウィジェットのロール
|
|
109
|
+
* (`listbox` / `tree` / `grid`) を置くべき唯一正しい位置。ルート要素にはスクロールバーも
|
|
110
|
+
* 含まれるため、そちらに置くと `role="scrollbar"` が不正な所有子になる。`id` を渡すと生成 id を
|
|
111
|
+
* 上書きするので、スクロールバーの `aria-controls` も同じ要素を指し続ける。
|
|
112
|
+
*
|
|
113
|
+
* `className` / `style` / `ref` を表現できない型にしてあり、この口からレイアウトを壊せない。
|
|
114
|
+
*/
|
|
115
|
+
contentProps?: React.AriaAttributes & { id?: string; role?: React.AriaRole }
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
export type ScrollPaneInertiaOptions = {
|
|
@@ -226,6 +247,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
226
247
|
visibleEndIndex,
|
|
227
248
|
renderOverlay,
|
|
228
249
|
initialScrollPosition = 0,
|
|
250
|
+
contentProps,
|
|
229
251
|
},
|
|
230
252
|
ref,
|
|
231
253
|
) => {
|
|
@@ -661,6 +683,9 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
661
683
|
)
|
|
662
684
|
|
|
663
685
|
const id = useId()
|
|
686
|
+
// コンテンツ要素の id。利用側が contentProps.id を渡した場合はそちらを正とし、
|
|
687
|
+
// ScrollBar の aria-controls も同じ要素を指すようにする
|
|
688
|
+
const contentId = contentProps?.id ?? id
|
|
664
689
|
|
|
665
690
|
const dragStateRef = useRef<DragState>({
|
|
666
691
|
pointerType: "",
|
|
@@ -1228,7 +1253,10 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1228
1253
|
// (空のペインがページのパンを殺すデッドゾーンになり、そちらの実害の方が大きいため)。
|
|
1229
1254
|
...(enablePointerDrag && isScrollable && pointerDragInputs.some((type) => type === "touch" || type === "pen") ? { touchAction: "none" } : {}),
|
|
1230
1255
|
}}
|
|
1231
|
-
|
|
1256
|
+
// ARIA 属性はコンテンツ要素に載せる。ルート要素にはスクロールバーも含まれるため、
|
|
1257
|
+
// 合成ウィジェットのロールをそちらに置くと不正な所有子が生まれる
|
|
1258
|
+
{...contentProps}
|
|
1259
|
+
id={contentId}>
|
|
1232
1260
|
{background}
|
|
1233
1261
|
{children(scrollPositionRef.current)}
|
|
1234
1262
|
</div>
|
|
@@ -1242,7 +1270,7 @@ export const ScrollPane = forwardRef<ScrollPaneHandle, ScrollPaneProps>(
|
|
|
1242
1270
|
enableTrackClick={enableTrackClick}
|
|
1243
1271
|
enableArrowButtons={enableArrowButtons}
|
|
1244
1272
|
scrollBarWidth={scrollBarWidth}
|
|
1245
|
-
ariaControls={
|
|
1273
|
+
ariaControls={contentId}
|
|
1246
1274
|
tapScrollCircleOptions={tapScrollCircleOptions}
|
|
1247
1275
|
itemCount={itemCount}
|
|
1248
1276
|
renderThumbOverlay={renderThumbOverlay}
|
package/src/VirtualScroll.tsx
CHANGED
|
@@ -139,6 +139,31 @@ export type VirtualScrollProps<T> = {
|
|
|
139
139
|
behaviorOptions?: VirtualScrollBehaviorOptions
|
|
140
140
|
/** Delegates horizontal wheel/trackpad delta to an upstream owner (e.g. a frozen-column grid). / 横ホイール量を上流へ委譲する。 */
|
|
141
141
|
onWheelHorizontal?: (deltaX: number) => void
|
|
142
|
+
/**
|
|
143
|
+
* ARIA / identity attributes applied to the scrollable CONTENT element — the element that
|
|
144
|
+
* directly owns the rendered rows and excludes the scrollbar and overlay chrome.
|
|
145
|
+
*
|
|
146
|
+
* This is where a composite widget role belongs. Putting `role="listbox"` / `"tree"` / `"grid"`
|
|
147
|
+
* on the outer root instead would make the scrollbar (`role="scrollbar"`) an owned child of that
|
|
148
|
+
* role, which ARIA does not allow. Passing `id` here also overrides the generated one, so the
|
|
149
|
+
* scrollbar's `aria-controls` keeps referring to the same element.
|
|
150
|
+
*
|
|
151
|
+
* Intermediate wrappers between the content element and the rows carry no role, no global ARIA
|
|
152
|
+
* attribute and no tabindex, so they are transparent to the owned-element relationship — but
|
|
153
|
+
* only while `behaviorOptions.enableKeyboardNavigation` is `false`, which is what a consumer
|
|
154
|
+
* driving selection through `aria-activedescendant` should be doing anyway.
|
|
155
|
+
*
|
|
156
|
+
* スクロール可能な「コンテンツ要素」へ適用する ARIA / 識別属性。行だけを直接所有し、
|
|
157
|
+
* スクロールバーとオーバーレイを含まないため、合成ウィジェットのロールはここへ置く。
|
|
158
|
+
* ルート要素に置くとスクロールバー (`role="scrollbar"`) が不正な所有子になる。`id` を渡すと
|
|
159
|
+
* 生成 id を上書きし、スクロールバーの `aria-controls` も同じ要素を指し続ける。
|
|
160
|
+
*
|
|
161
|
+
* コンテンツ要素と行の間のラッパーはロール・グローバル ARIA 属性・tabindex のいずれも持たない
|
|
162
|
+
* ため所有関係に対して透過だが、それが成り立つのは
|
|
163
|
+
* `behaviorOptions.enableKeyboardNavigation` が `false` のときに限る (行ラッパーへ
|
|
164
|
+
* `tabIndex={-1}` が付くため)。`aria-activedescendant` 方式の利用側は元より false にすべきである。
|
|
165
|
+
*/
|
|
166
|
+
contentProps?: ScrollPaneProps["contentProps"]
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
/**
|
|
@@ -155,7 +180,12 @@ const normalizeInsets = (insets?: ScrollPaneContentInsets): Required<ScrollPaneC
|
|
|
155
180
|
|
|
156
181
|
const toLogicalPositionWithInset = (pane: number, top: number) => (pane <= top ? 0 : pane - top)
|
|
157
182
|
|
|
158
|
-
|
|
183
|
+
// 論理 0 は「本当の先頭」= ペイン 0 (上インセットが見えている状態) へ写像する。
|
|
184
|
+
// ❗ `logical <= 0 ? top` (インセットを隠して先頭行を上端密着にする端点) へ戻さないこと:
|
|
185
|
+
// マウント既定 (initialScroll 未指定 = ペイン 0) や ScrollPane のクランプ休止点 (ペイン 0) と
|
|
186
|
+
// 端点が食い違い、「保存した論理 0 を復元すると数 px スクロールされて見える」実害バグになる
|
|
187
|
+
// (toLogical はペイン [0..top] を論理 0 へ潰す不可逆写像のため、逆写像の端点選択がここで確定する)
|
|
188
|
+
const toPanePositionWithInset = (logical: number, top: number) => (logical <= 0 ? 0 : logical + top)
|
|
159
189
|
|
|
160
190
|
/**
|
|
161
191
|
* Maximum run length of consecutive zero-height rows scanned linearly before attempting an
|
|
@@ -547,15 +577,27 @@ const useThrottledInvoker = <Callback, Payload>(callbackRef: React.MutableRefObj
|
|
|
547
577
|
// last: 最終実行時刻, id: RAF ID, arg: 待機中の引数
|
|
548
578
|
const state = useRef({ last: 0, id: null as number | null, arg: null as Payload | null }).current
|
|
549
579
|
|
|
550
|
-
// クリーンアップ: アンマウント時にRAF
|
|
580
|
+
// クリーンアップ: アンマウント時に RAF をキャンセルし、待機中のペイロードは**破棄せず同期配信**する。
|
|
581
|
+
// ❗ 破棄すると、フリング中のタブ切替などで確定済みの待機通知まで消費側へ届かず、
|
|
582
|
+
// 「スクロールのたびに書き戻す」型の位置保存が取りこぼす。なおこの flush が救えるのは
|
|
583
|
+
// 「キューに載った最後のペイロード」まで: 最後の render-loop tick 以後の移動 (最大 1 フレーム分、
|
|
584
|
+
// 慣性で概ね 100px 未満) は残余として許容する (読中でないフリング途中の位置のため実害は軽微)
|
|
551
585
|
useEffect(
|
|
552
586
|
() => () => {
|
|
553
587
|
if (state.id !== null) {
|
|
554
588
|
cancelAnimationFrame(state.id)
|
|
555
589
|
state.id = null
|
|
556
590
|
}
|
|
591
|
+
if (state.arg !== null && callbackRef.current) {
|
|
592
|
+
try {
|
|
593
|
+
invoke(callbackRef.current, state.arg)
|
|
594
|
+
} catch (e) {
|
|
595
|
+
console.error("[useThrottledInvoker] Error flushing pending callback on unmount", e)
|
|
596
|
+
}
|
|
597
|
+
state.arg = null
|
|
598
|
+
}
|
|
557
599
|
},
|
|
558
|
-
[state],
|
|
600
|
+
[state, callbackRef, invoke],
|
|
559
601
|
)
|
|
560
602
|
|
|
561
603
|
return useCallback(
|
|
@@ -667,6 +709,7 @@ const VirtualScrollInner = <T,>(
|
|
|
667
709
|
scrollBarOptions,
|
|
668
710
|
behaviorOptions,
|
|
669
711
|
onWheelHorizontal,
|
|
712
|
+
contentProps,
|
|
670
713
|
}: VirtualScrollProps<T>,
|
|
671
714
|
ref: React.Ref<VirtualScrollHandle>,
|
|
672
715
|
) => {
|
|
@@ -1882,6 +1925,7 @@ const VirtualScrollInner = <T,>(
|
|
|
1882
1925
|
wheelSpeedMultiplier={wheelSpeedMultiplier}
|
|
1883
1926
|
onWheelHorizontal={onWheelHorizontal}
|
|
1884
1927
|
contentInsets={resolvedInsets}
|
|
1928
|
+
contentProps={contentProps}
|
|
1885
1929
|
visibleStartIndex={visibleStartIndex}
|
|
1886
1930
|
visibleEndIndex={visibleEndIndex}
|
|
1887
1931
|
renderOverlay={renderOverlay}
|