@opensig/opendesign
Version:
338 lines (337 loc) • 11.3 kB
JavaScript
import { defineComponent, ref, toRefs, watchEffect, onUnmounted, createElementBlock, openBlock, normalizeClass, unref, createCommentVNode, Fragment, createBlock, withCtx, renderSlot } from "vue";
import _sfc_main$1 from "./ScrollbarRail.vue.mjs";
import { scrollbarProps } from "./types.mjs";
import { resolveHtmlElement, mergeClass } from "../_utils/vue-utils.mjs";
import { useResizeObserver } from "../hooks/use-resize-observer.mjs";
import "../_utils/is.mjs";
import "../hooks/use-theme.mjs";
import { useScreen } from "../hooks/use-screen.mjs";
import "@vueuse/core";
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "OScrollbar",
props: scrollbarProps,
setup(__props, { expose: __expose }) {
const ScrollbarClass = {
container: "o-scrollbar-container"
};
const props = __props;
const { isPhonePad } = useScreen();
let scrollTargetEl = null;
let scrollListenEl = null;
const rootRef = ref(null);
const hasY = ref(false);
const hasX = ref(false);
const hThumbRate = ref(0);
const vThumbRate = ref(0);
const hOffsetRate = ref(0);
const vOffsetRate = ref(0);
const isBody = ref(false);
const showXBar = ref(false);
const showYBar = ref(false);
let lastTop = -1;
let lastLeft = -1;
let xTimer = null;
let yTimer = null;
let ro = null;
let lastScrollWidth = -1;
let lastScrollHeight = -1;
const updateScrollbar = () => {
if (!scrollTargetEl) {
return;
}
const { clientWidth, clientHeight, scrollWidth, scrollHeight, scrollTop, scrollLeft } = scrollTargetEl;
lastScrollWidth = scrollWidth;
lastScrollHeight = scrollHeight;
hThumbRate.value = clientWidth / scrollWidth;
vThumbRate.value = clientHeight / scrollHeight;
hOffsetRate.value = scrollLeft / scrollWidth;
vOffsetRate.value = scrollTop / scrollHeight;
if (!props.disabledX) {
hasX.value = clientWidth < scrollWidth;
}
if (!props.disabledY) {
hasY.value = clientHeight < scrollHeight;
}
};
const updateScrollbarByScollSize = () => {
if (!scrollTargetEl) {
return;
}
const { scrollWidth, scrollHeight } = scrollTargetEl;
if (lastScrollWidth !== scrollWidth || lastScrollHeight !== scrollHeight) {
updateScrollbar();
}
};
const onScroll = () => {
if (!scrollTargetEl) {
return;
}
const { scrollLeft, scrollWidth, scrollTop, scrollHeight } = scrollTargetEl;
if (lastScrollWidth !== scrollWidth || lastScrollHeight !== scrollHeight) {
updateScrollbar();
}
hOffsetRate.value = scrollLeft / scrollWidth;
vOffsetRate.value = scrollTop / scrollHeight;
if (lastLeft >= 0) {
showXBar.value = scrollLeft !== lastLeft;
if (xTimer) {
clearTimeout(xTimer);
}
xTimer = window.setTimeout(() => {
showXBar.value = false;
xTimer = null;
}, props.duration);
}
lastLeft = scrollLeft;
if (lastTop >= 0) {
showYBar.value = scrollTop !== lastTop;
if (yTimer) {
clearTimeout(yTimer);
yTimer = null;
}
yTimer = window.setTimeout(() => {
showYBar.value = false;
}, props.duration);
}
lastTop = scrollTop;
};
let childToObserve = null;
const init = () => {
if (!scrollTargetEl) {
return;
}
scrollTargetEl.classList.add(ScrollbarClass.container);
ro = useResizeObserver();
ro.observe(scrollTargetEl, updateScrollbar);
if (scrollTargetEl.children.length === 1 && scrollTargetEl.children[0] instanceof HTMLElement) {
childToObserve = scrollTargetEl.children[0];
ro.observe(childToObserve, updateScrollbar);
}
updateScrollbar();
scrollListenEl = isBody.value ? window : scrollTargetEl;
scrollListenEl.addEventListener("scroll", onScroll, { passive: true });
handleWrapperHoverEvent();
};
let updateTimer;
let updateIdleTimer;
const updateScrollbarOnIdle = () => {
if (window.requestIdleCallback) {
updateTimer = window.setInterval(() => {
updateIdleTimer = window.requestIdleCallback(updateScrollbarByScollSize);
}, 1e3);
}
};
const cancelUpdateScrollbarOnIdle = () => {
if (updateTimer) {
clearInterval(updateTimer);
if (cancelIdleCallback) {
cancelIdleCallback(updateIdleTimer);
}
updateTimer = 0;
updateIdleTimer = 0;
}
};
const { target } = toRefs(props);
resolveHtmlElement(target).then((el) => {
if (el === document.body) {
isBody.value = true;
scrollTargetEl = document.documentElement;
} else if (el) {
scrollTargetEl = el;
}
if (!scrollTargetEl) {
return;
}
init();
});
const isShowScrollbar = ref(props.showType === "always");
watchEffect(() => {
isShowScrollbar.value = props.showType === "always";
if (props.showType === "always") {
if (props.autoUpdateOnScrollSize) {
updateScrollbarOnIdle();
}
} else {
cancelUpdateScrollbarOnIdle();
}
});
let wrapperEl = null;
const onWrapperHoverIn = () => {
isShowScrollbar.value = true;
};
const onWrapperHoverOut = () => {
isShowScrollbar.value = false;
if (scrollTargetEl) {
const { scrollWidth, scrollHeight } = scrollTargetEl;
if (lastScrollWidth !== scrollWidth || lastScrollHeight !== scrollHeight) {
updateScrollbar();
}
}
};
const removeWrapperHoverEvent = () => {
if (wrapperEl) {
wrapperEl.removeEventListener("mouseenter", onWrapperHoverIn);
wrapperEl.removeEventListener("mouseleave", onWrapperHoverOut);
}
};
function handleWrapperHoverEvent() {
watchEffect(() => {
var _a;
const isHoverShow = props.showType === "hover" && !isPhonePad.value;
wrapperEl = (_a = rootRef.value) == null ? void 0 : _a.offsetParent;
if (!wrapperEl) {
return;
}
if (isHoverShow) {
wrapperEl == null ? void 0 : wrapperEl.addEventListener("mouseenter", onWrapperHoverIn);
wrapperEl == null ? void 0 : wrapperEl.addEventListener("mouseleave", onWrapperHoverOut);
} else {
removeWrapperHoverEvent();
}
});
}
onUnmounted(() => {
if (scrollTargetEl) {
ro == null ? void 0 : ro.unobserve(scrollTargetEl, updateScrollbar);
scrollListenEl == null ? void 0 : scrollListenEl.removeEventListener("scroll", onScroll);
}
if (childToObserve) {
ro == null ? void 0 : ro.unobserve(childToObserve, updateScrollbar);
}
removeWrapperHoverEvent();
cancelUpdateScrollbarOnIdle();
scrollTargetEl == null ? void 0 : scrollTargetEl.classList.remove(ScrollbarClass.container);
});
const onHBarScroll = (ratio) => {
if (scrollTargetEl) {
const d = ratio * scrollTargetEl.scrollWidth;
scrollTargetEl.scrollTo({
left: d
});
}
};
const onVBarScroll = (ratio) => {
if (scrollTargetEl) {
const d = ratio * scrollTargetEl.scrollHeight;
scrollTargetEl.scrollTo({
top: d
});
}
};
const onBarHoverIn = (d) => {
if (isPhonePad.value) {
return;
}
if (d === "x") {
showXBar.value = true;
if (xTimer) {
clearTimeout(xTimer);
yTimer = null;
}
} else if (d === "y") {
showYBar.value = true;
if (yTimer) {
clearTimeout(yTimer);
yTimer = null;
}
}
};
const onBarHoverOut = (d) => {
if (isPhonePad.value) {
return;
}
if (d === "x") {
xTimer = window.setTimeout(() => {
showXBar.value = false;
}, props.duration);
} else if (d === "y") {
yTimer = window.setTimeout(() => {
showYBar.value = false;
}, props.duration);
}
};
__expose({
/**
* @zh-CN 更新滚动条样式
* @en-US Update scrollbar styles
*/
update: updateScrollbar
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock(
"div",
{
ref_key: "rootRef",
ref: rootRef,
class: normalizeClass(["o-scrollbar", unref(mergeClass)(
`o-scrollbar-${props.size}`,
{
"o-scrollbar-auto-show": props.showType === "auto",
"o-scrollbar-always-show": props.showType === "always",
"o-scrollbar-hover-show": props.showType === "hover" && !unref(isPhonePad),
"o-scrollbar-visible": isShowScrollbar.value,
"o-scrollbar-both": hasX.value && hasY.value,
"o-scrollbar-visible-x": showXBar.value,
"o-scrollbar-visible-y": showYBar.value,
"o-scrollbar-to-body": isBody.value
},
props.barClass
)])
},
[
props.showType !== "never" ? (openBlock(), createElementBlock(
Fragment,
{ key: 0 },
[
hasX.value && !props.disabledX ? (openBlock(), createBlock(_sfc_main$1, {
key: 0,
size: props.size,
direction: "x",
"thumb-rate": hThumbRate.value,
"offset-rate": hOffsetRate.value,
onScroll: onHBarScroll,
onMouseenter: _cache[0] || (_cache[0] = ($event) => onBarHoverIn("x")),
onMouseleave: _cache[1] || (_cache[1] = ($event) => onBarHoverOut("x"))
}, {
thumb: withCtx(() => [
renderSlot(_ctx.$slots, "thumb")
]),
track: withCtx(() => [
renderSlot(_ctx.$slots, "track")
]),
_: 3
/* FORWARDED */
}, 8, ["size", "thumb-rate", "offset-rate"])) : createCommentVNode("v-if", true),
hasY.value && !props.disabledY ? (openBlock(), createBlock(_sfc_main$1, {
key: 1,
direction: "y",
size: props.size,
"thumb-rate": vThumbRate.value,
"offset-rate": vOffsetRate.value,
onScroll: onVBarScroll,
onMouseenter: _cache[2] || (_cache[2] = ($event) => onBarHoverIn("y")),
onMouseleave: _cache[3] || (_cache[3] = ($event) => onBarHoverOut("y"))
}, {
thumb: withCtx(() => [
renderSlot(_ctx.$slots, "thumb")
]),
track: withCtx(() => [
renderSlot(_ctx.$slots, "track")
]),
_: 3
/* FORWARDED */
}, 8, ["size", "thumb-rate", "offset-rate"])) : createCommentVNode("v-if", true)
],
64
/* STABLE_FRAGMENT */
)) : createCommentVNode("v-if", true)
],
2
/* CLASS */
);
};
}
});
export {
_sfc_main as default
};