Skip to content

Commit

Permalink
fix(vue-directive): unify the functions in vue-hooks into double-laye…
Browse files Browse the repository at this point in the history
…r functions and optimize the infinite-scroll function (#2864)
  • Loading branch information
shenjunjian authored Feb 7, 2025
1 parent 2581baa commit f4fcf7a
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 122 deletions.
1 change: 0 additions & 1 deletion packages/renderless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
},
"dependencies": {
"@opentiny/utils": "workspace:~",
"@opentiny/vue-directive": "workspace:~",
"@opentiny/vue-hooks": "workspace:~",
"color": "4.2.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/renderless/src/recycle-scroller/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getScrollContainer } from '@opentiny/vue-directive'
import { getScrollContainer } from '@opentiny/utils'
import { isNull } from '@opentiny/utils'

let supportsPassive = false
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-directive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
53 changes: 2 additions & 51 deletions packages/vue-directive/src/infinite-scroll.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { throttle } from '@opentiny/utils'
import { getScrollContainer } from '@opentiny/utils'

const CONTEXT_KEY = '@@infinitescrollContext'
const OBSERVER_CHECK_INTERVAL = 50
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -211,7 +162,7 @@ const unbind = (el) => {
stopObserver(el)
}

export const InfiniteScroll = {
export default {
bind,
update,
unbind,
Expand Down
5 changes: 2 additions & 3 deletions packages/vue-hooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
},
"dependencies": {
"@floating-ui/dom": "^1.6.9",
"@opentiny/utils": "workspace:~",
"@opentiny/vue-common": "workspace:~"
"@opentiny/utils": "workspace:~"
}
}
}
106 changes: 52 additions & 54 deletions packages/vue-hooks/src/use-floating.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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<IFloatOption> = {}) => {
const state = reactive(option) as IFloatOption
export const useFloating =
({ reactive, watch, markRaw, onBeforeUnmount }) =>
(option: Partial<IFloatOption> = {}) => {
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 }
}
18 changes: 8 additions & 10 deletions packages/vue-hooks/src/use-lazy-show.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,11 +8,13 @@ const { ref, watch, isRef } = hooks
* isShow 第一次为true时,才会加载该DOM
* </div>
*/
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 }
}

0 comments on commit f4fcf7a

Please sign in to comment.