From 518bbbc07bb0eeab300b5b9d9d5e0502ebc069aa Mon Sep 17 00:00:00 2001 From: shenjunjian Date: Wed, 21 May 2025 15:59:29 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat(tooltip):=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E7=94=A8=E6=A8=A1=E6=9D=BF=E9=87=8D=E6=9E=84tooltip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...stom-markdown.css => custom-markdown.less} | 0 examples/sites/src/main.js | 2 +- packages/renderless/src/tooltip/new-index.ts | 32 ++++ packages/renderless/src/tooltip/new-vue.ts | 68 ++++++++ packages/vue-hooks/index.ts | 1 + packages/vue-hooks/src/useTimer.ts | 36 +++++ packages/vue/src/tooltip/src/index.ts | 8 +- packages/vue/src/tooltip/src/tooltip.vue | 148 ++++++++++++++++++ 8 files changed, 293 insertions(+), 2 deletions(-) rename examples/sites/src/assets/{custom-markdown.css => custom-markdown.less} (100%) create mode 100644 packages/renderless/src/tooltip/new-index.ts create mode 100644 packages/renderless/src/tooltip/new-vue.ts create mode 100644 packages/vue-hooks/src/useTimer.ts create mode 100644 packages/vue/src/tooltip/src/tooltip.vue diff --git a/examples/sites/src/assets/custom-markdown.css b/examples/sites/src/assets/custom-markdown.less similarity index 100% rename from examples/sites/src/assets/custom-markdown.css rename to examples/sites/src/assets/custom-markdown.less diff --git a/examples/sites/src/main.js b/examples/sites/src/main.js index d009daa8d5..393e910265 100644 --- a/examples/sites/src/main.js +++ b/examples/sites/src/main.js @@ -12,7 +12,7 @@ import './assets/index.less' import './style.css' // 覆盖默认的github markdown样式 -import './assets/custom-markdown.css' +import './assets/custom-markdown.less' import './assets/custom-block.less' import './assets/md-preview.less' diff --git a/packages/renderless/src/tooltip/new-index.ts b/packages/renderless/src/tooltip/new-index.ts new file mode 100644 index 0000000000..34e643aa92 --- /dev/null +++ b/packages/renderless/src/tooltip/new-index.ts @@ -0,0 +1,32 @@ +export const handleRefEvent = + ({ props, api }) => + (type: string) => { + if (props.manual) return + + if (type === 'mouseenter') { + api.cancelDelayHide() + api.delayShow() + } else if (type === 'mouseleave') { + api.cancelDelayShow() + api.delayHide() + } + } + +export const handlePopEvent = + ({ props, api }) => + (type: string) => { + if (props.manual) return + + if (type === 'mouseenter') { + api.cancelDelayHide() + } + } + +export const toggleShow = + ({ state, props, emit }) => + (isShow: boolean) => { + state.showPopper = isShow + if (props.manual) { + emit('update:show', isShow) + } + } diff --git a/packages/renderless/src/tooltip/new-vue.ts b/packages/renderless/src/tooltip/new-vue.ts new file mode 100644 index 0000000000..3d4e1d971c --- /dev/null +++ b/packages/renderless/src/tooltip/new-vue.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2022 - present TinyVue Authors. + * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { handlePopEvent, handleRefEvent, toggleShow } from './new-index' +import { userPopper, useTimer } from '@opentiny/vue-hooks' + +export const api = ['state', 'handlePopEvent', 'handleRefEvent'] + +export const renderless = ( + props, + { watch, toRefs, toRef, reactive, onBeforeUnmount, onDeactivated, onMounted, onUnmounted, inject }, + { vm, emit, slots, nextTick, parent } +) => { + const api = {} as any + + const { showPopper, updatePopper, popperElm, referenceElm, doDestroy, popperJS } = userPopper({ + emit, + props, + nextTick, + toRefs, + reactive, + parent: parent.$parent, + vm, + slots, + onBeforeUnmount, + onDeactivated, + watch + } as any) + + const state = reactive({ + showPopper, + popperElm, + referenceElm, + showContent: inject('showContent', null), + tipsMaxWidth: inject('tips-max-width', null) + }) + + const { start: delayShow, clear: cancelDelayShow } = useTimer(() => api.toggleShow(true), toRef(props, 'openDelay')) + const { start: delayHide, clear: cancelDelayHide } = useTimer(() => api.toggleShow(false), toRef(props, 'closeDelay')) + + Object.assign(api, { + state, + delayShow, + cancelDelayShow, + delayHide, + cancelDelayHide, + handlePopEvent: handlePopEvent({ props, api }), + handleRefEvent: handleRefEvent({ props, api }), + toggleShow: toggleShow({ state, props, emit }) + }) + + onMounted(() => {}) + + vm.$on('tooltip-update', updatePopper()) + + onUnmounted(() => {}) + + return api +} diff --git a/packages/vue-hooks/index.ts b/packages/vue-hooks/index.ts index 4c8e1bd425..36c82f10fb 100644 --- a/packages/vue-hooks/index.ts +++ b/packages/vue-hooks/index.ts @@ -21,3 +21,4 @@ export { useUserAgent } from './src/useUserAgent' export { useWindowSize } from './src/useWindowSize' export { userPopper } from './src/vue-popper' export { usePopup } from './src/vue-popup' +export { useTimer } from './src/useTimer' diff --git a/packages/vue-hooks/src/useTimer.ts b/packages/vue-hooks/src/useTimer.ts new file mode 100644 index 0000000000..83ce3e23e8 --- /dev/null +++ b/packages/vue-hooks/src/useTimer.ts @@ -0,0 +1,36 @@ +import { onUnmounted, ref, type MaybeRef } from 'vue' + +/** 延时触发的通用定时器。 setTimeout/ debounce 的地方均由该函数代替。 + * 比如按钮禁用, 1秒后修改disable为false + * 1、 防止连续触发 + * 2、 组件卸载时,自动取消 + * @example + * const {start: resetDisabled } = useTimer(()=> state.disabled=false, 1000) + * resetDisabled(); + * + * const {start: debounceQuery } = useTimer((page)=> grid.query(page), 500) + * debounceQuery(1); + * debounceQuery(2); // 仅请求第2页 + */ +export function useTimer(cb: (...args: any[]) => void, delay: MaybeRef) { + let timerId = 0 + const $delay = ref(delay) + + function start(...args: any[]) { + clear() + timerId = setTimeout(() => { + cb(...args) + timerId = 0 + }, $delay.value) + } + function clear() { + if (timerId) { + clearTimeout(timerId) + timerId = 0 + } + } + + onUnmounted(() => clear()) + + return { start, clear, delay: $delay } +} diff --git a/packages/vue/src/tooltip/src/index.ts b/packages/vue/src/tooltip/src/index.ts index 4c5ffa7456..b52569cb36 100644 --- a/packages/vue/src/tooltip/src/index.ts +++ b/packages/vue/src/tooltip/src/index.ts @@ -1,7 +1,13 @@ import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common' import type { ITooltipApi } from '@opentiny/vue-renderless/types/tooltip.type' +import TooltipPc from './tooltip.vue' +import TooltipMf from './mobile-first.vue' -import template from 'virtual-template?pc|mobile-first' +// 切换下面,以发布不同的tooltip组件 + +// import template // from 'virtual-template?pc|mobile-first' + +const template = (mode) => (mode === 'pc' ? TooltipPc : TooltipMf) // 新PC, 老 MF export const tooltipProps = { ...$props, diff --git a/packages/vue/src/tooltip/src/tooltip.vue b/packages/vue/src/tooltip/src/tooltip.vue new file mode 100644 index 0000000000..aa46e0dcc9 --- /dev/null +++ b/packages/vue/src/tooltip/src/tooltip.vue @@ -0,0 +1,148 @@ + + + From 45d814d91eb315e47729d2c03c16e61f587930da Mon Sep 17 00:00:00 2001 From: shenjunjian Date: Wed, 21 May 2025 17:18:09 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix(tooltip):=20=E8=BF=98=E5=8E=9F=E6=89=80?= =?UTF-8?q?=E6=9C=89=E5=B1=9E=E6=80=A7=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=A0=B8=E9=AA=8C=E6=89=80=E6=9C=89=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/renderless/src/tooltip/new-index.ts | 20 +++++++++- packages/renderless/src/tooltip/new-vue.ts | 42 ++++++++++++-------- packages/vue/src/tooltip/src/tooltip.vue | 27 ++++++++----- 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/packages/renderless/src/tooltip/new-index.ts b/packages/renderless/src/tooltip/new-index.ts index 34e643aa92..157734d781 100644 --- a/packages/renderless/src/tooltip/new-index.ts +++ b/packages/renderless/src/tooltip/new-index.ts @@ -16,17 +16,33 @@ export const handlePopEvent = ({ props, api }) => (type: string) => { if (props.manual) return + if (!props.enterable) return if (type === 'mouseenter') { api.cancelDelayHide() + } else if (type === 'mouseleave') { + api.delayHide() } } export const toggleShow = - ({ state, props, emit }) => + ({ state, props, emit, api }) => (isShow: boolean) => { + // 智能识别模式 + if (props.visible === 'auto' && state.referenceElm) { + const { clientWidth, scrollWidth } = state.referenceElm.firstElementChild + if (scrollWidth <= clientWidth) { + return + } + } + state.showPopper = isShow if (props.manual) { - emit('update:show', isShow) + emit('update:modelValue', isShow) + } + + // 自动隐藏: 如果显示,且要自动隐藏,则延时后关闭 + if (!props.manual && props.hideAfter && isShow) { + api.delayHideAfter() } } diff --git a/packages/renderless/src/tooltip/new-vue.ts b/packages/renderless/src/tooltip/new-vue.ts index 3d4e1d971c..4974144bc1 100644 --- a/packages/renderless/src/tooltip/new-vue.ts +++ b/packages/renderless/src/tooltip/new-vue.ts @@ -1,17 +1,6 @@ -/** - * Copyright (c) 2022 - present TinyVue Authors. - * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. - * - * Use of this source code is governed by an MIT-style license. - * - * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, - * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR - * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. - * - */ - import { handlePopEvent, handleRefEvent, toggleShow } from './new-index' import { userPopper, useTimer } from '@opentiny/vue-hooks' +import { guid } from '@opentiny/utils' export const api = ['state', 'handlePopEvent', 'handleRefEvent'] @@ -21,7 +10,7 @@ export const renderless = ( { vm, emit, slots, nextTick, parent } ) => { const api = {} as any - + const popperVmRef = {} const { showPopper, updatePopper, popperElm, referenceElm, doDestroy, popperJS } = userPopper({ emit, props, @@ -33,19 +22,23 @@ export const renderless = ( slots, onBeforeUnmount, onDeactivated, - watch + watch, + popperVmRef } as any) const state = reactive({ showPopper, popperElm, referenceElm, + tooltipId: guid('tiny-tooltip-', 4), showContent: inject('showContent', null), tipsMaxWidth: inject('tips-max-width', null) }) + state.showPopper = false // 初始为false const { start: delayShow, clear: cancelDelayShow } = useTimer(() => api.toggleShow(true), toRef(props, 'openDelay')) const { start: delayHide, clear: cancelDelayHide } = useTimer(() => api.toggleShow(false), toRef(props, 'closeDelay')) + const { start: delayHideAfter } = useTimer(() => api.toggleShow(false), toRef(props, 'hideAfter')) Object.assign(api, { state, @@ -53,12 +46,27 @@ export const renderless = ( cancelDelayShow, delayHide, cancelDelayHide, + delayHideAfter, handlePopEvent: handlePopEvent({ props, api }), handleRefEvent: handleRefEvent({ props, api }), - toggleShow: toggleShow({ state, props, emit }) + toggleShow: toggleShow({ state, props, emit, api }) + }) + watch( + () => props.modelValue, + (val) => { + if (props.manual) { + val ? delayShow() : delayHide() + } + } + ) + onMounted(() => { + state.popperElm = vm.$refs.popperRef + state.referenceElm = vm.$refs.referenceRef + // 初始显示 + if (props.manual && props.modelValue) { + state.showPopper = true + } }) - - onMounted(() => {}) vm.$on('tooltip-update', updatePopper()) diff --git a/packages/vue/src/tooltip/src/tooltip.vue b/packages/vue/src/tooltip/src/tooltip.vue index aa46e0dcc9..355f8267b0 100644 --- a/packages/vue/src/tooltip/src/tooltip.vue +++ b/packages/vue/src/tooltip/src/tooltip.vue @@ -1,31 +1,35 @@