Skip to content

Commit

Permalink
Merge pull request #393 from inokawa/vue-scroll-ref
Browse files Browse the repository at this point in the history
Add scrollRef prop to Vue Virtualizer
  • Loading branch information
inokawa authored Mar 19, 2024
2 parents 51ad8f7 + 3ed1cc7 commit 10ae1dc
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 5 deletions.
22 changes: 18 additions & 4 deletions src/vue/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SlotsType,
ComponentOptionsWithObjectProps,
ComponentObjectPropsOptions,
PropType,
} from "vue";
import {
SCROLL_IDLE,
Expand All @@ -26,6 +27,7 @@ import { createScroller } from "../core/scroller";
import { ScrollToIndexOpts } from "../core/types";
import { ListItem } from "./ListItem";
import { getKey } from "./utils";
import { microtask } from "../core/utils";

export interface VirtualizerHandle {
/**
Expand Down Expand Up @@ -96,6 +98,10 @@ const props = {
* A prop for SSR. If set, the specified amount of items will be mounted in the initial rendering regardless of the container size until hydrated.
*/
ssrCount: Number,
/**
* Reference to the scrollable element. The default will get the parent element of virtualizer.
*/
scrollRef: Object as PropType<HTMLElement>,
} satisfies ComponentObjectPropsOptions;

export const Virtualizer = /*#__PURE__*/ defineComponent({
Expand Down Expand Up @@ -135,10 +141,18 @@ export const Virtualizer = /*#__PURE__*/ defineComponent({
onMounted(() => {
isSSR = false;

const scrollable = containerRef.value!.parentElement;
if (!scrollable) return;
resizer._observeRoot(scrollable);
scroller._observe(scrollable);
microtask(() => {
const assignScrollableElement = (e: HTMLElement) => {
resizer._observeRoot(e);
scroller._observe(e);
};
if (props.scrollRef) {
// parent's ref doesn't exist when onMounted is called
assignScrollableElement(props.scrollRef!);
} else {
assignScrollableElement(containerRef.value!.parentElement!);
}
});
});
onUnmounted(() => {
unsubscribeStore();
Expand Down
1 change: 0 additions & 1 deletion stories/vue/HeaderAndFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const createItem = (i: number) => ({ index: i, size: sizes[i % 4] + 'px' })
const data = Array.from({ length: 1000 }).map((_, i) => createItem(i));
const headerHeight = 400;
</script>

Expand Down
37 changes: 37 additions & 0 deletions stories/vue/Nested.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Virtualizer } from '../../src/vue'
const sizes = [20, 40, 180, 77];
const createItem = (i: number) => ({ index: i, size: sizes[i % 4] + 'px' })
const data = Array.from({ length: 1000 }).map((_, i) => createItem(i));
const outerPadding = 40;
const innerPadding = 60;
const scrollRef = ref<HTMLElement>();
</script>

<template>
<div ref="scrollRef" :style="{
width: '100%',
height: '100vh',
overflowY: 'auto',
// opt out browser's scroll anchoring on header/footer because it will conflict to scroll anchoring of virtualizer
overflowAnchor: 'none'
}">
<div :style="{ backgroundColor: 'burlywood', padding: outerPadding + 'px' }">
<div :style="{ backgroundColor: 'steelblue', padding: innerPadding + 'px' }">
<Virtualizer :data="data" #default="item" :scrollRef="scrollRef" :startMargin="outerPadding + innerPadding">
<div :key="item.index" :style="{ height: item.size, background: 'white', borderBottom: 'solid 1px #ccc' }">
{{ item.index }}
</div>
</Virtualizer>
</div>
</div>
</div>
</template>

<style scoped>
/* NOP */
</style>
8 changes: 8 additions & 0 deletions stories/vue/Virtualizer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "@storybook/vue3";
import { Virtualizer } from "../../src/vue";
import HeaderAndFooterComponent from "./HeaderAndFooter.vue";
import NestedComponent from "./Nested.vue";

export default {
component: Virtualizer,
Expand All @@ -12,3 +13,10 @@ export const HeaderAndFooter: StoryObj = {
template: "<Component />",
}),
};

export const Nested: StoryObj = {
render: () => ({
components: { Component: NestedComponent },
template: "<Component />",
}),
};

0 comments on commit 10ae1dc

Please sign in to comment.