749 lines
24 KiB
JavaScript
749 lines
24 KiB
JavaScript
|
|
import { h, ref, defineComponent, computed, onMounted, onBeforeUnmount, mergeProps, Transition, watchEffect, Fragment } from 'vue';
|
||
|
|
import { on, off } from 'evtd';
|
||
|
|
import { VResizeObserver } from 'vueuc';
|
||
|
|
import { useIsIos } from 'vooks';
|
||
|
|
import { getPreciseEventTarget } from 'seemly';
|
||
|
|
import { useConfig, useTheme, useThemeClass, useRtl } from "../../../_mixins/index.mjs";
|
||
|
|
import { useReactivated, Wrapper } from "../../../_utils/index.mjs";
|
||
|
|
import { scrollbarLight } from "../styles/index.mjs";
|
||
|
|
import style from "./styles/index.cssr.mjs";
|
||
|
|
const scrollbarProps = Object.assign(Object.assign({}, useTheme.props), {
|
||
|
|
size: {
|
||
|
|
type: Number,
|
||
|
|
default: 5
|
||
|
|
},
|
||
|
|
duration: {
|
||
|
|
type: Number,
|
||
|
|
default: 0
|
||
|
|
},
|
||
|
|
scrollable: {
|
||
|
|
type: Boolean,
|
||
|
|
default: true
|
||
|
|
},
|
||
|
|
xScrollable: Boolean,
|
||
|
|
trigger: {
|
||
|
|
type: String,
|
||
|
|
default: 'hover'
|
||
|
|
},
|
||
|
|
useUnifiedContainer: Boolean,
|
||
|
|
triggerDisplayManually: Boolean,
|
||
|
|
// If container is set, resize observer won't not attached
|
||
|
|
container: Function,
|
||
|
|
content: Function,
|
||
|
|
containerClass: String,
|
||
|
|
containerStyle: [String, Object],
|
||
|
|
contentClass: [String, Array],
|
||
|
|
contentStyle: [String, Object],
|
||
|
|
horizontalRailStyle: [String, Object],
|
||
|
|
verticalRailStyle: [String, Object],
|
||
|
|
onScroll: Function,
|
||
|
|
onWheel: Function,
|
||
|
|
onResize: Function,
|
||
|
|
internalOnUpdateScrollLeft: Function,
|
||
|
|
internalHoistYRail: Boolean
|
||
|
|
});
|
||
|
|
const Scrollbar = defineComponent({
|
||
|
|
name: 'Scrollbar',
|
||
|
|
props: scrollbarProps,
|
||
|
|
inheritAttrs: false,
|
||
|
|
setup(props) {
|
||
|
|
const {
|
||
|
|
mergedClsPrefixRef,
|
||
|
|
inlineThemeDisabled,
|
||
|
|
mergedRtlRef
|
||
|
|
} = useConfig(props);
|
||
|
|
const rtlEnabledRef = useRtl('Scrollbar', mergedRtlRef, mergedClsPrefixRef);
|
||
|
|
// dom ref
|
||
|
|
const wrapperRef = ref(null);
|
||
|
|
const containerRef = ref(null);
|
||
|
|
const contentRef = ref(null);
|
||
|
|
const yRailRef = ref(null);
|
||
|
|
const xRailRef = ref(null);
|
||
|
|
// data ref
|
||
|
|
const contentHeightRef = ref(null);
|
||
|
|
const contentWidthRef = ref(null);
|
||
|
|
const containerHeightRef = ref(null);
|
||
|
|
const containerWidthRef = ref(null);
|
||
|
|
const yRailSizeRef = ref(null);
|
||
|
|
const xRailSizeRef = ref(null);
|
||
|
|
const containerScrollTopRef = ref(0);
|
||
|
|
const containerScrollLeftRef = ref(0);
|
||
|
|
const isShowXBarRef = ref(false);
|
||
|
|
const isShowYBarRef = ref(false);
|
||
|
|
let yBarPressed = false;
|
||
|
|
let xBarPressed = false;
|
||
|
|
let xBarVanishTimerId;
|
||
|
|
let yBarVanishTimerId;
|
||
|
|
let memoYTop = 0;
|
||
|
|
let memoXLeft = 0;
|
||
|
|
let memoMouseX = 0;
|
||
|
|
let memoMouseY = 0;
|
||
|
|
const isIos = useIsIos();
|
||
|
|
const yBarSizeRef = computed(() => {
|
||
|
|
const {
|
||
|
|
value: containerHeight
|
||
|
|
} = containerHeightRef;
|
||
|
|
const {
|
||
|
|
value: contentHeight
|
||
|
|
} = contentHeightRef;
|
||
|
|
const {
|
||
|
|
value: yRailSize
|
||
|
|
} = yRailSizeRef;
|
||
|
|
if (containerHeight === null || contentHeight === null || yRailSize === null) {
|
||
|
|
return 0;
|
||
|
|
} else {
|
||
|
|
return Math.min(containerHeight, yRailSize * containerHeight / contentHeight + props.size * 1.5);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const yBarSizePxRef = computed(() => {
|
||
|
|
return `${yBarSizeRef.value}px`;
|
||
|
|
});
|
||
|
|
const xBarSizeRef = computed(() => {
|
||
|
|
const {
|
||
|
|
value: containerWidth
|
||
|
|
} = containerWidthRef;
|
||
|
|
const {
|
||
|
|
value: contentWidth
|
||
|
|
} = contentWidthRef;
|
||
|
|
const {
|
||
|
|
value: xRailSize
|
||
|
|
} = xRailSizeRef;
|
||
|
|
if (containerWidth === null || contentWidth === null || xRailSize === null) {
|
||
|
|
return 0;
|
||
|
|
} else {
|
||
|
|
return xRailSize * containerWidth / contentWidth + props.size * 1.5;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const xBarSizePxRef = computed(() => {
|
||
|
|
return `${xBarSizeRef.value}px`;
|
||
|
|
});
|
||
|
|
const yBarTopRef = computed(() => {
|
||
|
|
const {
|
||
|
|
value: containerHeight
|
||
|
|
} = containerHeightRef;
|
||
|
|
const {
|
||
|
|
value: containerScrollTop
|
||
|
|
} = containerScrollTopRef;
|
||
|
|
const {
|
||
|
|
value: contentHeight
|
||
|
|
} = contentHeightRef;
|
||
|
|
const {
|
||
|
|
value: yRailSize
|
||
|
|
} = yRailSizeRef;
|
||
|
|
if (containerHeight === null || contentHeight === null || yRailSize === null) {
|
||
|
|
return 0;
|
||
|
|
} else {
|
||
|
|
const heightDiff = contentHeight - containerHeight;
|
||
|
|
if (!heightDiff) return 0;
|
||
|
|
return containerScrollTop / heightDiff * (yRailSize - yBarSizeRef.value);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const yBarTopPxRef = computed(() => {
|
||
|
|
return `${yBarTopRef.value}px`;
|
||
|
|
});
|
||
|
|
const xBarLeftRef = computed(() => {
|
||
|
|
const {
|
||
|
|
value: containerWidth
|
||
|
|
} = containerWidthRef;
|
||
|
|
const {
|
||
|
|
value: containerScrollLeft
|
||
|
|
} = containerScrollLeftRef;
|
||
|
|
const {
|
||
|
|
value: contentWidth
|
||
|
|
} = contentWidthRef;
|
||
|
|
const {
|
||
|
|
value: xRailSize
|
||
|
|
} = xRailSizeRef;
|
||
|
|
if (containerWidth === null || contentWidth === null || xRailSize === null) {
|
||
|
|
return 0;
|
||
|
|
} else {
|
||
|
|
const widthDiff = contentWidth - containerWidth;
|
||
|
|
if (!widthDiff) return 0;
|
||
|
|
return containerScrollLeft / widthDiff * (xRailSize - xBarSizeRef.value);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const xBarLeftPxRef = computed(() => {
|
||
|
|
return `${xBarLeftRef.value}px`;
|
||
|
|
});
|
||
|
|
const needYBarRef = computed(() => {
|
||
|
|
const {
|
||
|
|
value: containerHeight
|
||
|
|
} = containerHeightRef;
|
||
|
|
const {
|
||
|
|
value: contentHeight
|
||
|
|
} = contentHeightRef;
|
||
|
|
return containerHeight !== null && contentHeight !== null && contentHeight > containerHeight;
|
||
|
|
});
|
||
|
|
const needXBarRef = computed(() => {
|
||
|
|
const {
|
||
|
|
value: containerWidth
|
||
|
|
} = containerWidthRef;
|
||
|
|
const {
|
||
|
|
value: contentWidth
|
||
|
|
} = contentWidthRef;
|
||
|
|
return containerWidth !== null && contentWidth !== null && contentWidth > containerWidth;
|
||
|
|
});
|
||
|
|
const mergedShowXBarRef = computed(() => {
|
||
|
|
const {
|
||
|
|
trigger
|
||
|
|
} = props;
|
||
|
|
return trigger === 'none' || isShowXBarRef.value;
|
||
|
|
});
|
||
|
|
const mergedShowYBarRef = computed(() => {
|
||
|
|
const {
|
||
|
|
trigger
|
||
|
|
} = props;
|
||
|
|
return trigger === 'none' || isShowYBarRef.value;
|
||
|
|
});
|
||
|
|
const mergedContainerRef = computed(() => {
|
||
|
|
const {
|
||
|
|
container
|
||
|
|
} = props;
|
||
|
|
if (container) return container();
|
||
|
|
return containerRef.value;
|
||
|
|
});
|
||
|
|
const mergedContentRef = computed(() => {
|
||
|
|
const {
|
||
|
|
content
|
||
|
|
} = props;
|
||
|
|
if (content) return content();
|
||
|
|
return contentRef.value;
|
||
|
|
});
|
||
|
|
const activateState = useReactivated(() => {
|
||
|
|
// Only restore for builtin container & content
|
||
|
|
if (!props.container) {
|
||
|
|
// remount
|
||
|
|
scrollTo({
|
||
|
|
top: containerScrollTopRef.value,
|
||
|
|
left: containerScrollLeftRef.value
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
// methods
|
||
|
|
const handleContentResize = () => {
|
||
|
|
if (activateState.isDeactivated) return;
|
||
|
|
sync();
|
||
|
|
};
|
||
|
|
const handleContainerResize = e => {
|
||
|
|
if (activateState.isDeactivated) return;
|
||
|
|
const {
|
||
|
|
onResize
|
||
|
|
} = props;
|
||
|
|
if (onResize) onResize(e);
|
||
|
|
sync();
|
||
|
|
};
|
||
|
|
const scrollTo = (options, y) => {
|
||
|
|
if (!props.scrollable) return;
|
||
|
|
if (typeof options === 'number') {
|
||
|
|
scrollToPosition(y !== null && y !== void 0 ? y : 0, options, 0, false, 'auto');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
left,
|
||
|
|
top,
|
||
|
|
index,
|
||
|
|
elSize,
|
||
|
|
position,
|
||
|
|
behavior,
|
||
|
|
el,
|
||
|
|
debounce = true
|
||
|
|
} = options;
|
||
|
|
if (left !== undefined || top !== undefined) {
|
||
|
|
scrollToPosition(left !== null && left !== void 0 ? left : 0, top !== null && top !== void 0 ? top : 0, 0, false, behavior);
|
||
|
|
}
|
||
|
|
if (el !== undefined) {
|
||
|
|
scrollToPosition(0, el.offsetTop, el.offsetHeight, debounce, behavior);
|
||
|
|
} else if (index !== undefined && elSize !== undefined) {
|
||
|
|
scrollToPosition(0, index * elSize, elSize, debounce, behavior);
|
||
|
|
} else if (position === 'bottom') {
|
||
|
|
scrollToPosition(0, Number.MAX_SAFE_INTEGER, 0, false, behavior);
|
||
|
|
} else if (position === 'top') {
|
||
|
|
scrollToPosition(0, 0, 0, false, behavior);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
const scrollBy = (options, y) => {
|
||
|
|
if (!props.scrollable) return;
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (!container) return;
|
||
|
|
if (typeof options === 'object') {
|
||
|
|
container.scrollBy(options);
|
||
|
|
} else {
|
||
|
|
container.scrollBy(options, y || 0);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
function scrollToPosition(left, top, elSize, debounce, behavior) {
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (!container) return;
|
||
|
|
if (debounce) {
|
||
|
|
const {
|
||
|
|
scrollTop,
|
||
|
|
offsetHeight
|
||
|
|
} = container;
|
||
|
|
if (top > scrollTop) {
|
||
|
|
if (top + elSize <= scrollTop + offsetHeight) {
|
||
|
|
// do nothing
|
||
|
|
} else {
|
||
|
|
container.scrollTo({
|
||
|
|
left,
|
||
|
|
top: top + elSize - offsetHeight,
|
||
|
|
behavior
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
container.scrollTo({
|
||
|
|
left,
|
||
|
|
top,
|
||
|
|
behavior
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function handleMouseEnterWrapper() {
|
||
|
|
showXBar();
|
||
|
|
showYBar();
|
||
|
|
sync();
|
||
|
|
}
|
||
|
|
function handleMouseLeaveWrapper() {
|
||
|
|
hideBar();
|
||
|
|
}
|
||
|
|
function hideBar() {
|
||
|
|
hideYBar();
|
||
|
|
hideXBar();
|
||
|
|
}
|
||
|
|
function hideYBar() {
|
||
|
|
if (yBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(yBarVanishTimerId);
|
||
|
|
}
|
||
|
|
yBarVanishTimerId = window.setTimeout(() => {
|
||
|
|
isShowYBarRef.value = false;
|
||
|
|
}, props.duration);
|
||
|
|
}
|
||
|
|
function hideXBar() {
|
||
|
|
if (xBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(xBarVanishTimerId);
|
||
|
|
}
|
||
|
|
xBarVanishTimerId = window.setTimeout(() => {
|
||
|
|
isShowXBarRef.value = false;
|
||
|
|
}, props.duration);
|
||
|
|
}
|
||
|
|
function showXBar() {
|
||
|
|
if (xBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(xBarVanishTimerId);
|
||
|
|
}
|
||
|
|
isShowXBarRef.value = true;
|
||
|
|
}
|
||
|
|
function showYBar() {
|
||
|
|
if (yBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(yBarVanishTimerId);
|
||
|
|
}
|
||
|
|
isShowYBarRef.value = true;
|
||
|
|
}
|
||
|
|
function handleScroll(e) {
|
||
|
|
const {
|
||
|
|
onScroll
|
||
|
|
} = props;
|
||
|
|
if (onScroll) onScroll(e);
|
||
|
|
syncScrollState();
|
||
|
|
}
|
||
|
|
function syncScrollState() {
|
||
|
|
// only collect scroll state, do not trigger any dom event
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (container) {
|
||
|
|
containerScrollTopRef.value = container.scrollTop;
|
||
|
|
containerScrollLeftRef.value = container.scrollLeft * ((rtlEnabledRef === null || rtlEnabledRef === void 0 ? void 0 : rtlEnabledRef.value) ? -1 : 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function syncPositionState() {
|
||
|
|
// only collect position state, do not trigger any dom event
|
||
|
|
// Don't use getClientBoundingRect because element may be scale transformed
|
||
|
|
const {
|
||
|
|
value: content
|
||
|
|
} = mergedContentRef;
|
||
|
|
if (content) {
|
||
|
|
contentHeightRef.value = content.offsetHeight;
|
||
|
|
contentWidthRef.value = content.offsetWidth;
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (container) {
|
||
|
|
containerHeightRef.value = container.offsetHeight;
|
||
|
|
containerWidthRef.value = container.offsetWidth;
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
value: xRailEl
|
||
|
|
} = xRailRef;
|
||
|
|
const {
|
||
|
|
value: yRailEl
|
||
|
|
} = yRailRef;
|
||
|
|
if (xRailEl) {
|
||
|
|
xRailSizeRef.value = xRailEl.offsetWidth;
|
||
|
|
}
|
||
|
|
if (yRailEl) {
|
||
|
|
yRailSizeRef.value = yRailEl.offsetHeight;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Sometimes there's only one element that we can scroll,
|
||
|
|
* For example for textarea, there won't be a content element.
|
||
|
|
*/
|
||
|
|
function syncUnifiedContainer() {
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (container) {
|
||
|
|
containerScrollTopRef.value = container.scrollTop;
|
||
|
|
containerScrollLeftRef.value = container.scrollLeft * ((rtlEnabledRef === null || rtlEnabledRef === void 0 ? void 0 : rtlEnabledRef.value) ? -1 : 1);
|
||
|
|
containerHeightRef.value = container.offsetHeight;
|
||
|
|
containerWidthRef.value = container.offsetWidth;
|
||
|
|
contentHeightRef.value = container.scrollHeight;
|
||
|
|
contentWidthRef.value = container.scrollWidth;
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
value: xRailEl
|
||
|
|
} = xRailRef;
|
||
|
|
const {
|
||
|
|
value: yRailEl
|
||
|
|
} = yRailRef;
|
||
|
|
if (xRailEl) {
|
||
|
|
xRailSizeRef.value = xRailEl.offsetWidth;
|
||
|
|
}
|
||
|
|
if (yRailEl) {
|
||
|
|
yRailSizeRef.value = yRailEl.offsetHeight;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function sync() {
|
||
|
|
if (!props.scrollable) return;
|
||
|
|
if (props.useUnifiedContainer) {
|
||
|
|
syncUnifiedContainer();
|
||
|
|
} else {
|
||
|
|
syncPositionState();
|
||
|
|
syncScrollState();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function isMouseUpAway(e) {
|
||
|
|
var _a;
|
||
|
|
return !((_a = wrapperRef.value) === null || _a === void 0 ? void 0 : _a.contains(getPreciseEventTarget(e)));
|
||
|
|
}
|
||
|
|
function handleXScrollMouseDown(e) {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
xBarPressed = true;
|
||
|
|
on('mousemove', window, handleXScrollMouseMove, true);
|
||
|
|
on('mouseup', window, handleXScrollMouseUp, true);
|
||
|
|
memoXLeft = containerScrollLeftRef.value;
|
||
|
|
memoMouseX = (rtlEnabledRef === null || rtlEnabledRef === void 0 ? void 0 : rtlEnabledRef.value) ? window.innerWidth - e.clientX : e.clientX;
|
||
|
|
}
|
||
|
|
function handleXScrollMouseMove(e) {
|
||
|
|
if (!xBarPressed) return;
|
||
|
|
if (xBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(xBarVanishTimerId);
|
||
|
|
}
|
||
|
|
if (yBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(yBarVanishTimerId);
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
value: containerWidth
|
||
|
|
} = containerWidthRef;
|
||
|
|
const {
|
||
|
|
value: contentWidth
|
||
|
|
} = contentWidthRef;
|
||
|
|
const {
|
||
|
|
value: xBarSize
|
||
|
|
} = xBarSizeRef;
|
||
|
|
if (containerWidth === null || contentWidth === null) return;
|
||
|
|
const dX = (rtlEnabledRef === null || rtlEnabledRef === void 0 ? void 0 : rtlEnabledRef.value) ? window.innerWidth - e.clientX - memoMouseX : e.clientX - memoMouseX;
|
||
|
|
const dScrollLeft = dX * (contentWidth - containerWidth) / (containerWidth - xBarSize);
|
||
|
|
const toScrollLeftUpperBound = contentWidth - containerWidth;
|
||
|
|
let toScrollLeft = memoXLeft + dScrollLeft;
|
||
|
|
toScrollLeft = Math.min(toScrollLeftUpperBound, toScrollLeft);
|
||
|
|
toScrollLeft = Math.max(toScrollLeft, 0);
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (container) {
|
||
|
|
container.scrollLeft = toScrollLeft * ((rtlEnabledRef === null || rtlEnabledRef === void 0 ? void 0 : rtlEnabledRef.value) ? -1 : 1);
|
||
|
|
const {
|
||
|
|
internalOnUpdateScrollLeft
|
||
|
|
} = props;
|
||
|
|
if (internalOnUpdateScrollLeft) internalOnUpdateScrollLeft(toScrollLeft);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function handleXScrollMouseUp(e) {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
off('mousemove', window, handleXScrollMouseMove, true);
|
||
|
|
off('mouseup', window, handleXScrollMouseUp, true);
|
||
|
|
xBarPressed = false;
|
||
|
|
sync();
|
||
|
|
if (isMouseUpAway(e)) {
|
||
|
|
hideBar();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function handleYScrollMouseDown(e) {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
yBarPressed = true;
|
||
|
|
on('mousemove', window, handleYScrollMouseMove, true);
|
||
|
|
on('mouseup', window, handleYScrollMouseUp, true);
|
||
|
|
memoYTop = containerScrollTopRef.value;
|
||
|
|
memoMouseY = e.clientY;
|
||
|
|
}
|
||
|
|
function handleYScrollMouseMove(e) {
|
||
|
|
if (!yBarPressed) return;
|
||
|
|
if (xBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(xBarVanishTimerId);
|
||
|
|
}
|
||
|
|
if (yBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(yBarVanishTimerId);
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
value: containerHeight
|
||
|
|
} = containerHeightRef;
|
||
|
|
const {
|
||
|
|
value: contentHeight
|
||
|
|
} = contentHeightRef;
|
||
|
|
const {
|
||
|
|
value: yBarSize
|
||
|
|
} = yBarSizeRef;
|
||
|
|
if (containerHeight === null || contentHeight === null) return;
|
||
|
|
const dY = e.clientY - memoMouseY;
|
||
|
|
const dScrollTop = dY * (contentHeight - containerHeight) / (containerHeight - yBarSize);
|
||
|
|
const toScrollTopUpperBound = contentHeight - containerHeight;
|
||
|
|
let toScrollTop = memoYTop + dScrollTop;
|
||
|
|
toScrollTop = Math.min(toScrollTopUpperBound, toScrollTop);
|
||
|
|
toScrollTop = Math.max(toScrollTop, 0);
|
||
|
|
const {
|
||
|
|
value: container
|
||
|
|
} = mergedContainerRef;
|
||
|
|
if (container) {
|
||
|
|
container.scrollTop = toScrollTop;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function handleYScrollMouseUp(e) {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
off('mousemove', window, handleYScrollMouseMove, true);
|
||
|
|
off('mouseup', window, handleYScrollMouseUp, true);
|
||
|
|
yBarPressed = false;
|
||
|
|
sync();
|
||
|
|
if (isMouseUpAway(e)) {
|
||
|
|
hideBar();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
watchEffect(() => {
|
||
|
|
const {
|
||
|
|
value: needXBar
|
||
|
|
} = needXBarRef;
|
||
|
|
const {
|
||
|
|
value: needYBar
|
||
|
|
} = needYBarRef;
|
||
|
|
const {
|
||
|
|
value: mergedClsPrefix
|
||
|
|
} = mergedClsPrefixRef;
|
||
|
|
const {
|
||
|
|
value: xRailEl
|
||
|
|
} = xRailRef;
|
||
|
|
const {
|
||
|
|
value: yRailEl
|
||
|
|
} = yRailRef;
|
||
|
|
if (xRailEl) {
|
||
|
|
if (!needXBar) {
|
||
|
|
xRailEl.classList.add(`${mergedClsPrefix}-scrollbar-rail--disabled`);
|
||
|
|
} else {
|
||
|
|
xRailEl.classList.remove(`${mergedClsPrefix}-scrollbar-rail--disabled`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (yRailEl) {
|
||
|
|
if (!needYBar) {
|
||
|
|
yRailEl.classList.add(`${mergedClsPrefix}-scrollbar-rail--disabled`);
|
||
|
|
} else {
|
||
|
|
yRailEl.classList.remove(`${mergedClsPrefix}-scrollbar-rail--disabled`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
onMounted(() => {
|
||
|
|
// if container exist, it always can't be resolved when scrollbar is mounted
|
||
|
|
// for example:
|
||
|
|
// - component
|
||
|
|
// - scrollbar
|
||
|
|
// - inner
|
||
|
|
// if you pass inner to scrollbar, you may use a ref inside component
|
||
|
|
// however, when scrollbar is mounted, ref is not ready at component
|
||
|
|
// you need to init by yourself
|
||
|
|
if (props.container) return;
|
||
|
|
sync();
|
||
|
|
});
|
||
|
|
onBeforeUnmount(() => {
|
||
|
|
if (xBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(xBarVanishTimerId);
|
||
|
|
}
|
||
|
|
if (yBarVanishTimerId !== undefined) {
|
||
|
|
window.clearTimeout(yBarVanishTimerId);
|
||
|
|
}
|
||
|
|
off('mousemove', window, handleYScrollMouseMove, true);
|
||
|
|
off('mouseup', window, handleYScrollMouseUp, true);
|
||
|
|
});
|
||
|
|
const themeRef = useTheme('Scrollbar', '-scrollbar', style, scrollbarLight, props, mergedClsPrefixRef);
|
||
|
|
const cssVarsRef = computed(() => {
|
||
|
|
const {
|
||
|
|
common: {
|
||
|
|
cubicBezierEaseInOut,
|
||
|
|
scrollbarBorderRadius,
|
||
|
|
scrollbarHeight,
|
||
|
|
scrollbarWidth
|
||
|
|
},
|
||
|
|
self: {
|
||
|
|
color,
|
||
|
|
colorHover
|
||
|
|
}
|
||
|
|
} = themeRef.value;
|
||
|
|
return {
|
||
|
|
'--n-scrollbar-bezier': cubicBezierEaseInOut,
|
||
|
|
'--n-scrollbar-color': color,
|
||
|
|
'--n-scrollbar-color-hover': colorHover,
|
||
|
|
'--n-scrollbar-border-radius': scrollbarBorderRadius,
|
||
|
|
'--n-scrollbar-width': scrollbarWidth,
|
||
|
|
'--n-scrollbar-height': scrollbarHeight
|
||
|
|
};
|
||
|
|
});
|
||
|
|
const themeClassHandle = inlineThemeDisabled ? useThemeClass('scrollbar', undefined, cssVarsRef, props) : undefined;
|
||
|
|
const exposedMethods = {
|
||
|
|
scrollTo,
|
||
|
|
scrollBy,
|
||
|
|
sync,
|
||
|
|
syncUnifiedContainer,
|
||
|
|
handleMouseEnterWrapper,
|
||
|
|
handleMouseLeaveWrapper
|
||
|
|
};
|
||
|
|
return Object.assign(Object.assign({}, exposedMethods), {
|
||
|
|
mergedClsPrefix: mergedClsPrefixRef,
|
||
|
|
rtlEnabled: rtlEnabledRef,
|
||
|
|
containerScrollTop: containerScrollTopRef,
|
||
|
|
wrapperRef,
|
||
|
|
containerRef,
|
||
|
|
contentRef,
|
||
|
|
yRailRef,
|
||
|
|
xRailRef,
|
||
|
|
needYBar: needYBarRef,
|
||
|
|
needXBar: needXBarRef,
|
||
|
|
yBarSizePx: yBarSizePxRef,
|
||
|
|
xBarSizePx: xBarSizePxRef,
|
||
|
|
yBarTopPx: yBarTopPxRef,
|
||
|
|
xBarLeftPx: xBarLeftPxRef,
|
||
|
|
isShowXBar: mergedShowXBarRef,
|
||
|
|
isShowYBar: mergedShowYBarRef,
|
||
|
|
isIos,
|
||
|
|
handleScroll,
|
||
|
|
handleContentResize,
|
||
|
|
handleContainerResize,
|
||
|
|
handleYScrollMouseDown,
|
||
|
|
handleXScrollMouseDown,
|
||
|
|
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
|
||
|
|
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
|
||
|
|
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender
|
||
|
|
});
|
||
|
|
},
|
||
|
|
render() {
|
||
|
|
var _a;
|
||
|
|
const {
|
||
|
|
$slots,
|
||
|
|
mergedClsPrefix,
|
||
|
|
triggerDisplayManually,
|
||
|
|
rtlEnabled,
|
||
|
|
internalHoistYRail
|
||
|
|
} = this;
|
||
|
|
if (!this.scrollable) return (_a = $slots.default) === null || _a === void 0 ? void 0 : _a.call($slots);
|
||
|
|
const triggerIsNone = this.trigger === 'none';
|
||
|
|
const createYRail = (className, style) => {
|
||
|
|
return h("div", {
|
||
|
|
ref: "yRailRef",
|
||
|
|
class: [`${mergedClsPrefix}-scrollbar-rail`, `${mergedClsPrefix}-scrollbar-rail--vertical`, className],
|
||
|
|
"data-scrollbar-rail": true,
|
||
|
|
style: [style || '', this.verticalRailStyle],
|
||
|
|
"aria-hiddens": true
|
||
|
|
}, h(
|
||
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||
|
|
triggerIsNone ? Wrapper : Transition, triggerIsNone ? null : {
|
||
|
|
name: 'fade-in-transition'
|
||
|
|
}, {
|
||
|
|
default: () => this.needYBar && this.isShowYBar && !this.isIos ? h("div", {
|
||
|
|
class: `${mergedClsPrefix}-scrollbar-rail__scrollbar`,
|
||
|
|
style: {
|
||
|
|
height: this.yBarSizePx,
|
||
|
|
top: this.yBarTopPx
|
||
|
|
},
|
||
|
|
onMousedown: this.handleYScrollMouseDown
|
||
|
|
}) : null
|
||
|
|
}));
|
||
|
|
};
|
||
|
|
const createChildren = () => {
|
||
|
|
var _a, _b;
|
||
|
|
(_a = this.onRender) === null || _a === void 0 ? void 0 : _a.call(this);
|
||
|
|
return h('div', mergeProps(this.$attrs, {
|
||
|
|
role: 'none',
|
||
|
|
ref: 'wrapperRef',
|
||
|
|
class: [`${mergedClsPrefix}-scrollbar`, this.themeClass, rtlEnabled && `${mergedClsPrefix}-scrollbar--rtl`],
|
||
|
|
style: this.cssVars,
|
||
|
|
onMouseenter: triggerDisplayManually ? undefined : this.handleMouseEnterWrapper,
|
||
|
|
onMouseleave: triggerDisplayManually ? undefined : this.handleMouseLeaveWrapper
|
||
|
|
}), [this.container ? (_b = $slots.default) === null || _b === void 0 ? void 0 : _b.call($slots) : h("div", {
|
||
|
|
role: "none",
|
||
|
|
ref: "containerRef",
|
||
|
|
class: [`${mergedClsPrefix}-scrollbar-container`, this.containerClass],
|
||
|
|
style: this.containerStyle,
|
||
|
|
onScroll: this.handleScroll,
|
||
|
|
onWheel: this.onWheel
|
||
|
|
}, h(VResizeObserver, {
|
||
|
|
onResize: this.handleContentResize
|
||
|
|
}, {
|
||
|
|
default: () => h("div", {
|
||
|
|
ref: "contentRef",
|
||
|
|
role: "none",
|
||
|
|
style: [{
|
||
|
|
width: this.xScrollable ? 'fit-content' : null
|
||
|
|
}, this.contentStyle],
|
||
|
|
class: [`${mergedClsPrefix}-scrollbar-content`, this.contentClass]
|
||
|
|
}, $slots)
|
||
|
|
})), internalHoistYRail ? null : createYRail(undefined, undefined), this.xScrollable && h("div", {
|
||
|
|
ref: "xRailRef",
|
||
|
|
class: [`${mergedClsPrefix}-scrollbar-rail`, `${mergedClsPrefix}-scrollbar-rail--horizontal`],
|
||
|
|
style: this.horizontalRailStyle,
|
||
|
|
"data-scrollbar-rail": true,
|
||
|
|
"aria-hidden": true
|
||
|
|
}, h(
|
||
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||
|
|
triggerIsNone ? Wrapper : Transition, triggerIsNone ? null : {
|
||
|
|
name: 'fade-in-transition'
|
||
|
|
}, {
|
||
|
|
default: () => this.needXBar && this.isShowXBar && !this.isIos ? h("div", {
|
||
|
|
class: `${mergedClsPrefix}-scrollbar-rail__scrollbar`,
|
||
|
|
style: {
|
||
|
|
width: this.xBarSizePx,
|
||
|
|
right: rtlEnabled ? this.xBarLeftPx : undefined,
|
||
|
|
left: rtlEnabled ? undefined : this.xBarLeftPx
|
||
|
|
},
|
||
|
|
onMousedown: this.handleXScrollMouseDown
|
||
|
|
}) : null
|
||
|
|
}))]);
|
||
|
|
};
|
||
|
|
const scrollbarNode = this.container ? createChildren() : h(VResizeObserver, {
|
||
|
|
onResize: this.handleContainerResize
|
||
|
|
}, {
|
||
|
|
default: createChildren
|
||
|
|
});
|
||
|
|
if (internalHoistYRail) {
|
||
|
|
return h(Fragment, null, scrollbarNode, createYRail(this.themeClass, this.cssVars));
|
||
|
|
} else {
|
||
|
|
return scrollbarNode;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
export default Scrollbar;
|
||
|
|
export const XScrollbar = Scrollbar;
|