import type { Ref } from 'vue'; import { getCurrentInstance, onBeforeUpdate, onBeforeMount, defineComponent, onBeforeUnmount, onMounted, ref, watch, } from 'vue'; import addEventListenerWrap from '../vc-util/Dom/addEventListener'; import { getOffset } from '../vc-util/Dom/css'; import classNames from '../_util/classNames'; import type { MouseEventHandler } from '../_util/EventInterface'; import getScrollBarSize from '../_util/getScrollBarSize'; import { useInjectTable } from './context/TableContext'; import { useLayoutState } from './hooks/useFrame'; interface StickyScrollBarProps { scrollBodyRef: Ref; onScroll: (params: { scrollLeft?: number }) => void; offsetScroll: number; container: HTMLElement | Window; } export default defineComponent({ name: 'StickyScrollBar', inheritAttrs: false, props: ['offsetScroll', 'container', 'scrollBodyRef'] as any, emits: ['scroll'], setup(props, { emit, expose }) { const tableContext = useInjectTable(); const bodyScrollWidth = ref(0); const bodyWidth = ref(0); const scrollBarWidth = ref(0); const instance = getCurrentInstance(); const updateSomeValue = () => { bodyScrollWidth.value = props.scrollBodyRef.value.scrollWidth || 0; bodyWidth.value = props.scrollBodyRef.value.clientWidth || 0; scrollBarWidth.value = bodyScrollWidth.value && bodyWidth.value * (bodyWidth.value / bodyScrollWidth.value); }; onBeforeMount(() => { updateSomeValue(); }); onBeforeUpdate(() => { updateSomeValue(); }); const scrollBarRef = ref(); const [scrollState, setScrollState] = useLayoutState({ scrollLeft: 0, isHiddenScrollBar: false, }); const refState = ref({ delta: 0, x: 0, }); const isActive = ref(false); const onMouseUp: MouseEventHandler = () => { isActive.value = false; }; const onMouseDown: MouseEventHandler = event => { refState.value = { delta: event.pageX - scrollState.value.scrollLeft, x: 0 }; isActive.value = true; event.preventDefault(); }; const onMouseMove: MouseEventHandler = event => { // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons const { buttons } = event || (window?.event as any); if (!isActive.value || buttons === 0) { // If out body mouse up, we can set isActive false when mouse move if (isActive.value) { isActive.value = false; } return; } let left: number = refState.value.x + event.pageX - refState.value.x - refState.value.delta; if (left <= 0) { left = 0; } if (left + scrollBarWidth.value >= bodyWidth.value) { left = bodyWidth.value - scrollBarWidth.value; } emit('scroll', { scrollLeft: (left / bodyWidth.value) * (bodyScrollWidth.value + 2), }); refState.value.x = event.pageX; }; const onContainerScroll = () => { if (!props.scrollBodyRef.value) { return; } const tableOffsetTop = getOffset(props.scrollBodyRef.value).top; const tableBottomOffset = tableOffsetTop + props.scrollBodyRef.value.offsetHeight; const currentClientOffset = props.container === window ? document.documentElement.scrollTop + window.innerHeight : getOffset(props.container).top + (props.container as HTMLElement).clientHeight; if ( tableBottomOffset - getScrollBarSize() <= currentClientOffset || tableOffsetTop >= currentClientOffset - props.offsetScroll ) { setScrollState(state => ({ ...state, isHiddenScrollBar: true, })); } else { setScrollState(state => ({ ...state, isHiddenScrollBar: false, })); } instance.update?.(); }; const setScrollLeft = (left: number) => { setScrollState(state => { return { ...state, scrollLeft: (left / bodyScrollWidth.value) * bodyWidth.value || 0, }; }); }; expose({ setScrollLeft, }); let onMouseUpListener = null; let onMouseMoveListener = null; let onResizeListener = null; let onScrollListener = null; onMounted(() => { onMouseUpListener = addEventListenerWrap(document.body, 'mouseup', onMouseUp, false); onMouseMoveListener = addEventListenerWrap(document.body, 'mousemove', onMouseMove, false); onResizeListener = addEventListenerWrap(window, 'resize', onContainerScroll, false); }); watch( [scrollBarWidth, isActive], () => { onContainerScroll(); }, { immediate: true }, ); watch( () => props.container, () => { onScrollListener?.remove(); onScrollListener = addEventListenerWrap( props.container, 'scroll', onContainerScroll, false, ); }, { immediate: true, flush: 'post' }, ); onBeforeUnmount(() => { onMouseUpListener?.remove(); onMouseMoveListener?.remove(); onScrollListener?.remove(); onResizeListener?.remove(); }); watch( () => ({ ...scrollState.value }), (newState, preState) => { if ( newState.isHiddenScrollBar !== preState?.isHiddenScrollBar && !newState.isHiddenScrollBar ) { setScrollState(state => { const bodyNode = props.scrollBodyRef.value; if (!bodyNode) { return state; } return { ...state, scrollLeft: (bodyNode.scrollLeft / bodyNode.scrollWidth) * bodyNode.clientWidth, }; }); } }, { immediate: true }, ); const scrollbarSize = getScrollBarSize(); return () => { if ( bodyScrollWidth.value <= bodyWidth.value || !scrollBarWidth.value || scrollState.value.isHiddenScrollBar ) { return null; } const { prefixCls } = tableContext; return (
); }; }, });