UNPKG

tdesign-vue-next

Version:
1 lines 151 kB
{"version":3,"file":"dep-9ce910b6.mjs","sources":["../../../shared/hooks/icon/index.tsx","../../../shared/hooks/slot/index.ts","../../../common/js/utils/general.ts","../../../shared/hooks/tnode/index.ts","../../../shared/hooks/useCollapseAnimation/index.ts","../../../shared/hooks/useConfig/index.ts","../../../shared/hooks/useCommonClassName/index.ts","../../../shared/hooks/useDefaultValue/index.ts","../../../shared/hooks/useDestroyOnClose/index.ts","../../../shared/hooks/useDisabled/index.ts","../../../shared/hooks/useDragSort/index.ts","../../../shared/hooks/useElementLazyRender/index.ts","../../../shared/hooks/useGlobalIcon/index.ts","../../../shared/hooks/useImagePreviewUrl/index.ts","../../../shared/hooks/useKeepAnimation/index.ts","../../../shared/hooks/useLazyLoad/index.ts","../../../shared/hooks/useListener/index.ts","../../../shared/hooks/usePopupManager/index.ts","../../../shared/hooks/useReadonly/index.ts","../../../shared/hooks/useResizeObserver/index.ts","../../../common/js/utils/setStyle.ts","../../../shared/hooks/useRipple/index.ts","../../../shared/hooks/useTeleport/index.ts","../../../shared/hooks/useVModel/index.ts","../../../shared/hooks/useVirtualScroll/index.ts","../../../shared/hooks/useVirtualScrollNew/index.ts","../../../shared/hooks/useMutationObservable/index.ts","../../../common/js/utils/getColorTokenColor.ts","../../../shared/hooks/useVariables/index.ts","../../../shared/hooks/useEventForward/index.ts"],"sourcesContent":["import { isFunction } from 'lodash-es';\nimport { getCurrentInstance, h } from 'vue';\n\n/**\n * 渲染icon,用于icon、close等渲染图标的场景\n * @example const renderIconTNode = useIcon();\n * @returns renderIconTNode\n * @param iconType 要渲染的icon元素\n * @param defaultIcons 默认icon集合\n */\nexport function useIcon() {\n const instance = getCurrentInstance();\n return function renderIconTNode(iconType: string, defaultIcons?: Record<string, any>) {\n let iconContent;\n // 传入的是渲染函数\n if (isFunction(instance.props[iconType])) {\n iconContent = instance.props[iconType](h);\n } else if (instance.slots[iconType]) {\n // 插槽slot\n iconContent = instance.slots[iconType] && instance.slots[iconType](null)[0];\n } else if (defaultIcons) {\n const Component = defaultIcons[instance.props.theme as string];\n iconContent = <Component></Component>;\n }\n return iconContent;\n };\n}\n","import {\n Slots,\n VNode,\n Component,\n getCurrentInstance,\n Fragment,\n Comment,\n RendererNode,\n VNodeArrayChildren,\n RendererElement,\n VNodeChild,\n isVNode,\n Teleport,\n} from 'vue';\nimport { isArray } from 'lodash-es';\nimport { getChildren } from '@tdesign/shared-utils';\n\n/**\n * 渲染default slot,获取子组件VNode。处理多种子组件创建场景\n * 使用场景:<t-steps> <t-steps-item /> </t-steps>, <t-steps> <t-steps-item v-for=\"(item, index)\" :key=\"index\" /> </t-steps>\n * @returns {function(childComponentName: string, slots: Slots): VNode[]}\n * @param childComponentName\n * @param slots\n * @example const getChildByName = useChildComponentSlots()\n * @example getChildComponentByName('TStepItem')\n */\nexport function useChildComponentSlots() {\n const instance = getCurrentInstance();\n return (childComponentName: string, slots?: Slots): VNode[] => {\n if (!slots) {\n slots = instance.slots;\n }\n const content = slots?.default?.() || [];\n\n return getChildren(content).filter((item: VNode) =>\n (item.type as Component).name?.endsWith(childComponentName),\n ) as VNode[];\n };\n}\n\n/**\n * 渲染default slot,获取slot child\n * @param childComponentName\n * @param slots\n * @example const getChildSlots = useChildSlots()\n * @example getChildSlots()\n */\nexport function useChildSlots(): () => (\n | VNode<\n RendererNode,\n RendererElement,\n {\n [key: string]: any;\n }\n >\n | VNodeArrayChildren\n | VNodeChild\n)[] {\n const instance = getCurrentInstance();\n return () => {\n const { slots } = instance;\n const content = slots?.default?.() || [];\n\n return content\n .filter((item) => {\n if (typeof item.type === 'symbol' && !item.children) {\n return false;\n }\n return item.type !== Comment;\n })\n .map((item) => {\n if (item.children && isArray(item.children) && item.type === Fragment) return item.children;\n return item;\n })\n .flat();\n };\n}\n\n/**\n * 递归展开所有 Fragment,并跳过 Comment 节点,返回一维 VNodeChild 数组\n * @example const useFlatChildrenSlots = useFlatChildrenSlotsHook()\n * @example useFlatChildrenSlots(children)\n */\nexport function useFlatChildrenSlots() {\n function getFlatChildren(children: VNodeChild[]): VNodeChild[] {\n const result: VNodeChild[] = [];\n children.forEach((child) => {\n if (isVNode(child) && child.type === Fragment && Array.isArray(child.children)) {\n result.push(...getFlatChildren(child.children as VNodeChild[]));\n } else if (isVNode(child) && [Teleport, Comment].some((vNode) => vNode === child.type)) {\n // skip Teleport and Comment\n } else {\n result.push(child);\n }\n });\n return result;\n }\n return getFlatChildren;\n}\n","import { isFunction, isObject } from 'lodash-es';\n\nconst { hasOwnProperty } = Object.prototype;\n\nexport const hasOwn = <T extends object>(val: T, key: string | symbol | number): key is keyof T =>\n hasOwnProperty.call(val, key);\nexport const getPropertyValFromObj = <T extends object>(\n val: T,\n key: string | symbol | number\n): T[keyof T] | undefined => (hasOwn(val, key) ? val[key] : undefined);\n\nconst objectToString: typeof Object.prototype.toString = Object.prototype.toString;\nconst toTypeString = (value: unknown): string => objectToString.call(value);\nexport const isPlainObject = <T extends object>(val: unknown): val is T => toTypeString(val) === '[object Object]';\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> =>\n (isObject(val) || isFunction(val)) && isFunction((val as any).then) && isFunction((val as any).catch);\n","import { h, getCurrentInstance, ComponentInternalInstance, VNode } from 'vue';\nimport { camelCase, kebabCase, isFunction, isString, isObject } from 'lodash-es';\n\nimport {\n getDefaultNode,\n getParams,\n OptionsType,\n JSXRenderContext,\n getSlotFirst,\n isCommentVNode,\n} from '@tdesign/shared-utils';\nimport { hasOwn } from '@tdesign/common-js/utils/general';\n\n// 兼容处理插槽名称,同时支持驼峰命名和中划线命名,示例:value-display 和 valueDisplay\nfunction handleSlots(instance: ComponentInternalInstance, name: string, params: Record<string, any>) {\n // 2023-08 new Function 触发部分使用场景安全策略问题(Chrome插件/electron等)\n // // 每个 slots 需要单独的 h 函数 否则直接assign会重复把不同 slots 的 params 都注入\n // const finalParams = new Function('return ' + h.toString())();\n // if (params) {\n // Object.assign(finalParams, params);\n // }\n\n // 检查是否存在 驼峰命名 的插槽(过滤注释节点)\n let node = instance.slots[camelCase(name)]?.(params);\n if (node && node.filter((t) => !isCommentVNode(t)).length) return node;\n // 检查是否存在 中划线命名 的插槽\n node = instance.slots[kebabCase(name)]?.(params);\n if (node && node.filter((t) => !isCommentVNode(t)).length) return node;\n return null;\n}\n\n/**\n * 是否为空节点,需要过滤掉注释节点。注释节点也会被认为是空节点\n */\nfunction isEmptyNode(node: any) {\n if ([undefined, null, ''].includes(node)) return true;\n const innerNodes = node instanceof Array ? node : [node];\n const r = innerNodes.filter((node) => node?.type?.toString() !== 'Symbol(Comment)');\n return !r.length;\n}\n\n// TODO 可以把这里移动到 utils 中\n/**\n * 检查用户是否有主动传 prop\n * @param instance 组件实例\n * @param propName prop 名称\n * @returns boolean\n */\nfunction isPropExplicitlySet(instance: ComponentInternalInstance, propName: string) {\n const vProps = instance?.vnode.props || {};\n return hasOwn(vProps, camelCase(propName)) || hasOwn(vProps, kebabCase(propName));\n}\n\n/**\n/**\n * 通过 JSX 的方式渲染 TNode,props 和 插槽同时处理,也能处理默认值为 true 则渲染默认节点的情况\n * 优先级:用户注入的 props 值 > slot > 默认 props 值\n * 如果 props 值为 true ,则使用插槽渲染。如果也没有插槽的情况下,则使用 defaultNode 渲染\n * @example const renderTNodeJSX = useTNodeJSX()\n * @return () => {}\n * @param name 插槽和属性名称\n * @param options 值可能为默认渲染节点,也可能是默认渲染节点和参数的集合\n * @example renderTNodeJSX('closeBtn') 优先级 props function 大于 插槽\n * @example renderTNodeJSX('closeBtn', <close-icon />)。 当属性值为 true 时则渲染 <close-icon />\n * @example renderTNodeJSX('closeBtn', { defaultNode: <close-icon />, params })。 params 为渲染节点时所需的参数\n */\nexport const useTNodeJSX = () => {\n const instance = getCurrentInstance();\n return function (name: string, options?: OptionsType) {\n // 渲染节点时所需的参数\n const renderParams = getParams(options);\n // 默认渲染节点\n // TODO 这里需要讨论,这里的默认节点规则是什么呢? pp test:unit image-viewer pp test:unit Collapse\n const defaultNode = getDefaultNode(options);\n // 是否显示设置 slot 优先\n const isSlotFirst = getSlotFirst(options);\n // 插槽\n const renderSlot = instance.slots[camelCase(name)] || instance.slots[kebabCase(name)];\n\n if (isSlotFirst && renderSlot) {\n // 1. 如果显示设置了 slot 优先,并且存在 slot,那么优先使用 slot\n return handleSlots(instance, name, renderParams);\n } else {\n // 2. 否者按照 用户主动传入的 props 值 > slot > 默认 props 值\n // 2.1 处理主动传入的 prop\n if (isPropExplicitlySet(instance, name)) {\n // 2.1.1 如果有传,那么优先使用 prop 的值\n const propsNode = instance.props[camelCase(name)] || instance.props[kebabCase(name)];\n // 如果该属性的类型有多种且包含 Boolean 和 Slot 的情况下,处理 boolean casting true 的场景\n // https://vuejs.org/guide/components/props.html#boolean-casting\n const types = instance.type.props[name]?.type;\n if (types?.length > 1) {\n if (types.includes(Boolean) && types.includes(Function)) {\n if (propsNode === '' && !renderSlot) return defaultNode;\n }\n }\n // 2.1.2 如果 prop 的值为 false 或者 null,那么直接不渲染\n if (propsNode === false || propsNode === null) return;\n // 2.1.3 如果 prop 的值为 true,那么使用 slot 渲染\n if (propsNode === true) {\n return handleSlots(instance, name, renderParams) || defaultNode;\n }\n // 2.1.4 如果 prop 的值为函数,那么执行函数\n if (isFunction(propsNode)) return propsNode(h, renderParams);\n // 2.1.5 如果 prop 的值为 undefined、'',那么使用插槽渲染\n const isPropsEmpty = [undefined, ''].includes(propsNode as any);\n if (isPropsEmpty && renderSlot) {\n return handleSlots(instance, name, renderParams);\n }\n // 2.1.6 如果 prop 的值为其他值,那么直接返回\n return propsNode;\n }\n // 2.2 如果未主动传入 prop,那么渲染 slot,当然前提是存在 slot\n if (renderSlot) {\n return handleSlots(instance, name, renderParams);\n }\n // 2.3 如果未主动传入 prop,也没有 slot,那么就走 prop\n const propsNode = instance.props[camelCase(name)] || instance.props[kebabCase(name)];\n if (propsNode === false || propsNode === null) return;\n if (propsNode === true) {\n return defaultNode;\n }\n if (isFunction(propsNode)) return propsNode(h, renderParams);\n return propsNode;\n }\n };\n};\n\n/**\n * 在setup中,通过JSX的方式 TNode,props 和 插槽同时处理。与 renderTNodeJSX 区别在于属性值为 undefined 时会渲染默认节点\n * @example const renderTNodeJSXDefault = useTNodeDefault()\n * @return () => {}\n * @param name 插槽和属性名称\n * @example renderTNodeJSXDefault('closeBtn')\n * @example renderTNodeJSXDefault('closeBtn', <close-icon />) closeBtn 为空时,则兜底渲染 <close-icon />\n * @example renderTNodeJSXDefault('closeBtn', { defaultNode: <close-icon />, params }) 。params 为渲染节点时所需的参数\n */\nexport const useTNodeDefault = () => {\n const renderTNodeJSX = useTNodeJSX();\n return function (name: string, options?: VNode | JSXRenderContext) {\n const defaultNode = getDefaultNode(options);\n return renderTNodeJSX(name, options) || defaultNode;\n };\n};\n\n/**\n * 在setup中,用于处理相同名称的 TNode 渲染\n * @example const renderContent = useContent()\n * @return () => {}\n * @param name1 第一个名称,优先级高于 name2\n * @param name2 第二个名称\n * @param defaultNode 默认渲染内容:当 name1 和 name2 都为空时会启动默认内容渲染\n * @example renderContent('default', 'content')\n * @example renderContent('default', 'content', '我是默认内容')\n * @example renderContent('default', 'content', { defaultNode: '我是默认内容', params })\n */\nexport const useContent = () => {\n const renderTNodeJSX = useTNodeJSX();\n return function (name1: string, name2: string, options?: VNode | JSXRenderContext) {\n // assemble params && defaultNode\n const params = getParams(options);\n const defaultNode = getDefaultNode(options);\n\n const toParams = params ? { params } : undefined;\n\n const node1 = renderTNodeJSX(name1, toParams);\n const node2 = renderTNodeJSX(name2, toParams);\n\n const res = isEmptyNode(node1) ? node2 : node1;\n return isEmptyNode(res) ? defaultNode : res;\n };\n};\n\n/**\n * 过滤掉注释节点。\n *\n * @param nodes - VNode 数组\n * @returns 去除注释节点后的 VNode 数组。\n */\nexport const filterCommentNode = (nodes: VNode[]): VNode[] => {\n return nodes.filter((node) => !isCommentVNode(node));\n};\n\n/**\n * 从 VNode 中提取文本内容。\n *\n * @param VNodes - VNode 或字符串\n * @returns 提取的文本内容,多个会以空格分隔。\n */\nexport function getTextFromVNode(VNodes?: VNode[] | VNode | string): string {\n if (!VNodes) return '';\n\n // 如果是字符串,直接返回\n if (isString(VNodes)) return VNodes;\n\n // 如果是数组,递归获取文本内容并连接成字符串\n if (Array.isArray(VNodes)) {\n return filterCommentNode(VNodes)\n .map((node) => getTextFromVNode(node))\n .filter(Boolean)\n .join(' ');\n }\n\n // 注释节点返回空字符串\n if (isCommentVNode(VNodes)) return '';\n\n // 子节点是字符串,直接返回\n if (isString(VNodes.children)) {\n return VNodes.children;\n }\n\n // 递归获取子节点的文本内容并连接成字符串\n if (Array.isArray(VNodes.children)) {\n return filterCommentNode(VNodes.children as VNode[])\n .map((node) => getTextFromVNode(node))\n .filter(Boolean)\n .join(' ');\n }\n\n // 子节点是对象,获取对象的值并连接成字符串\n if (VNodes.children && isObject(VNodes.children)) {\n return Object.values(VNodes.children as Record<string, unknown>)\n .map((child) => {\n if (!child || isFunction(child)) return '';\n return getTextFromVNode(child as VNode[] | VNode | string);\n })\n .filter(Boolean)\n .join(' ');\n }\n\n return '';\n}\n","export function useCollapseAnimation() {\n const beforeEnter = (el: HTMLElement) => {\n el.dataset.oldPaddingTop = el.style.paddingTop;\n el.dataset.oldPaddingBottom = el.style.paddingBottom;\n\n el.style.height = '0';\n el.style.paddingTop = '0';\n el.style.paddingBottom = '0';\n };\n const enter = (el: HTMLElement) => {\n el.dataset.oldOverflow = el.style.overflow;\n el.style.height = `${el.scrollHeight}px`;\n el.style.paddingTop = el.dataset.oldPaddingTop;\n el.style.paddingBottom = el.dataset.oldPaddingBottom;\n el.style.overflow = 'hidden';\n };\n const afterEnter = (el: HTMLElement) => {\n el.style.height = '';\n el.style.overflow = el.dataset.oldOverflow;\n };\n const beforeLeave = (el: HTMLElement) => {\n el.dataset.oldPaddingTop = el.style.paddingTop;\n el.dataset.oldPaddingBottom = el.style.paddingBottom;\n el.dataset.oldOverflow = el.style.overflow;\n\n el.style.height = `${el.scrollHeight}px`;\n el.style.overflow = 'hidden';\n };\n const leave = (el: HTMLElement) => {\n if (el.scrollHeight !== 0) {\n el.style.height = '0';\n el.style.paddingTop = '0';\n el.style.paddingBottom = '0';\n }\n };\n const afterLeave = (el: HTMLElement) => {\n el.style.height = '';\n el.style.overflow = el.dataset.oldOverflow;\n el.style.paddingTop = el.dataset.oldPaddingTop;\n el.style.paddingBottom = el.dataset.oldPaddingBottom;\n };\n\n return {\n beforeEnter,\n enter,\n afterEnter,\n beforeLeave,\n leave,\n afterLeave,\n };\n}\n","import { computed } from 'vue';\n// TODO need refactor\nimport { useConfig } from '../../../components/config-provider/hooks/useConfig';\n\nexport function usePrefixClass(componentName?: string) {\n const { classPrefix } = useConfig('classPrefix');\n return computed(() => {\n return componentName ? `${classPrefix.value}-${componentName}` : classPrefix.value;\n });\n}\n\nexport { useConfig };\n","import { computed } from 'vue';\nimport { useConfig } from '../useConfig';\n\nexport function useCommonClassName() {\n const { classPrefix } = useConfig('classPrefix');\n\n return {\n classPrefix,\n SIZE: computed(() => ({\n small: `${classPrefix.value}-size-s`,\n medium: `${classPrefix.value}-size-m`,\n large: `${classPrefix.value}-size-l`,\n default: '',\n xs: `${classPrefix.value}-size-xs`,\n xl: `${classPrefix.value}-size-xl`,\n block: `${classPrefix.value}-size-full-width`,\n })),\n STATUS: computed(() => ({\n loading: `${classPrefix.value}-is-loading`,\n loadMore: `${classPrefix.value}-is-load-more`,\n disabled: `${classPrefix.value}-is-disabled`,\n focused: `${classPrefix.value}-is-focused`,\n success: `${classPrefix.value}-is-success`,\n error: `${classPrefix.value}-is-error`,\n warning: `${classPrefix.value}-is-warning`,\n selected: `${classPrefix.value}-is-selected`,\n active: `${classPrefix.value}-is-active`,\n checked: `${classPrefix.value}-is-checked`,\n current: `${classPrefix.value}-is-current`,\n hidden: `${classPrefix.value}-is-hidden`,\n visible: `${classPrefix.value}-is-visible`,\n expanded: `${classPrefix.value}-is-expanded`,\n indeterminate: `${classPrefix.value}-is-indeterminate`,\n })),\n };\n}\n\nexport type CommonClassNameType = ReturnType<typeof useCommonClassName>;\n","import { ref, Ref, getCurrentInstance } from 'vue';\nimport { kebabCase } from 'lodash-es';\nimport { ChangeHandler } from '../useVModel';\n\nexport function useDefaultValue<T, P extends any[]>(\n value: Ref<T>,\n defaultValue: T,\n onChange: ChangeHandler<T, P>,\n propsName: string,\n): [Ref<T>, ChangeHandler<T, P>] {\n const { emit, vnode } = getCurrentInstance();\n const internalValue: Ref<T> = ref();\n\n const vProps = vnode.props || {};\n const isVMP =\n Object.prototype.hasOwnProperty.call(vProps, propsName) ||\n Object.prototype.hasOwnProperty.call(vProps, kebabCase(propsName));\n\n if (isVMP) {\n return [\n value,\n (newValue, ...args) => {\n emit(`update:${propsName}`, newValue);\n onChange?.(newValue, ...args);\n },\n ];\n }\n\n internalValue.value = defaultValue;\n return [\n internalValue,\n (newValue, ...args) => {\n internalValue.value = newValue;\n onChange?.(newValue, ...args);\n },\n ];\n}\n","import { ref, provide, onUpdated } from 'vue';\n\nexport const TDisplayNoneElementRefresh = 't-display-none-element-refresh';\n\n// destroyOnClose=false 时,父元素为 display: none,此时的子元素无法获取到自身元素的任何宽度\n// 因此,需在父元素 display: none 发生变化时主动更新子元素\nexport function useDestroyOnClose() {\n const refresh = ref(0);\n provide(TDisplayNoneElementRefresh, refresh);\n onUpdated(() => {\n refresh.value += 1;\n });\n}\n","import { Ref, inject, computed, getCurrentInstance } from 'vue';\nimport { isArray, isBoolean } from 'lodash-es';\n// TODO: need refator\nimport { TdFormProps } from '../../../components/form/type';\n\nexport interface FormDisabledProvider {\n disabled: Ref<TdFormProps['disabled']>;\n}\n\nexport interface DisabledContext {\n beforeDisabled?: Ref<boolean>;\n afterDisabled?: Ref<boolean>;\n}\n\n/**\n * 用于实现组件全局禁用状态的hook\n * 优先级:(beforeDisabled) > Component.disabled > ComponentGroup.disabled(afterDisabled) > Form.disabled\n * @returns\n */\nexport function useDisabled(context?: DisabledContext) {\n const currentInstance = getCurrentInstance();\n const componentDisabled = computed(() => currentInstance.props.disabled as boolean | [boolean, boolean]);\n\n const formDisabled = inject<FormDisabledProvider>('formDisabled', Object.create(null));\n\n return computed(() => {\n if (isArray(componentDisabled.value) && componentDisabled.value.length === 2) return componentDisabled.value;\n\n if (isBoolean(context?.beforeDisabled?.value)) return context.beforeDisabled.value;\n // Component\n if (isBoolean(componentDisabled.value)) return componentDisabled.value;\n // ComponentGroup\n if (isBoolean(context?.afterDisabled?.value)) return context.afterDisabled.value;\n // Form\n if (isBoolean(formDisabled.disabled?.value)) return formDisabled.disabled.value;\n\n return false;\n });\n}\n","import { onUnmounted } from 'vue';\n\nconst traversalTabNavs = (tabNavs: HTMLCollection, fn: (tabNav: HTMLDivElement) => void) => {\n Array.from(tabNavs)\n .filter((node): node is HTMLDivElement => node instanceof HTMLDivElement && !!node.getAttribute('draggable'))\n .forEach(fn);\n};\n\nconst handleTarget = (target: EventTarget, tabNavs: HTMLCollection): HTMLDivElement | undefined => {\n let resultTarget: HTMLDivElement | undefined;\n\n traversalTabNavs(tabNavs, (itemNode) => {\n if (target instanceof Node && itemNode.contains(target)) {\n resultTarget = itemNode;\n }\n });\n\n return resultTarget;\n};\n\nexport function useDragSort(props: any) {\n let navsWrap: HTMLDivElement | null = null;\n\n // 获取当前正在拖动的tabNav节点\n let dragged: HTMLDivElement;\n const enterTargets: HTMLDivElement[] = [];\n\n const dragstart = (event: DragEvent) => {\n const target = event.target as HTMLDivElement;\n // const { target } = event;\n // 保存拖动元素的引用(ref.)\n dragged = target;\n // 使其半透明\n target.style.opacity = '0.5';\n\n // 指定允许的拖拽操作为 move,且兼容 Firefox(需要 setData)\n const dt = event.dataTransfer;\n if (dt) {\n dt.effectAllowed = 'copy';\n try {\n dt.setData('text/plain', '');\n } catch (e) {\n // 某些环境下可能抛错,忽略\n }\n }\n };\n\n const dragend = (event: DragEvent) => {\n // 重置透明度\n (event.target as HTMLDivElement).style.opacity = '';\n };\n /* 放置目标元素时触发事件 */\n const dragover = (event: DragEvent) => {\n if (!navsWrap) return;\n const target = handleTarget(event.target, navsWrap.children);\n const dt = event.dataTransfer;\n if (dt) {\n // 不可放置目标显示禁止状态\n dt.dropEffect = target?.draggable ? 'copy' : 'none';\n }\n if (target?.draggable) {\n // 阻止默认动作以启用drop\n event.preventDefault();\n }\n };\n // 当可拖动的元素进入可放置的目标时\n const dragenter = (event: DragEvent) => {\n // 高亮目标节点\n const target = handleTarget(event.target, navsWrap.children);\n if (target && target !== dragged && target.draggable) {\n const { firstChild } = target;\n if (firstChild instanceof HTMLElement) {\n const newStyle = { outline: '1px dashed #0052d9' };\n Object.assign(firstChild.style, newStyle);\n }\n // 进入的节点全部记录下来\n if (!enterTargets.includes(target)) {\n enterTargets.push(target);\n }\n }\n };\n // 当拖动元素离开可放置目标节点\n const dragleave = (event: DragEvent) => {\n const target = event.target as HTMLDivElement;\n // 重置其边框\n // const { target } = event;\n for (const enterTarget of enterTargets) {\n // 目标不在需要放入的节点内,则重置边框\n if (!enterTarget.contains(target)) {\n // 记录过的节点全部重置边框\n (enterTarget.firstChild as HTMLDivElement).style.outline = 'none';\n }\n }\n };\n const drop = (event: DragEvent) => {\n // 阻止默认动作(如打开一些元素的链接)\n event.preventDefault();\n\n traversalTabNavs(navsWrap.children, (tabNav) => {\n const firstChild = tabNav.firstChild as HTMLElement;\n if (firstChild) {\n firstChild.style.outline = 'none';\n }\n });\n\n // 将拖动的元素到所选择的放置目标节点中\n let target = handleTarget(event.target, navsWrap.children);\n if (target && target.parentNode !== dragged && target.draggable) {\n // 获取拖拽元素index\n const dragIndex = [].indexOf.call(navsWrap.children, dragged);\n // 获取放入元素index\n const targetIndex = [].indexOf.call(navsWrap.children, target);\n if (targetIndex > dragIndex) {\n target = navsWrap.children[targetIndex + 1] as HTMLDivElement | null;\n }\n\n // 当props.theme === \"normal\" 会多出一个指示条为第一个dom节点,所以需要减1\n const currentIndex = props.theme === 'card' ? dragIndex : dragIndex - 1;\n const endIndex = props.theme === 'card' ? targetIndex : targetIndex - 1;\n props.onDragSort?.({\n currentIndex,\n current: props.panels[currentIndex].value,\n targetIndex: endIndex,\n target: props.panels[endIndex].value,\n });\n }\n };\n function setNavsWrap(val: HTMLDivElement) {\n navsWrap = val;\n navsWrap.addEventListener('dragstart', dragstart, false);\n navsWrap.addEventListener('dragend', dragend, false);\n navsWrap.addEventListener('dragover', dragover, false);\n navsWrap.addEventListener('dragenter', dragenter, false);\n document.addEventListener('dragleave', dragleave, false);\n document.addEventListener('mousemove', dragleave, false);\n navsWrap.addEventListener('drop', drop, false);\n }\n\n onUnmounted(() => {\n if (navsWrap) {\n navsWrap.removeEventListener('dragstart', dragstart);\n navsWrap.removeEventListener('dragend', dragend);\n navsWrap.removeEventListener('dragover', dragover);\n navsWrap.removeEventListener('dragenter', dragenter);\n document.removeEventListener('dragleave', dragleave);\n document.removeEventListener('mousemove', dragleave);\n navsWrap.removeEventListener('drop', drop);\n }\n });\n return { setNavsWrap };\n}\n","import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';\nimport observe from '@tdesign/common-js/utils/observe';\n\nexport function useElementLazyRender(labelRef: Ref<HTMLElement>, lazyLoad: Ref<boolean>) {\n const ioObserver = ref<IntersectionObserver>();\n const showElement = ref(true);\n\n const handleLazyLoad = () => {\n if (!lazyLoad.value || !labelRef.value || ioObserver.value) return;\n showElement.value = false;\n const io = observe(\n labelRef.value,\n null,\n () => {\n showElement.value = true;\n },\n 10,\n );\n ioObserver.value = io;\n };\n\n onMounted(handleLazyLoad);\n\n lazyLoad.value && watch([lazyLoad, labelRef], handleLazyLoad);\n\n onBeforeUnmount(() => {\n if (!lazyLoad.value) return;\n ioObserver.value?.unobserve?.(labelRef.value);\n });\n\n return {\n showElement,\n };\n}\n\nexport default useElementLazyRender;\n","import { useConfig } from '../useConfig';\n// TODO need refactor\nimport { IconConfig } from '@tdesign/components/config-provider/type';\n\n// 从 globalConfig 获取 icon 配置用于覆盖组件内置 icon\n// TODO\nexport function useGlobalIcon(tdIcon: object): Record<string, any> {\n const { globalConfig } = useConfig('icon');\n\n const resultIcon: IconConfig = {};\n\n Object.keys(tdIcon).forEach((key: keyof typeof tdIcon) => {\n resultIcon[key] = globalConfig.value?.[key] || tdIcon[key];\n });\n\n return resultIcon;\n}\n\nexport default useGlobalIcon;\n","import { ComputedRef, ref, Ref, watch } from 'vue';\nimport { getFileUrlByFileRaw } from '@tdesign/common-js/upload/utils';\n\nexport function useImagePreviewUrl(imgUrl: Ref<string | File> | ComputedRef<string | File>) {\n const previewUrl = ref('');\n\n watch(\n [imgUrl],\n ([imgUrl], [preImgUrl]) => {\n if (preImgUrl === imgUrl) return;\n if (typeof imgUrl === 'string') {\n previewUrl.value = imgUrl;\n return;\n }\n getFileUrlByFileRaw(imgUrl).then((url) => {\n previewUrl.value = url;\n });\n },\n { immediate: true },\n );\n\n return { previewUrl };\n}\n","import { useConfig } from '../useConfig';\n// TODO need refactor\nimport { EAnimationType } from '../../../components/config-provider/utils/context';\n\nconst { expand, ripple, fade } = EAnimationType;\n\nexport function useKeepAnimation() {\n const { globalConfig } = useConfig('animation');\n\n const keepAnimation = (type: EAnimationType) => {\n const animationConfig = globalConfig.value;\n return animationConfig && !animationConfig.exclude?.includes(type) && animationConfig.include?.includes(type);\n };\n return {\n keepExpand: keepAnimation(expand),\n keepRipple: keepAnimation(ripple),\n keepFade: keepAnimation(fade),\n };\n}\n","import { ref, onMounted, computed, nextTick, Ref, UnwrapRef } from 'vue';\nimport observe from '@tdesign/common-js/utils/observe';\nimport { isServer } from '@tdesign/shared-utils';\n\nexport type UseLazyLoadParams = UnwrapRef<{\n type: 'lazy' | 'virtual';\n rowHeight?: number;\n bufferSize?: number;\n}>;\n\nexport function useLazyLoad(containerRef: Ref<HTMLElement>, childRef: Ref<HTMLElement>, params: UseLazyLoadParams) {\n const tRowHeight = computed(() => Math.max(params.rowHeight || 48, 48));\n const isInit = ref(false);\n const hasLazyLoadHolder = computed(() => params?.type === 'lazy' && !isInit.value);\n\n const requestAnimationFrame = (!isServer && window.requestAnimationFrame) || ((cb) => setTimeout(cb, 16.6));\n\n const init = () => {\n if (!isInit.value) {\n requestAnimationFrame(() => {\n isInit.value = true;\n });\n }\n };\n\n onMounted(() => {\n if (params?.type !== 'lazy') return;\n nextTick(() => {\n const bufferSize = Math.max(10, params.bufferSize || 10);\n const height = tRowHeight.value * bufferSize;\n observe(childRef.value, containerRef.value, init, height);\n });\n });\n\n return {\n hasLazyLoadHolder,\n tRowHeight,\n };\n}\n","import { onBeforeUnmount, onMounted } from 'vue';\n\n/**\n * 用于订阅Listener事件\n * @param updateSize\n */\nexport function useListener(type: string, listener: () => void): void {\n onMounted(() => {\n window.addEventListener(type, listener);\n });\n\n onBeforeUnmount(() => {\n window.removeEventListener(type, listener);\n });\n}\n\nexport function useResize(listener: () => void, observer?: HTMLElement) {\n useListener('resize', listener);\n\n let resizeObserver: ResizeObserver = null;\n\n onMounted(() => {\n if (!window.ResizeObserver || !observer) return;\n resizeObserver = new window.ResizeObserver(listener);\n resizeObserver.observe(observer);\n });\n\n onBeforeUnmount(() => {\n resizeObserver?.disconnect();\n });\n}\n","// https://github.dev/arco-design/arco-design-vue\nimport { onMounted, onBeforeUnmount, readonly, Ref, ref, watch } from 'vue';\nexport type PopupType = 'popup' | 'dialog' | 'message' | 'drawer';\n\nconst popupStackType = ['dialog', 'drawer'];\nconst POPUP_BASE_Z_INDEX = 1000;\nconst MESSAGE_BASE_Z_INDEX = 5000;\nconst Z_INDEX_STEP = 1;\n\nclass PopupManager {\n private popupStack = {\n popup: new Set<number>(),\n dialog: new Set<number>(),\n message: new Set<number>(),\n drawer: new Set<number>(),\n };\n\n private zIndexStack: number[] = [];\n\n private getNextZIndex = (type: PopupType) => {\n const current =\n type === 'message'\n ? Array.from(this.popupStack.message).pop() || MESSAGE_BASE_Z_INDEX\n : Array.from(this.popupStack.popup).pop() || POPUP_BASE_Z_INDEX;\n return current + Z_INDEX_STEP;\n };\n\n public add = (type: PopupType) => {\n const zIndex = this.getNextZIndex(type);\n this.popupStack[type].add(zIndex);\n if (popupStackType.includes(type)) {\n this.popupStack.popup.add(zIndex);\n }\n this.zIndexStack.push(zIndex);\n return zIndex;\n };\n\n public delete = (zIndex: number, type: PopupType) => {\n this.popupStack[type].delete(zIndex);\n if (popupStackType.includes(type)) {\n this.popupStack.popup.delete(zIndex);\n }\n const index = this.zIndexStack.indexOf(zIndex);\n if (index !== -1) {\n this.zIndexStack.splice(index, 1);\n }\n };\n\n // 最顶层的交互式弹窗(指Dialog和Drawer)\n public isTopInteractivePopup = (popupType: PopupType, zIndex: number) => {\n if (popupStackType.includes(popupType)) {\n const lastZIndex = this.zIndexStack[this.zIndexStack.length - 1];\n return zIndex === lastZIndex;\n }\n\n if (this.popupStack[popupType]?.size > 1) {\n return zIndex === Array.from(this.popupStack[popupType]).pop();\n }\n\n return true;\n };\n\n public getLastZIndex = () => {\n return this.zIndexStack[this.zIndexStack.length - 1];\n };\n}\n\nconst popupManager = new PopupManager();\n\nexport function usePopupManager(\n type: PopupType,\n {\n visible,\n runOnMounted,\n }: {\n visible?: Ref<boolean>;\n runOnMounted?: boolean;\n } = {},\n) {\n const zIndex = ref(0);\n\n const open = () => {\n zIndex.value = popupManager.add(type);\n };\n\n const close = () => {\n popupManager.delete(zIndex.value, type);\n };\n\n const isTopInteractivePopup = () => {\n if (popupStackType.includes(type)) {\n return popupManager.isTopInteractivePopup(type, zIndex.value);\n }\n return false;\n };\n\n watch(\n () => visible?.value,\n (visible) => {\n if (visible) {\n open();\n } else {\n close();\n }\n },\n {\n immediate: true,\n },\n );\n\n if (runOnMounted) {\n onMounted(() => {\n open();\n });\n\n onBeforeUnmount(() => {\n close();\n });\n }\n\n return {\n zIndex: readonly(zIndex),\n open,\n close,\n isTopInteractivePopup,\n };\n}\n","import { Ref, inject, computed, getCurrentInstance } from 'vue';\nimport { isBoolean } from 'lodash-es';\n// TODO need refactor\nimport { TdFormProps } from '../../../components/form/type';\n\nexport interface FormReadonlyProvider {\n readonly: Ref<TdFormProps['readonly']>;\n}\n\nexport interface ReadonlyContext {\n beforeReadonly?: Ref<boolean>;\n afterReadonly?: Ref<boolean>;\n}\n\n/**\n * 用于实现组件全局只读状态的hook\n * 优先级:(beforeReadonly) > Component.readonly > ComponentGroup.readonly(afterReadonly) > Form.readonly\n * @returns\n */\nexport function useReadonly(context?: ReadonlyContext) {\n const currentInstance = getCurrentInstance();\n const componentReadonly = computed(() => currentInstance.props.readonly as boolean);\n\n const formReadonly = inject<FormReadonlyProvider>('formReadonly', Object.create(null));\n\n return computed(() => {\n if (isBoolean(context?.beforeReadonly?.value)) return context.beforeReadonly.value;\n // Component\n if (isBoolean(componentReadonly?.value)) return componentReadonly.value;\n // ComponentGroup\n if (isBoolean(context?.afterReadonly?.value)) return context.afterReadonly.value;\n // Form\n if (isBoolean(formReadonly.readonly?.value)) return formReadonly.readonly.value;\n\n return false;\n });\n}\n","import { Ref, watch, onBeforeUnmount } from 'vue';\n\nexport function useResizeObserver(container: Ref<HTMLElement>, callback: (data: ResizeObserverEntry[]) => void) {\n if (typeof window === 'undefined') return;\n\n const isSupport = window && (window as Window & typeof globalThis).ResizeObserver;\n // unit tests do not need any warn console; too many warns influence focusing on more important log info\n if (!isSupport) return;\n\n let containerObserver: ResizeObserver = null;\n\n const cleanupObserver = () => {\n if (!containerObserver || !container.value) return;\n containerObserver.unobserve(container.value);\n containerObserver.disconnect();\n containerObserver = null;\n };\n\n const addObserver = (el: HTMLElement) => {\n containerObserver = new ResizeObserver(callback);\n containerObserver.observe(el);\n };\n\n // can not use container.value to judge\n container &&\n watch(\n container,\n (el) => {\n cleanupObserver();\n el && addObserver(el);\n },\n { immediate: true, flush: 'post' },\n );\n\n onBeforeUnmount(() => {\n cleanupObserver();\n });\n}\n","import { Styles } from '../common';\n\n/**\n * 用于为节点增加styles\n * @param el HTMLElement\n * @param styles Styles\n */\nfunction setStyle(el: HTMLElement, styles: Styles): void {\n if (!el) return;\n\n const keys = Object.keys(styles);\n keys.forEach((key) => {\n // @ts-ignore\n // eslint-disable-next-line no-param-reassign\n el.style[key] = styles[key];\n });\n // TODO: 这个怎么样\n // Object.assign(el.style, styles);\n}\n\nexport default setStyle;\n","import { ref, onMounted, onUnmounted, Ref } from 'vue';\nimport { useKeepAnimation } from '../useKeepAnimation';\nimport { usePrefixClass } from '../useConfig';\nimport setStyle from '@tdesign/common-js/utils/setStyle';\n\nconst period = 200;\n// 元素自身 background 等属性的过渡时长(与 less 变量 @anim-duration-base 对齐)\n// 用于在元素切换为 loading / disabled 时,让 ripple 背景过渡与元素自身同步收尾,避免视觉割裂\nconst elementTransitionPeriod = 200;\nconst noneRippleBg = 'rgba(0, 0, 0, 0)';\nconst defaultRippleColor = 'rgba(0, 0, 0, 0.35)';\n\n// 设置动画颜色 get the ripple animation color\nconst getRippleColor = (el: HTMLElement, fixedRippleColor?: string) => {\n // get fixed color from params\n if (fixedRippleColor) {\n return fixedRippleColor;\n }\n // get dynamic color from the dataset\n if (el?.dataset?.ripple) {\n const rippleColor = el.dataset.ripple;\n return rippleColor;\n }\n // use css variable, check if element is valid before calling getComputedStyle\n if (el instanceof Element) {\n const cssVariable = getComputedStyle(el).getPropertyValue('--ripple-color');\n if (cssVariable) {\n return cssVariable;\n }\n }\n return defaultRippleColor;\n};\n\n/**\n * 斜八角动画hooks 支持三种方式使用\n * 1. fixedRippleColor 固定色值 useRipple(ref,fixedRippleColor);\n * 2. dynamicColor 动态色值 data.ripple=\"rippleColor\" useRipple(ref)\n * 3. CSS variables(recommended) 配合节点对应CSS设置 --ripple-color useRipple(ref)\n * @param ref 需要使用斜八角动画的DOM\n * @param fixedRippleColor 斜八角的动画颜色\n */\nexport function useRipple(el: Ref<HTMLElement>, fixedRippleColor?: Ref<string>) {\n const rippleContainer = ref(null);\n const classPrefix = usePrefixClass();\n\n // 全局配置ripple\n const { keepRipple } = useKeepAnimation();\n\n // 为节点添加斜八角动画 add ripple to the DOM and set up the animation\n const handleAddRipple = (e: MouseEvent) => {\n const dom = el.value;\n // Early return if element is not valid or not attached to DOM\n if (!dom || !(dom instanceof Element)) return;\n\n const rippleColor = getRippleColor(dom, fixedRippleColor?.value);\n if (e.button !== 0 || !el || !keepRipple) return;\n\n if (\n dom.classList.contains(`${classPrefix.value}-is-active`) ||\n dom.classList.contains(`${classPrefix.value}-is-disabled`) ||\n dom.classList.contains(`${classPrefix.value}-is-checked`) ||\n dom.classList.contains(`${classPrefix.value}-is-loading`)\n )\n return;\n\n // Check again if element is still valid before calling getComputedStyle\n if (!(dom instanceof Element)) return;\n const elStyle = getComputedStyle(dom);\n\n const elBorder = parseInt(elStyle.borderWidth, 10);\n const border = elBorder > 0 ? elBorder : 0;\n const width = dom.offsetWidth;\n\n if (rippleContainer.value.parentNode === null) {\n setStyle(rippleContainer.value, {\n position: 'absolute',\n left: `${0 - border}px`,\n top: `${0 - border}px`,\n // 使用百分比尺寸,避免点击后元素尺寸(如 loading 状态新增 icon)发生变化时,\n // 容器无法跟随元素撑开,导致右侧露出元素自身的过渡背景从而出现\"左右割裂\"\n width: `calc(100% + ${border * 2}px)`,\n height: `calc(100% + ${border * 2}px)`,\n borderRadius: elStyle.borderRadius,\n pointerEvents: 'none',\n overflow: 'hidden',\n });\n dom.appendChild(rippleContainer.value);\n }\n // 新增一个ripple\n const ripple = document.createElement('div');\n\n setStyle(ripple, {\n marginTop: '0',\n marginLeft: '0',\n right: `${width}px`,\n width: `${width + 20}px`,\n height: '100%',\n transition: `transform ${period}ms cubic-bezier(.38, 0, .24, 1), background ${period * 2}ms linear`,\n transform: 'skewX(-8deg)',\n pointerEvents: 'none',\n position: 'absolute',\n zIndex: 0,\n backgroundColor: rippleColor,\n opacity: '0.9',\n });\n\n // fix zIndex:避免遮盖内部元素\n const elMap = new WeakMap();\n for (let n = dom.children.length, i = 0; i < n; ++i) {\n const child = dom.children[i];\n if ((child as HTMLElement).style.zIndex === '' && child !== rippleContainer.value) {\n (child as HTMLElement).style.zIndex = '1';\n elMap.set(child, true);\n }\n }\n\n // fix position\n let initPosition = dom.style.position;\n if (!initPosition && dom instanceof Element) {\n initPosition = getComputedStyle(dom).position;\n }\n if (initPosition === '' || initPosition === 'static') {\n // eslint-disable-next-line no-param-reassign\n dom.style.position = 'relative';\n }\n rippleContainer.value.insertBefore(ripple, rippleContainer.value.firstChild);\n\n setTimeout(() => {\n ripple.style.transform = `translateX(${width}px)`;\n }, 0);\n // 标记 ripple 是否已经清理过,避免多次重复触发\n let cleared = false;\n // 监听元素 class 变化的 observer,在 handleClearRipple 中会被断开\n let classChangeObserver: MutationObserver | null = null;\n // 清除动画节点 clear ripple container\n const handleClearRipple = () => {\n if (cleared) return;\n cleared = true;\n ripple.style.backgroundColor = noneRippleBg;\n\n if (classChangeObserver) {\n classChangeObserver.disconnect();\n classChangeObserver = null;\n }\n\n if (el.value) {\n el.value.removeEventListener('pointerup', handleClearRipple, false);\n el.value.removeEventListener('pointerleave', handleClearRipple, false);\n }\n\n setTimeout(() => {\n ripple.remove();\n if (rippleContainer.value.children.length === 0) rippleContainer.value.remove();\n }, period * 2 + 100);\n };\n\n // 监听元素 class 变化:当元素在 ripple 动画期间被切换为 loading / disabled / active / checked\n // 状态时(例如 click 回调中将按钮置为 loading),由于元素自身 background 的过渡时长\n // (@anim-duration-base) 远短于 ripple 的 background 过渡时长 (period * 2),会出现元素已经\n // 变成新背景色、而 ripple 还在缓慢淡出的视觉割裂。\n // 此处一旦检测到状态切换,立即将 ripple 的 background 过渡缩短到与元素自身一致,并触发清理,\n // 让两者同步结束褪色。\n if (typeof MutationObserver !== 'undefined') {\n classChangeObserver = new MutationObserver(() => {\n if (!dom || !(dom instanceof Element)) return;\n const cls = dom.classList;\n if (\n cls.contains(`${classPrefix.value}-is-loading`) ||\n cls.contains(`${classPrefix.value}-is-disabled`) ||\n cls.contains(`${classPrefix.value}-is-active`) ||\n cls.contains(`${classPrefix.value}-is-checked`)\n ) {\n // 将 background 过渡时长同步为元素自身的过渡时长,保留 transform 的原节奏\n ripple.style.transition = `transform ${period}ms cubic-bezier(.38, 0, .24, 1), background ${elementTransitionPeriod}ms linear`;\n handleClearRipple();\n }\n });\n classChangeObserver.observe(dom, { attributes: true, attributeFilter: ['class'] });\n }\n\n el.value.addEventListener('pointerup', handleClearRipple, false);\n el.value.addEventListener('pointerleave', handleClearRipple, false);\n };\n\n onMounted(() => {\n const dom = el?.value;\n if (!dom) return;\n\n rippleContainer.value = document.createElement('div');\n\n dom.addEventListener('pointerdown', handleAddRipple, false);\n });\n\n onUnmounted(() => {\n el?.value?.removeEventListener('pointerdown', handleAddRipple, false);\n });\n}\n","import { getAttach } from '@tdesign/shared-utils';\nimport { computed, Ref, onMounted, ref, watch } from 'vue';\n// TODO need refactor\nimport { AttachNode } from '../../../components/common';\nimport { isFunction } from 'lodash-es';\n\n/**\n * @description 返回挂载的节点, 用于teleport\n * @param attach 既可以是一个函数, 也可以是一个ref\n * @param triggerNode 既可以是一个函数, 也可以是一个ref\n */\nexport function useTeleport(\n attach: (() => AttachNode) | Ref<AttachNode>,\n triggerNode?: (() => any) | Ref<any>,\n): Ref<string | Element> {\n // 如果是函数, 则使用computed包裹 否则直接使用ref\n const to = isFunction(attach) ? computed(attach) : ref(attach);\n const innerTriggerNode = isFunction(triggerNode) ? computed(triggerNode) : ref(triggerNode);\n\n const element = ref<string | Element>();\n\n const getElement = () => {\n element.value = getAttach(to.value, innerTriggerNode.value);\n };\n\n onMounted(() => getElement());\n\n watch([to, innerTriggerNode], () => getElement());\n\n return element;\n}\n","import { ref, Ref, getCurrentInstance } from 'vue';\nimport { kebabCase } from 'lodash-es';\n\nexport type ChangeHandler<T, P extends any[]> = (value: T, ...args: P) => void;\n\nexport function useVModel<T, P extends any[]>(\n value: Ref<T>,\n modelValue: Ref<T>,\n defaultValue: T,\n onChange: ChangeHandler<T, P>,\n propName = 'value',\n): [Ref<T>, ChangeHandler<T, P>] {\n const { emit, vnode } = getCurrentInstance();\n const internalValue: Ref<T> = ref();\n\n const vProps = vnode.props || {};\n const isVM =\n Object.prototype.hasOwnProperty.call(vProps, 'modelValue') ||\n Object.prototype.hasOwnProperty.call(vProps, 'model-value');\n const isVMP =\n Object.prototype.hasOwnProperty.call(vProps, propName) ||\n Object.prototype.hasOwnProperty.call(vProps, kebabCase(propName));\n\n if (isVM) {\n return [\n modelValue,\n (newValue, ...args) => {\n emit('update:modelValue', newValue);\n onChange?.(newValue, ...args);\n },\n ];\n }\n\n if (isVMP) {\n return [\n value,\n (newValue, ...args) => {\n emit(`update:${propName}`, newValue);\n onChange?.(newValue, ...args);\n },\n ];\n }\n\n internalValue.value = defaultValue;\n return [\n internalValue,\n (newValue, ...args) => {\n internalValue.value = newValue;\n onChange?.(newValue, ...args);\n },\n ];\n}\n","/* eslint-disable */\n/**\n * 当前虚拟滚动存在的问题\n * 1. 反复拖动滚动条,底部会出现奇怪的高度\n * 2. 表格高度发生变化时,底部也会出现奇怪的高度\n * 3. 无法直接定位滚动到某个元素,进而无法实现 Select 组件直接滚动到选中项\n */\nimport { ref, toRefs, reactive, onMounted, computed, watch, nextTick } from 'vue';\n\n// 虚拟滚动Hooks的完整实现,只所以封装成hooks,主要是为了方便跟其他组件搭配使用,比如说表格或者下拉框\nexport function useVirtualScroll({\n data,\n container,\n fixedHeight = false,\n lineHeight = 30,\n bufferSize = 20,\n threshold = 100,\n}: {\n data: any;\n container: any;\n fixedHeight: boolean;\n lineHeight: number;\n bufferSize: number;\n threshold: number;\n}) {\n const state = reactive({\n visibleData: [],\n cachedHeight: [],\n cachedScrollY: [],\n });\n const isVirtual = computed(() => data.value.length > threshold);\n const updateId = ref(0);\n const trs = new Map(); // 当前展示的行元素和数据\n\n let visibleCount = 0; // 可见的节点数量\n let beforeScrollTop = 0; // 上一次的滚动条位置\n let index = 0; // 偏移行数\n let offset = 0; // 少于一行行高的偏移量\n let start = 0; // 第一条显示的行\n let last = 0; // 最后一条显示的行\n // let revising = false; // 是否正在修正滚动条\n\n const reset = () => {\n data.value.forEach((item: any, i: number) => {\n item.$index = i;\n if (fixedHeight) {\n state.cachedScrollY[i] = i * lineHeight;\n }\n });\n if (!fixedHeight) {\n state.cachedScrollY[data.value.length - 1] = undefined; // 初始化cachedScrollY数组的长度\n }\n };\n reset();\n\n // 计算虚拟滚动列表总高度,需要动态修正\n const scrollHeight = computed(() => {\n const { cachedHeight } = state;\n const { length } = cachedHeight;\n if (length) {\n const maxScrollY = cachedHeight.reduce((sum, v) => sum + v || lineHeight, 0); // 当前总高度\n if (cachedHeight.length === data.value.length) {\n return maxScrollY;\n }\n const average = maxScrollY / cachedHeight.length; // 平均高度\n return maxScrollY + (data.value.length - cachedHeight.length) * average; // 预估总高度\n }\n return isVirtual.value ? data.value.length * lineHeight : 0;\n });\n const translateY = computed(() => {\n const { visibleData } = state;\n const firstRow = visibleData[0];\n if (firstRow) {\n // 修复只有一个元素时存在偏移的问题\n return visibleData.length === 1 ? 0 : state.cachedScrollY[firstRow.$index];\n }\n return 0;\n });\n\n // 更新可视区域的节点数据\n const updateVisibleData = () => {\n last = Math.min(start + visibleCount + bufferSize * 2, data.value.length);\n state.visibleData = data.value.slice(start, last);\n };\n // 计算每行对应的scrollTop值\n const calculateScrollY = () => {\n const anchorDom = trs.get(index); // 获取锚点元素\n if (!anchorDom) {\n return; // 快速调整高度时,新的元素可能来不及加载,暂时跳过更新\n }\n const anchorDomHeight = anchorDom?.getBoundingClientRect()?.height; // 获取锚点元素的高\n state.cachedScrollY[index] = container.value.scrollTop - offset; // 锚点元素scrollY= 容器滚动高度 - 锚点元素的offset\n state.cachedHeight[index] = anchorDomHeight;\n\n for (let i = index + 1; i <= state.visibleData[state.visibleData.length - 1]?.$index; i++) {\n // 计算锚点后面的元素scrollY\n const tr = trs.get(i);\n const { height } = tr?.getBoundingClientRect() || {};\n state.cachedHeight[i] = height;\n const scrollY = state.cachedScrollY[i - 1] + state.cachedHeight[i - 1]; // 当前元素的y 是前一个元素的y+前一个元素高度\n // state.cachedScrollY[i] = scrollY;\n state.cachedScrollY.splice(i, 1, scrollY); // 兼容vue2的composition api\n }\n\n for (let i = index - 1; i >= state.visibleData[0]?.$index; i--) {\n const tr = trs.get(i);\n const { height } = tr?.getBoundingClientRect() || {};\n state.cachedHeight[i] = height;\n const scrollY = state.cachedScrollY[i + 1] - state.cachedHeight[i]; // 当前元素的y是下一个元素y - 当前元素高度\n // state.cachedScrollY[i] = scrollY;\n state.cachedScrollY.splice(i, 1, scrollY);\n }\n if (state.cachedScrollY[0] > 0) {\n // 修正滚动过快时,滚动到顶部时,滚动条多余的问题\n // revising = true;\n const distance = state.cachedScrollY[0]; // 第一个元素scrollY即为多出的量\n const length = Math.min(last, data.value.length);\n for (let i = 0; i < length; i++) {\n // state.cachedScrollY[i] -= distance;\n state.cachedScrollY.splice(i, 1, state.cachedScrollY[i] - distance);\n }\n\n const scrollTop = state.cachedScrollY[index - 1] ? state.cachedScrollY[index - 1] + offset : offset;\n container.value.scrollTop = scrollTop;\n beforeScrollTop = scrollTo