Skip to content

Commit

Permalink
fix(main-scroll): obtain observed container element reliably to work …
Browse files Browse the repository at this point in the history
…with any Vue version (#1552)
  • Loading branch information
joseacabaneros authored Jul 9, 2024
1 parent 59d3beb commit 0b4e415
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 173 deletions.
34 changes: 28 additions & 6 deletions packages/_vue3-migration-test/src/x-modules/scroll/test-scroll.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts">
import MainScroll from '../../../../x-components/src/x-modules/scroll/components/main-scroll.vue';
import MainScrollItem from '../../../../x-components/src/x-modules/scroll/components/main-scroll-item.vue';
const items = Array.from({ length: 24 }, (_, index) => ({ id: `item-${index}` }));
</script>

<template>
<MainScroll class="CUSTOM-CLASS" data-custom="CUSTOM-DATA-ATTR">
<ul class="list" data-test="scroll">
<MainScrollItem v-for="item in items" :key="item.id" class="item" tag="article" :item="item">
{{ item.id }}
</MainScrollItem>
</ul>
</MainScroll>
<MainScroll>
<ul class="list" data-test="scroll">
<MainScrollItem v-for="item in items" :key="item.id" class="item" tag="article" :item="item">
Expand All @@ -14,6 +15,27 @@
</MainScroll>
</template>

<script setup lang="ts">
import { useXBus } from '../../../../x-components/src/composables/use-x-bus';
import { XEvent } from '../../../../x-components/src/wiring/events.types';
import MainScroll from '../../../../x-components/src/x-modules/scroll/components/main-scroll.vue';
import MainScrollItem from '../../../../x-components/src/x-modules/scroll/components/main-scroll-item.vue';
const xBus = useXBus();
const items = Array.from({ length: 24 }, (_, index) => ({ id: `item-${index}` }));
const events: XEvent[] = [
'UserScrolledToElement',
'ScrollRestoreSucceeded',
'ScrollRestoreFailed'
];
events.forEach(event => {
// eslint-disable-next-line no-console
xBus.on(event, true).subscribe(args => console.log(`${event} event ->`, args));
});
</script>

<style scoped lang="scss">
.list {
overflow: auto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ScrollXModule } from '../../../../x-components/src/x-modules/scroll/x-m
export const scrollXModule: PrivateXModuleOptions<ScrollXModule> = {
storeModule: {
state: {
pendingScrollTo: 'item-10'
pendingScrollTo: 'item-15'
}
}
};
3 changes: 2 additions & 1 deletion packages/x-components/src/components/no-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { useNoElementRender } from '../composables/use-no-element-render';
* @internal
*/
export const NoElement = defineComponent({
setup(props, { slots }) {
name: 'NoElement',
setup(_, { slots }) {
return () => useNoElementRender(slots);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,67 +33,35 @@
name: 'MainScrollItem',
xModule: scrollXModule.name,
props: {
/**
* The item data. Used to set the scroll identifier.
*
* @public
*/
/** The item data. Used to set the scroll identifier. */
item: {
type: Object as PropType<Identifiable>,
required: true
},
/**
* The tag to render.
*
* @public
*/
/** The tag to render. */
tag: {
type: [String, Object] as PropType<string | typeof Vue>,
default: 'div'
}
},
setup(props) {
type ElementRef = {
$el: HTMLElement;
};
const xBus = useXBus();
/**
* Rendered HTML node.
*
* @public
*/
const rootRef = ref<ElementRef | HTMLElement | null>(null);
/** Rendered HTML node. */
const rootRef = ref<HTMLElement>();
/**
* Pending identifier scroll position to restore. If it matches the {@link MainScrollItem} item
* `id` property, this component should be scrolled into view.
*
* @internal
*/
const { pendingScrollTo } = useState('scroll', ['pendingScrollTo']);
/**
* Observer to detect the first visible element.
*
* @internal
*/
/** Observer to detect the first visible element. */
const firstVisibleItemObserver = inject<Ref<ScrollVisibilityObserver> | null>(
ScrollObserverKey as string,
null
);
/**
* Checks if a given value is an `ElementRef` object.
*
* @param value - The value to check.
* @returns `true` if the value is an `ElementRef` object, `false` otherwise.
*
* @internal
*/
const isElementRef = (value: any): value is ElementRef => {
return value && value.$el instanceof HTMLElement;
};
/**
* Initialises the element visibility observation, stopping the previous one if it has.
*
Expand All @@ -105,14 +73,12 @@
oldObserver: ScrollVisibilityObserver | null
): void => {
{
if (rootRef.value !== null) {
const htmlElement = isElementRef(rootRef.value) ? rootRef.value.$el : rootRef.value;
oldObserver?.unobserve(htmlElement);
newObserver?.observe(htmlElement);
if (rootRef.value) {
oldObserver?.unobserve(rootRef.value);
newObserver?.observe(rootRef.value);
if (pendingScrollTo.value === props.item.id) {
Vue.nextTick(() => {
htmlElement.scrollIntoView({
rootRef.value!.scrollIntoView({
block: 'center'
});
});
Expand All @@ -122,15 +88,10 @@
}
};
/**
* Detaches the observer from the rendered element to prevent memory leaks.
*
* @internal
*/
/** Detaches the observer from the rendered element to prevent memory leaks. */
onBeforeUnmount(() => {
if (rootRef.value !== null) {
const htmlElement = isElementRef(rootRef.value) ? rootRef.value.$el : rootRef.value;
firstVisibleItemObserver?.value.unobserve(htmlElement);
if (rootRef.value) {
firstVisibleItemObserver?.value.unobserve(rootRef.value);
}
});
Expand All @@ -139,8 +100,6 @@
* - Observes the rendered element to detect if it is the first visible item.
* - If the rendered element matches the {@link MainScrollItem.pendingScrollTo}, scrolls the
* element into the first position of the view.
*
* @internal
*/
onMounted(() => {
nextTick(() => {
Expand All @@ -151,7 +110,7 @@
});
});
return { rootRef, firstVisibleItemObserver, observeItem, isElementRef };
return { rootRef };
}
});
</script>
Expand Down
Loading

0 comments on commit 0b4e415

Please sign in to comment.