@empathyco/x-components
Version:
Empathy X Components
137 lines (134 loc) • 5.34 kB
JavaScript
import { defineComponent, ref, computed, onMounted, watch, onBeforeUnmount } from 'vue';
import { useDebounce } from '../composables/use-debounce.js';
/**
* This component allows for any other component or element inside it to be horizontally
* navigable. It also implements customizable buttons as well as other minor customizations to its
* general behavior.
*
* Additionally, this component exposes the following props to modify the classes of the
* elements: `buttonClass`.
*
* @public
*/
var _sfc_main = defineComponent({
name: 'SlidingPanel',
props: {
/**
* Scroll factor that will dictate how much the scroll moves when pressing a navigation button.
*/
scrollFactor: {
type: Number,
default: 0.7,
},
/** Would make the navigation buttons visible when they're needed or always hide them. */
showButtons: {
type: Boolean,
default: true,
},
/**
* When true, whenever the DOM content in the sliding panel slot changes, it will reset
* the scroll position to 0.
*/
resetOnContentChange: {
type: Boolean,
default: true,
},
buttonClass: { type: [String, Object, Array] },
scrollContainerClass: { type: [String, Object, Array] },
},
setup(props, { slots }) {
/** Indicates if the scroll is at the start of the sliding panel. */
const isScrollAtStart = ref(true);
/** Indicates if the scroll is at the end of the sliding panel. */
const isScrollAtEnd = ref(true);
const scrollContainerRef = ref();
/**
* Updates the values of the scroll positions to show or hide the buttons depending on it.
*
* @remarks The 2px extra is to fix some cases in some resolutions where the scroll + client
* size is less than the scroll width even when the scroll is at the end.
*/
function updateScrollPosition() {
if (scrollContainerRef.value) {
const { scrollLeft, clientWidth, scrollWidth } = scrollContainerRef.value;
isScrollAtStart.value = !scrollLeft;
isScrollAtEnd.value = scrollLeft + clientWidth + 2 >= scrollWidth;
}
}
/**
* Debounced version of the {@link SlidingPanel.updateScrollPosition} method.
*/
const debouncedUpdateScroll = useDebounce(updateScrollPosition, 50, { leading: true });
/**
* Resets the scroll and updates the values of the scroll for the buttons to react.
*/
const debouncedRestoreAndUpdateScroll = useDebounce(() => {
scrollContainerRef.value.scroll({ left: 0, behavior: 'smooth' });
updateScrollPosition();
}, 50, { leading: true });
/**
* Scrolls the wrapper element towards the provided scroll value.
*
* @param scrollValue - The value the scroll will go towards.
*/
function scrollTo(scrollValue) {
scrollContainerRef.value.scrollBy({
left: scrollValue * props.scrollFactor,
behavior: 'smooth',
});
}
/** Scrolls the wrapper element to the left. */
function scrollLeft() {
scrollTo(-scrollContainerRef.value.clientWidth);
}
/** Scrolls the wrapper element to the right. */
function scrollRight() {
scrollTo(scrollContainerRef.value.clientWidth);
}
/** CSS classes to apply based on the scroll position. */
const cssClasses = computed(() => ({
'x-sliding-panel-at-start': isScrollAtStart.value,
'x-sliding-panel-at-end': isScrollAtEnd.value,
}));
let resizeObserver;
let contentChangedObserver;
/**
* Initialises browser platform code:
* - Creates a mutation observer to detect content changes and reset scroll position.
* - Stores initial size and scroll position values.
*/
onMounted(() => {
resizeObserver = new ResizeObserver(debouncedUpdateScroll);
resizeObserver.observe(scrollContainerRef.value);
contentChangedObserver = new MutationObserver(debouncedRestoreAndUpdateScroll);
watch(() => props.resetOnContentChange, shouldReset => {
if (shouldReset) {
contentChangedObserver.observe(scrollContainerRef.value, {
subtree: true,
childList: true,
attributes: false,
characterData: false,
});
}
else {
contentChangedObserver.disconnect();
}
}, { immediate: true });
updateScrollPosition();
});
onBeforeUnmount(() => {
contentChangedObserver.disconnect();
resizeObserver.disconnect();
});
return {
cssClasses,
debouncedUpdateScroll,
scrollContainerRef,
scrollLeft,
scrollRight,
slots,
};
},
});
export { _sfc_main as default };
//# sourceMappingURL=sliding-panel.vue2.js.map