@shopware-ag/meteor-component-library
Version:
The meteor component library is a Vue component library developed by Shopware. It is based on the [Meteor Design System](https://shopware.design/).
151 lines (125 loc) • 4.74 kB
text/typescript
import type { Ref } from "vue";
import { onUpdated, nextTick, onBeforeUnmount, onMounted, watch } from "vue";
import { throttle } from "@/utils/throttle";
/**
* This composable expect a Vue reference to an HTML element. It sets
* automatically the data attributes "data-scroll-(top|right|bottom|left)"
* when the element is scrollable into this direction.
*/
export default function useScrollPossibilitiesClasses(
refElement: Ref,
showScrollIndicators: Ref<boolean>,
) {
const setScrollPossibilitiesClasses = (elementOrEvent: Event | HTMLElement | undefined) => {
// @ts-expect-error - check if event or not
const element = (elementOrEvent?.target ? elementOrEvent.target : elementOrEvent) as
| HTMLElement
| undefined;
if (!element) return;
const hideShadows = !showScrollIndicators.value;
if (hideShadows) {
element.parentElement?.style.removeProperty("--scrollbar-width");
element.parentElement?.style.removeProperty("--scrollbar-height");
delete element.dataset.scrollTop;
delete element.dataset.scrollBottom;
delete element.dataset.scrollRight;
delete element.dataset.scrollLeft;
return;
}
const pufferPx = 5;
// check if vertical scrolling exists
if (element.scrollHeight > element.clientHeight) {
// check if scrolling downwards is possible
if (element.scrollHeight - element.clientHeight - pufferPx > element.scrollTop) {
element.dataset.scrollBottom = "";
} else {
delete element.dataset.scrollBottom;
}
// check if scrolling upwards is possible
if (element.scrollTop - pufferPx > 0) {
element.dataset.scrollTop = "";
} else {
delete element.dataset.scrollTop;
}
// save scrollbar width in parent element as css variable
element.parentElement?.style.setProperty(
"--scrollbar-width",
`${element.offsetWidth - element.clientWidth}px`,
);
} else {
// cleanup previous values
element.parentElement?.style.removeProperty("--scrollbar-width");
delete element.dataset.scrollTop;
delete element.dataset.scrollBottom;
}
// check if horizontal scrolling exists
if (element.scrollWidth > element.clientWidth) {
// check if scrolling rightwards is possible
if (element.scrollWidth - element.clientWidth - pufferPx > element.scrollLeft) {
element.dataset.scrollRight = "";
} else {
delete element.dataset.scrollRight;
}
// check if scrolling leftwards is possible
if (element.scrollLeft - pufferPx > 0) {
element.dataset.scrollLeft = "";
} else {
delete element.dataset.scrollLeft;
}
// save scrollbar height in parent element as css variable
element.parentElement?.style.setProperty(
"--scrollbar-height",
`${element.offsetHeight - element.clientHeight}px`,
);
} else {
// cleanup previous values
delete element.dataset.scrollRight;
delete element.dataset.scrollLeft;
element.parentElement?.style.removeProperty("--scrollbar-height");
}
};
const updateSetScrollPossibiltiesClasses = throttle(function () {
setScrollPossibilitiesClasses(refElement.value);
}, 15);
const refElementResizeObserver = new ResizeObserver(() => {
nextTick().then(updateSetScrollPossibiltiesClasses);
});
watch(
() => showScrollIndicators,
(shouldShowScrollIndicators) => {
if (shouldShowScrollIndicators) return;
refElement.value?.parentElement?.style.removeProperty("--scrollbar-width");
refElement.value?.parentElement?.style.removeProperty("--scrollbar-height");
delete refElement.value?.dataset.scrollTop;
delete refElement.value?.dataset.scrollBottom;
delete refElement.value?.dataset.scrollRight;
delete refElement.value?.dataset.scrollLeft;
},
{
immediate: true,
},
);
onUpdated(() => {
nextTick().then(updateSetScrollPossibiltiesClasses);
});
onMounted(() => {
window.addEventListener("resize", updateSetScrollPossibiltiesClasses);
if (!refElement.value) {
return;
}
refElement.value.addEventListener("scroll", updateSetScrollPossibiltiesClasses);
refElementResizeObserver.observe(refElement.value);
nextTick().then(() => {
updateSetScrollPossibiltiesClasses();
});
});
onBeforeUnmount(() => {
window.removeEventListener("resize", updateSetScrollPossibiltiesClasses);
if (!refElement.value) {
return;
}
refElement.value.removeEventListener("scroll", updateSetScrollPossibiltiesClasses);
refElementResizeObserver.unobserve(refElement.value);
});
return {};
}