UNPKG

naive-ui

Version:

A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast

245 lines 8.16 kB
import { getScrollParent, unwrapElement } from 'seemly'; import { useIsMounted, useMergedState } from 'vooks'; import { computed, defineComponent, h, mergeProps, nextTick, onBeforeUnmount, onMounted, ref, toRef, Transition, watch, watchEffect } from 'vue'; import { VLazyTeleport } from 'vueuc'; import { NBaseIcon } from "../../_internal/index.mjs"; import { useConfig, useTheme, useThemeClass } from "../../_mixins/index.mjs"; import { formatLength, isDocument, lockHtmlScrollRightCompensationRef, resolveSlot, warn, warnOnce } from "../../_utils/index.mjs"; import { backTopLight } from "../styles/index.mjs"; import renderBackTopIcon from "./BackTopIcon.mjs"; import style from "./styles/index.cssr.mjs"; export const backTopProps = Object.assign(Object.assign({}, useTheme.props), { show: { type: Boolean, default: undefined }, right: { type: [Number, String], default: 40 }, bottom: { type: [Number, String], default: 40 }, to: { type: [String, Object], default: 'body' }, visibilityHeight: { type: Number, default: 180 }, listenTo: [String, Object, Function], 'onUpdate:show': { type: Function, default: () => {} }, // deprecated target: Function, onShow: Function, onHide: Function }); export default defineComponent({ name: 'BackTop', // make style applied to back-top button inheritAttrs: false, props: backTopProps, setup(props) { if (process.env.NODE_ENV !== 'production') { watchEffect(() => { if (props.target !== undefined) { warnOnce('back-top', '`target` is deprecated, please use `listen-to` instead.'); } if (props.onShow !== undefined) { warnOnce('back-top', '`on-show` is deprecated, please use `on-update:show` instead.'); } if (props.onHide !== undefined) { warnOnce('back-top', '`on-hide` is deprecated, please use `on-update:show` instead.'); } }); } const { mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props); const scrollTopRef = ref(null); const uncontrolledShowRef = ref(false); watchEffect(() => { const { value: scrollTop } = scrollTopRef; if (scrollTop === null) { uncontrolledShowRef.value = false; return; } uncontrolledShowRef.value = scrollTop >= props.visibilityHeight; }); const DomInfoReadyRef = ref(false); watch(uncontrolledShowRef, value => { var _a; if (DomInfoReadyRef.value) { (_a = props['onUpdate:show']) === null || _a === void 0 ? void 0 : _a.call(props, value); } }); const controlledShowRef = toRef(props, 'show'); const mergedShowRef = useMergedState(controlledShowRef, uncontrolledShowRef); const transitionDisabledRef = ref(true); const placeholderRef = ref(null); const styleRef = computed(() => { return { right: `calc(${formatLength(props.right)} + ${lockHtmlScrollRightCompensationRef.value})`, bottom: formatLength(props.bottom) }; }); let scrollElement; let scrollListenerRegistered; // deprecated watch(mergedShowRef, value => { var _a, _b; if (DomInfoReadyRef.value) { if (value) { (_a = props.onShow) === null || _a === void 0 ? void 0 : _a.call(props); } (_b = props.onHide) === null || _b === void 0 ? void 0 : _b.call(props); } }); const themeRef = useTheme('BackTop', '-back-top', style, backTopLight, props, mergedClsPrefixRef); function init() { var _a; if (scrollListenerRegistered) return; scrollListenerRegistered = true; const scrollEl = ((_a = props.target) === null || _a === void 0 ? void 0 : _a.call(props)) || unwrapElement(props.listenTo) || getScrollParent(placeholderRef.value); if (!scrollEl) { if (process.env.NODE_ENV !== 'production') { warn('back-top', 'Container of back-top element is not found. This could be a bug of naive-ui.'); } return; } scrollElement = scrollEl === document.documentElement ? document : scrollEl; const { to } = props; const target = typeof to === 'string' ? document.querySelector(to) : to; if (process.env.NODE_ENV !== 'production' && !target) { warn('back-top', 'Target is not found.'); } scrollElement.addEventListener('scroll', handleScroll); handleScroll(); } function handleClick() { ; (isDocument(scrollElement) ? document.documentElement : scrollElement).scrollTo({ top: 0, behavior: 'smooth' }); } function handleScroll() { scrollTopRef.value = (isDocument(scrollElement) ? document.documentElement : scrollElement).scrollTop; if (!DomInfoReadyRef.value) { void nextTick(() => { DomInfoReadyRef.value = true; }); } } function handleAfterEnter() { transitionDisabledRef.value = false; } onMounted(() => { init(); transitionDisabledRef.value = mergedShowRef.value; }); onBeforeUnmount(() => { if (scrollElement) { scrollElement.removeEventListener('scroll', handleScroll); } }); const cssVarsRef = computed(() => { const { self: { color, boxShadow, boxShadowHover, boxShadowPressed, iconColor, iconColorHover, iconColorPressed, width, height, iconSize, borderRadius, textColor }, common: { cubicBezierEaseInOut } } = themeRef.value; return { '--n-bezier': cubicBezierEaseInOut, '--n-border-radius': borderRadius, '--n-height': height, '--n-width': width, '--n-box-shadow': boxShadow, '--n-box-shadow-hover': boxShadowHover, '--n-box-shadow-pressed': boxShadowPressed, '--n-color': color, '--n-icon-size': iconSize, '--n-icon-color': iconColor, '--n-icon-color-hover': iconColorHover, '--n-icon-color-pressed': iconColorPressed, '--n-text-color': textColor }; }); const themeClassHandle = inlineThemeDisabled ? useThemeClass('back-top', undefined, cssVarsRef, props) : undefined; return { placeholderRef, style: styleRef, mergedShow: mergedShowRef, isMounted: useIsMounted(), scrollElement: ref(null), scrollTop: scrollTopRef, DomInfoReady: DomInfoReadyRef, transitionDisabled: transitionDisabledRef, mergedClsPrefix: mergedClsPrefixRef, handleAfterEnter, handleScroll, handleClick, 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() { const { mergedClsPrefix } = this; return h("div", { ref: "placeholderRef", class: `${mergedClsPrefix}-back-top-placeholder`, style: "display: none", "aria-hidden": true }, h(VLazyTeleport, { to: this.to, show: this.mergedShow }, { default: () => h(Transition, { name: "fade-in-scale-up-transition", appear: this.isMounted, onAfterEnter: this.handleAfterEnter }, { default: () => { var _a; (_a = this.onRender) === null || _a === void 0 ? void 0 : _a.call(this); return this.mergedShow ? h('div', mergeProps(this.$attrs, { class: [`${mergedClsPrefix}-back-top`, this.themeClass, this.transitionDisabled && `${mergedClsPrefix}-back-top--transition-disabled`], style: [this.style, this.cssVars], onClick: this.handleClick }), resolveSlot(this.$slots.default, () => [h(NBaseIcon, { clsPrefix: mergedClsPrefix }, { default: renderBackTopIcon })])) : null; } }) })); } });