From f4fcf7ab54cfbd33a00004d604369c98fced961f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B3=E5=90=9B=E5=81=A5?= <40288193@qq.com> Date: Fri, 7 Feb 2025 15:56:45 +0800 Subject: [PATCH] fix(vue-directive): unify the functions in vue-hooks into double-layer functions and optimize the infinite-scroll function (#2864) --- packages/renderless/package.json | 1 - .../renderless/src/recycle-scroller/index.ts | 2 +- packages/vue-directive/index.ts | 4 +- packages/vue-directive/src/infinite-scroll.ts | 53 +-------- packages/vue-hooks/package.json | 5 +- packages/vue-hooks/src/use-floating.ts | 106 +++++++++--------- packages/vue-hooks/src/use-lazy-show.ts | 18 ++- 7 files changed, 67 insertions(+), 122 deletions(-) diff --git a/packages/renderless/package.json b/packages/renderless/package.json index 05f87d9923..59abadcae3 100644 --- a/packages/renderless/package.json +++ b/packages/renderless/package.json @@ -37,7 +37,6 @@ }, "dependencies": { "@opentiny/utils": "workspace:~", - "@opentiny/vue-directive": "workspace:~", "@opentiny/vue-hooks": "workspace:~", "color": "4.2.3" }, diff --git a/packages/renderless/src/recycle-scroller/index.ts b/packages/renderless/src/recycle-scroller/index.ts index 33311e7cd3..55f85dd217 100644 --- a/packages/renderless/src/recycle-scroller/index.ts +++ b/packages/renderless/src/recycle-scroller/index.ts @@ -1,4 +1,4 @@ -import { getScrollContainer } from '@opentiny/vue-directive' +import { getScrollContainer } from '@opentiny/utils' import { isNull } from '@opentiny/utils' let supportsPassive = false diff --git a/packages/vue-directive/index.ts b/packages/vue-directive/index.ts index baca57adf8..cb0b4a9033 100644 --- a/packages/vue-directive/index.ts +++ b/packages/vue-directive/index.ts @@ -13,8 +13,8 @@ import AutoTip from './src/auto-tip' import Clickoutside from './src/clickoutside' import HighlightQuery from './src/highlight-query' +import InfiniteScroll from './src/infinite-scroll' import ObserveVisibility from './src/observe-visibility' import RepeatClick from './src/repeat-click' -export { InfiniteScroll, getScrollContainer } from './src/infinite-scroll' -export { AutoTip, HighlightQuery, Clickoutside, RepeatClick, ObserveVisibility } +export { AutoTip, HighlightQuery, Clickoutside, RepeatClick, ObserveVisibility, InfiniteScroll } diff --git a/packages/vue-directive/src/infinite-scroll.ts b/packages/vue-directive/src/infinite-scroll.ts index 31f7ebd89c..c168141fc5 100644 --- a/packages/vue-directive/src/infinite-scroll.ts +++ b/packages/vue-directive/src/infinite-scroll.ts @@ -1,4 +1,5 @@ import { throttle } from '@opentiny/utils' +import { getScrollContainer } from '@opentiny/utils' const CONTEXT_KEY = '@@infinitescrollContext' const OBSERVER_CHECK_INTERVAL = 50 @@ -107,56 +108,6 @@ function observerChecker(el, cb) { } } -const cached = (fn) => { - const cache = Object.create(null) - return (str) => cache[str] || (cache[str] = fn(str)) -} - -const camelizeRE = /-(\w)/g - -const camelize = cached((str) => str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))) - -/** TINY_DUP dom.ts */ -const getElementStyle = (elem, styleKey) => { - if (!elem || !styleKey) return '' - - let key = camelize(styleKey) - - if (key === 'float') key = 'cssFloat' - - try { - const styleValue = elem.style[key] - - if (styleValue) return styleValue - - const computedStyle = document.defaultView ? document.defaultView.getComputedStyle(elem, '') : null - - return computedStyle ? computedStyle[key] : '' - } catch (e) { - return elem.style[key] - } -} -/** TINY_DUP dom.ts */ -const canScroll = (el, isVertical) => { - const overflowKey = { undefined: 'overflow', true: 'overflow-y', false: 'overflow-x' }[String(isVertical)] - const overflowVal = getElementStyle(el, overflowKey) - return ['scroll', 'auto', 'overlay'].some((s) => overflowVal.includes(s)) -} -/** TINY_DUP dom.ts */ -export const getScrollContainer = (el, isVertical) => { - let parentEl = el - - while (parentEl) { - if ([window, document, document.documentElement].includes(parentEl)) return window - - if (canScroll(parentEl, isVertical)) return parentEl - - parentEl = parentEl.parentNode - } - - return parentEl -} - const bind = (el, binding, vnode) => { const instance = binding.instance || vnode.context const { value: cb } = binding @@ -211,7 +162,7 @@ const unbind = (el) => { stopObserver(el) } -export const InfiniteScroll = { +export default { bind, update, unbind, diff --git a/packages/vue-hooks/package.json b/packages/vue-hooks/package.json index 3abffd7377..de7fe803a9 100644 --- a/packages/vue-hooks/package.json +++ b/packages/vue-hooks/package.json @@ -15,7 +15,6 @@ }, "dependencies": { "@floating-ui/dom": "^1.6.9", - "@opentiny/utils": "workspace:~", - "@opentiny/vue-common": "workspace:~" + "@opentiny/utils": "workspace:~" } -} \ No newline at end of file +} diff --git a/packages/vue-hooks/src/use-floating.ts b/packages/vue-hooks/src/use-floating.ts index e874057100..0f9dcded05 100644 --- a/packages/vue-hooks/src/use-floating.ts +++ b/packages/vue-hooks/src/use-floating.ts @@ -1,10 +1,6 @@ import type { Placement, Strategy, OffsetOptions, RootBoundary, Boundary, ReferenceElement } from '@floating-ui/dom' import { computePosition, autoUpdate, flip, offset, shift, arrow, hide, limitShift } from '@floating-ui/dom' -import { hooks } from '@opentiny/vue-common' - -const { reactive, watch, markRaw, onBeforeUnmount } = hooks - interface IFloatOption { reference: null | ReferenceElement popper: null | HTMLElement @@ -330,7 +326,7 @@ const emit = (state: IFloatOption, eventName: string, params?: any) => { } /** 快速构建虚拟元素的辅助方法, 适于右键菜单,区域选择, 跟随光标等场景 */ -const virtualEl = (x: number, y: number, w: number = 0, h: number = 0) => ({ +const virtualEl = (x: number, y: number, w = 0, h = 0) => ({ getBoundingClientRect() { return { width: 0, @@ -346,66 +342,68 @@ const virtualEl = (x: number, y: number, w: number = 0, h: number = 0) => ({ }) /** 响应式的弹出层管理函数,适用于场景: tooltip, poppover, select, 右键菜单, floatbar, notify, 或 canvas上跟随鼠标等 */ -export const useFloating = (option: Partial = {}) => { - const state = reactive(option) as IFloatOption +export const useFloating = + ({ reactive, watch, markRaw, onBeforeUnmount }) => + (option: Partial = {}) => { + const state = reactive(option) as IFloatOption - let cleanup: null | (() => void) = null + let cleanup: null | (() => void) = null - // 0、标准化state - Object.keys(defaultOption).forEach((key) => { - if (!Object.prototype.hasOwnProperty.call(state, key)) { - state[key] = defaultOption[key] - } - }) - state._last = markRaw({}) as any - state._events = markRaw({ show: [], hide: [], update: [] }) - - const watchState = () => { - // 1、引用和弹窗同时存在 - if (state.popper && state.reference) { - // 1.1 当前需要显示, 可能是show变化了,也可能是其它任意值变化了, 都需要重新的一次update - if (state.show) { - appendPopper(state) - if (state.autoUpdate) { + // 0、标准化state + Object.keys(defaultOption).forEach((key) => { + if (!Object.prototype.hasOwnProperty.call(state, key)) { + state[key] = defaultOption[key] + } + }) + state._last = markRaw({}) as any + state._events = markRaw({ show: [], hide: [], update: [] }) + + const watchState = () => { + // 1、引用和弹窗同时存在 + if (state.popper && state.reference) { + // 1.1 当前需要显示, 可能是show变化了,也可能是其它任意值变化了, 都需要重新的一次update + if (state.show) { + appendPopper(state) + if (state.autoUpdate) { + cleanup && cleanup() + cleanup = autoUpdatePopper(state) + } else { + updatePopper(state) + } + } + // 1.2 当前不需要显示 + else { cleanup && cleanup() - cleanup = autoUpdatePopper(state) - } else { - updatePopper(state) + closePopper(state) } } - // 1.2 当前不需要显示 + // 2、引用和弹窗不全。 可能前一次是全的,所以要释放一下 else { cleanup && cleanup() closePopper(state) } - } - // 2、引用和弹窗不全。 可能前一次是全的,所以要释放一下 - else { - cleanup && cleanup() - closePopper(state) - } - state._last.popper = state.popper - state._last.reference = state.reference - state._last.show = (state.show && state.popper && state.reference) as boolean // 真实的是否show变量 - state._last.appendToBody = state.appendToBody - state._last.timestamp = new Date().getTime() - } + state._last.popper = state.popper + state._last.reference = state.reference + state._last.show = (state.show && state.popper && state.reference) as boolean // 真实的是否show变量 + state._last.appendToBody = state.appendToBody + state._last.timestamp = new Date().getTime() + } - watch(state, watchState, { immediate: true }) + watch(state, watchState, { immediate: true }) - const on = (eventName, cb) => state._events[eventName].push(cb) - const off = (eventName, cb) => (state._events[eventName] = state._events[eventName].filter((i) => i !== cb)) + const on = (eventName, cb) => state._events[eventName].push(cb) + const off = (eventName, cb) => (state._events[eventName] = state._events[eventName].filter((i) => i !== cb)) - // 3、组件卸载前,移除元素 - onBeforeUnmount(() => { - cleanup && cleanup() - closePopper(state) - }) + // 3、组件卸载前,移除元素 + onBeforeUnmount(() => { + cleanup && cleanup() + closePopper(state) + }) - // 4、返回state 及辅助方法 - // 正常修改state去触发更新,但如果某些业务想在state不变时,仍想执行一次更新, 则使用forceUpdate即可 - // 比如select 懒加载: popper, show都不变, 但popper 的大小变化了,可以forceUpdate一下。 - // 【autoUpdate 理论上会监听 popper的resize的, 这层考虑可能是多余。】 - return { state, on, off, virtualEl, forceUpdate: watchState } -} + // 4、返回state 及辅助方法 + // 正常修改state去触发更新,但如果某些业务想在state不变时,仍想执行一次更新, 则使用forceUpdate即可 + // 比如select 懒加载: popper, show都不变, 但popper 的大小变化了,可以forceUpdate一下。 + // 【autoUpdate 理论上会监听 popper的resize的, 这层考虑可能是多余。】 + return { state, on, off, virtualEl, forceUpdate: watchState } + } diff --git a/packages/vue-hooks/src/use-lazy-show.ts b/packages/vue-hooks/src/use-lazy-show.ts index 2095900b32..5ceb4bb953 100644 --- a/packages/vue-hooks/src/use-lazy-show.ts +++ b/packages/vue-hooks/src/use-lazy-show.ts @@ -1,7 +1,3 @@ -import { hooks } from '@opentiny/vue-common' - -const { ref, watch, isRef } = hooks - /** 慢加载的 v-show 的办法, 灵感来自于: https://github.com/antfu/v-lazy-show * 适用场景: 存在初始加载时,不需要显示的区域,但又需要用v-show切换显示。 比如 tabs\collapse\dropdown\cascader\carousel等 * @example @@ -12,11 +8,13 @@ const { ref, watch, isRef } = hooks * isShow 第一次为true时,才会加载该DOM * */ -export const useLazyShow = (show) => { - const lazyShow = ref(isRef(show) ? show.value : show) +export const useLazyShow = + ({ ref, watch, isRef }) => + (show) => { + const lazyShow = ref(isRef(show) ? show.value : show) - if (!lazyShow.value) { - const stop = watch(show, (v) => v && (lazyShow.value = true) && stop(), { flush: 'sync' }) + if (!lazyShow.value) { + const stop = watch(show, (v) => v && (lazyShow.value = true) && stop(), { flush: 'sync' }) + } + return { lazyShow } } - return { lazyShow } -}