Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Request] Expose cached offsets and heights #278

Closed
mattwondra opened this issue Dec 7, 2023 · 4 comments · Fixed by #389
Closed

[Request] Expose cached offsets and heights #278

mattwondra opened this issue Dec 7, 2023 · 4 comments · Fixed by #389

Comments

@mattwondra
Copy link

Is your feature request related to a problem? Please describe.
VListHandle exposes a cache object with really useful details about the virtualized rendering — but unfortunately the keys are obfuscated in production and the CacheSnapshot type is opaque. I think there are valid use cases for consumers to access this data and would love to see it exposed in a way I know I can rely on.

One example use case — true sticky items.

Virtualization makes position: sticky challenging, because the sticky element may get scrolled outside of the rendered range and disappear. The official Sticky demo skirts around this with pseudo-virtualization — each group is virtualized to ensure that the header will always be rendered. However this creates limitations, such as making it impossible to scrollToIndex(...) on any particular row (#241).

However, if we know the virtual position of all the rows, we can actually have true sticky headers by rendering them next to the virtualized items. I've got an example in this branch — load it up in Storybook and you can see that to the user it looks exactly the same as the official Sticky demo. But now, each row is individually virtualized, meaning we can be more efficient with our overscan and use imperative functions like scrollToIndex(...) as expected.

However, this technique relies on grabbing offset data from the cache. I feel hesitant doing that, especially when in production it's clearly not meant for human consumption.

Is there any reason not to expose the inner data of cache so we can rely on it? Thanks!

Describe the solution you'd like
VListHandle.cache should not have its keys obfuscated in production, and should have transparent types and documentation.

Describe alternatives you've considered
We can already use the data in VListHandle.cache — but since it's not exposed to users I don't know if it's safe to assume it will always be available in that format.

@inokawa
Copy link
Owner

inokawa commented Dec 8, 2023

I think the structure of CacheSnapshot should be public and stable in some day but it's not ready yet. For example, I'm planning to replace the offset cache with better data structure for performance. If that suceeded, that may break some user implementations if public.
And CacheSnapshot is deep copied because it's just a snapshot and it's not a mutable handle of cache. Frequent calling of VListHandle.cache will not be good at performance if the list is long.

To address the real sticky problem, the rangeExtractor of TanStack Virtual like approach may be desirable.
Its sticky implementation with rangeExtractor is here:
https://github.com/TanStack/virtual/blob/62d6dc8cf917e8500b4709a59a46f936d9252f61/examples/react/sticky/src/main.tsx#L35-L48

Virtua is designed to calculate visible range filling viewport and extend it with overscan. If we extend range with user defined indexes not overscan (or maybe mixed?), they will be rendered always and suitable for sticky. And if user keeps mounted indexes and extends range with them, that will works like append only mode (#181). However this approach may make it difficult to solve #171 because items can become sparse.

@mattwondra
Copy link
Author

@inokawa Thanks for the feedback!

Have you verified that a rangeExtractor-like solution would actually enable sticky positioning in virtua? I've been testing with the current implementation (setting a huge overscan) but can't seem to make stickiness work. I think we'd need a custom Item because by default it's position: absolute which prevents my rendered components from sticky-ing to the actual scrolling context. Even with custom Item, we have to overwrite position to sticky and use top for the stuck position — which means we need to adjust the offset with margin, or translateY, and it seems to bug out whenever I try.

I can kind of fake it by creating a fixed-height container within the custom Item, with a much larger-height sticky bounding box that overflows outside of that:

const Item = forwardRef<HTMLDivElement, CustomItemComponentProps>(
  ({ children, style, index }, ref): ReactElement => (
    <div ref={ref} style={style}>
      <div style={{ height: 20 }}> {/* The virtualized item will be this height */}
        <div style={{ position: 'absolute', height: 700 }}> {/* The sticky content will scroll within this box */}
          <div style={{ position: 'sticky', top: 0 }}>
            {children}
          </div>
        </div>
      </div>
    </div>
  ),
)

In any case, the vanilla version of this still only enables "global stickiness" instead of "ranged stickiness". Meaning we can set a "top: X" but if we don't know the offset of the next sticky header we can't set a "bottom: Y". That means all the sticky items will simple stack on top of one another (like the tanstack example) rather than "moving out of the way for one another" (like the virtua example). If Virtua doesn't offer an API for items to know the position of other items, I suppose the best solution would be inspecting the DOM, and hoping the component is updated when cached positions change to re-calculate the DOM positions.

@inokawa
Copy link
Owner

inokawa commented Dec 9, 2023

I checked your branch again and read your comments, I understood what you meant, trickiness of positioning of sticky items and the difference of "global stickiness" and "ranged stickiness".

Probably adding getItemOffset(index: number): number like method to VListHandle is good for this case. It's just a exposed internal store._getItemOffset but it will be faster and much safer than direct access to cache snapshot.

@inokawa
Copy link
Owner

inokawa commented Feb 21, 2024

VirtualizerHandle.getItemOffset was added in 0.27.4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants