@empathyco/x-components
Version:
Empathy X Components
219 lines (216 loc) • 7.21 kB
JavaScript
import { isArray } from '@empathyco/x-utils';
import { ref, computed, onMounted, nextTick, watch } from 'vue';
import { use$x } from '../../composables/use-_x.js';
import { throttle } from '../../utils/throttle.js';
/**
* Composable to share Scroll logic.
*
* @param props - Composable props.
* @param context - Component setup context.
* @param scrollEl - The scrolling container reference.
* @returns A throttled version of the function to store the scroll data.
* @public
*/
function useScroll(props, { emit }, scrollEl) {
/**
* Property for getting the client height of scroll.
*
* @internal
*/
const clientHeight = ref(0);
/**
* Property for getting the current position of scroll.
*
* @internal
*/
const currentPosition = ref(0);
/**
* Property for getting the previous position of scroll.
*
* @internal
*/
const previousPosition = ref(0);
/**
* Property for getting the scroll height.
*
* @internal
*/
const scrollHeight = ref(0);
/**
* Flag to know if the property clientHeight is changing or gets the final value.
*
* @internal
*/
const isClientHeightChanging = ref(false);
/**
* Property for setting the direction of scroll.
*
* @internal
*/
const scrollDirection = ref('UP');
/**
* Restores the clientHeight flag when clientHeight property stops changing.
* Also sets a new previous position, before update the current one.
*
* @internal
*/
const restoreClientHeightFlag = () => {
isClientHeightChanging.value = false;
previousPosition.value = currentPosition.value;
};
const throtteledCall = throttle(restoreClientHeightFlag, 100);
/**
* Updates scroll related properties.
*
* @internal
*/
const storeScrollData = () => {
if (scrollEl.value) {
scrollHeight.value = scrollEl.value.scrollHeight;
clientHeight.value = scrollEl.value.clientHeight;
currentPosition.value = scrollEl.value.scrollTop;
}
};
/**
* Throttled version of the function that stores the DOM scroll related properties.
* The duration of the throttle is configured through the `throttleMs` prop passed as parameter.
*
* @returns A throttled version of the function to store the scroll data.
* @internal
*/
const throttledStoreScrollData = computed(() => throttle(storeScrollData, props.throttleMs));
/**
* Returns end position of scroll.
*
* @returns A number for knowing end position of scroll.
* @internal
*/
const scrollEndPosition = computed(() => scrollHeight.value - clientHeight.value);
/**
* Returns distance missing to endPosition position.
*
* @returns A number for knowing the distance missing to endPosition position.
* @internal
*/
const distanceToEnd = computed(() => scrollEndPosition.value - currentPosition.value);
/**
* Returns `true` when there is no more content to scroll.
*
* @returns A boolean for knowing if the user scrolls to the end.
* @internal
*/
const hasScrollReachedEnd = computed(() => currentPosition.value === scrollEndPosition.value);
/**
* Returns `true` when the scroll is at the initial position.
*
* @returns A boolean for knowing if the user scrolls to the start.
* @internal
*/
const hasScrollReachedStart = computed(() => currentPosition.value === 0);
/**
* Returns `true` when the amount of pixels scrolled is greater than
* the `distanceToBottom` prop passed as parameter.
*
* @returns A boolean for knowing if the user is about to reaching to the end.
* @internal
*/
const hasScrollAlmostReachedEnd = computed(() => !hasScrollReachedStart.value && props.distanceToBottom > distanceToEnd.value);
onMounted(() => {
// eslint-disable-next-line ts/no-floating-promises
nextTick().then(() => {
if (!scrollEl.value) {
console.warn('[useScroll]', 'Components using this composable must pass `scrollEl` as the HTML node that is scrolling.');
}
else {
storeScrollData();
}
});
});
/**
* Change the isClientHeightChanging flag when the property clientHeight is changing and
* calls throttleledCall method.
*
* @internal
*/
watch(clientHeight, () => {
isClientHeightChanging.value = true;
throtteledCall();
}, { immediate: true });
/**
* Emits the `scroll` event.
*
* @param _newScrollPosition - The new position of scroll.
* @param oldScrollPosition - The old position of scroll.
* @internal
*/
watch(currentPosition, (_newScrollPosition, oldScrollPosition) => {
emit('scroll', currentPosition.value);
previousPosition.value = oldScrollPosition;
});
/**
* Sets direction of scroll based in {@link ScrollDirection} when the current position
* has changed.
*
* @internal
*/
watch(currentPosition, () => {
if (!isClientHeightChanging.value && currentPosition.value !== previousPosition.value) {
scrollDirection.value = currentPosition.value > previousPosition.value ? 'DOWN' : 'UP';
}
});
/**
* Emits the 'scroll:almost-at-end' event when the user is about to reach to end.
*
* @param isScrollAlmostAtEnd - For knowing if the user is about to reach to end.
* @internal
*/
watch(hasScrollReachedStart, (isScrollAtStart) => {
emit('scroll:at-start', isScrollAtStart);
});
/**
* Sets direction of scroll based in {@link ScrollDirection} when the current position
* has changed.
*
* @internal
*/
watch(hasScrollAlmostReachedEnd, (isScrollAlmostAtEnd) => {
emit('scroll:almost-at-end', isScrollAlmostAtEnd);
});
/**
* Emits the 'scroll:at-end' event when the user reaches the end.
*
* @param isScrollAtEnd - For knowing if the user reaches at end.
* @internal
*/
watch(hasScrollReachedEnd, (isScrollAtEnd) => {
emit('scroll:at-end', isScrollAtEnd);
});
/**
* Emits the `scroll:direction-change` event when the scrolling direction has changed.
*
* @param direction - The new direction of scroll.
* @internal
*/
watch(scrollDirection, (direction) => {
if (!isClientHeightChanging.value) {
emit('scroll:direction-change', direction);
}
});
/**
* Resets the scroll position.
*
* @internal
*/
const $x = use$x();
const resetOnEvents = isArray(props.resetOn) ? props.resetOn : [props.resetOn];
resetOnEvents.forEach(event =>
// eslint-disable-next-line ts/no-misused-promises
$x.on(event, false).subscribe(async () => nextTick().then(() => {
if (props.resetOnChange) {
scrollEl.value?.scrollTo({ top: 0 });
}
})));
return { throttledStoreScrollData };
}
export { useScroll };
//# sourceMappingURL=use-scroll.js.map