@veltra/compositions 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/use-component-props/index.js +3 -1
- package/dist/use-component-props/index.js.map +1 -1
- package/dist/use-config/index.js +5 -0
- package/dist/use-config/index.js.map +1 -1
- package/dist/use-fallback-props/index.js.map +1 -1
- package/dist/use-model/index.js.map +1 -1
- package/dist/use-pop/index.js +2 -0
- package/dist/use-pop/index.js.map +1 -1
- package/dist/use-resize-observer/index.js.map +1 -1
- package/dist/use-transition/use-css-transition.js +6 -0
- package/dist/use-transition/use-css-transition.js.map +1 -1
- package/dist/use-user-action/index.d.ts +32 -0
- package/dist/use-user-action/index.js +50 -0
- package/dist/use-user-action/index.js.map +1 -0
- package/dist/use-virtualizer/index.d.ts +69 -0
- package/dist/use-virtualizer/index.js +87 -0
- package/dist/use-virtualizer/index.js.map +1 -0
- package/package.json +5 -8
- package/src/index.ts +2 -2
- package/src/use-user-action/index.ts +56 -0
- package/src/use-virtualizer/index.ts +181 -0
- package/dist/use-lock/index.d.ts +0 -28
- package/dist/use-lock/index.js +0 -36
- package/dist/use-lock/index.js.map +0 -1
- package/dist/use-virtual/index.d.ts +0 -30
- package/dist/use-virtual/index.js +0 -67
- package/dist/use-virtual/index.js.map +0 -1
- package/src/use-lock/index.ts +0 -50
- package/src/use-virtual/index.ts +0 -130
package/dist/index.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { useDrag } from "./use-drag/index.js";
|
|
|
4
4
|
import { useFallbackProps, useFormFallbackProps } from "./use-fallback-props/index.js";
|
|
5
5
|
import { useFocus } from "./use-focus/index.js";
|
|
6
6
|
import { useFormComponent } from "./use-form-component/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { UserAction, UserActionResult, useUserAction } from "./use-user-action/index.js";
|
|
8
8
|
import { useModel } from "./use-model/index.js";
|
|
9
9
|
import { usePop } from "./use-pop/index.js";
|
|
10
10
|
import { RefElement, ResizeObserverReturn, useObserverCallback, useResizeObserver } from "./use-resize-observer/index.js";
|
|
11
11
|
import { useReactiveSize } from "./use-reactive-size/index.js";
|
|
12
12
|
import { useTransition } from "./use-transition/index.js";
|
|
13
|
-
import {
|
|
14
|
-
export { RefElement, ResizeObserverReturn,
|
|
13
|
+
import { EstimateSize, GetItemKey, UseVirtualizerOptions, UseVirtualizerReturned, VirtualAlign, VirtualItem, VirtualMeasurement, VirtualRange, VirtualScrollOptions, VirtualSnapshot, Virtualizer, VirtualizerOptions, useVirtualizer } from "./use-virtualizer/index.js";
|
|
14
|
+
export { EstimateSize, GetItemKey, RefElement, ResizeObserverReturn, UseVirtualizerOptions, UseVirtualizerReturned, UserAction, UserActionResult, VirtualAlign, VirtualItem, VirtualMeasurement, VirtualRange, VirtualScrollOptions, VirtualSnapshot, Virtualizer, VirtualizerOptions, setDocumentSize, useComponentProps, useConfig, useDrag, useFallbackProps, useFocus, useFormComponent, useFormFallbackProps, useModel, useObserverCallback, usePop, useReactiveSize, useResizeObserver, useTransition, useUserAction, useVirtualizer };
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,11 @@ import { useDrag } from "./use-drag/index.js";
|
|
|
4
4
|
import { useFallbackProps, useFormFallbackProps } from "./use-fallback-props/index.js";
|
|
5
5
|
import { useFocus } from "./use-focus/index.js";
|
|
6
6
|
import { useFormComponent } from "./use-form-component/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { useUserAction } from "./use-user-action/index.js";
|
|
8
8
|
import { useModel } from "./use-model/index.js";
|
|
9
9
|
import { usePop } from "./use-pop/index.js";
|
|
10
10
|
import { useObserverCallback, useResizeObserver } from "./use-resize-observer/index.js";
|
|
11
11
|
import { useReactiveSize } from "./use-reactive-size/index.js";
|
|
12
12
|
import { useTransition } from "./use-transition/index.js";
|
|
13
|
-
import {
|
|
14
|
-
export { setDocumentSize, useComponentProps, useConfig, useDrag, useFallbackProps, useFocus, useFormComponent, useFormFallbackProps, useModel, useObserverCallback, usePop, useReactiveSize, useResizeObserver, useTransition,
|
|
13
|
+
import { useVirtualizer } from "./use-virtualizer/index.js";
|
|
14
|
+
export { setDocumentSize, useComponentProps, useConfig, useDrag, useFallbackProps, useFocus, useFormComponent, useFormFallbackProps, useModel, useObserverCallback, usePop, useReactiveSize, useResizeObserver, useTransition, useUserAction, useVirtualizer };
|
|
@@ -10,7 +10,9 @@ function useComponentProps(props) {
|
|
|
10
10
|
return defineComponent({
|
|
11
11
|
name: "ComponentCommonProps",
|
|
12
12
|
inheritAttrs: false,
|
|
13
|
-
props: {
|
|
13
|
+
props: {
|
|
14
|
+
/** 渲染一个标准html5标签 */
|
|
15
|
+
tag: { type: String } },
|
|
14
16
|
setup(componentProps, { slots, attrs }) {
|
|
15
17
|
const isPropsRef = isRef(props);
|
|
16
18
|
const staticKeys = isPropsRef ? null : Object.keys(props);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-component-props/index.ts"],"sourcesContent":["import { extractNormalVNodes } from '@veltra/utils'\nimport { defineComponent, isRef, type MaybeRef, createVNode, cloneVNode, type Component } from 'vue'\n\n/**\n * 生成一个用于设置组件通用属性的组件\n * @param props 组件通用的属性\n * @returns\n */\nexport function useComponentProps<T extends Record<string, any>>(\n props: MaybeRef<T & Record<string, any>>\n): Component {\n return defineComponent({\n name: 'ComponentCommonProps',\n inheritAttrs: false,\n\n props: {\n /** 渲染一个标准html5标签 */\n tag: { type: String }\n },\n\n setup(componentProps, { slots, attrs }) {\n const isPropsRef = isRef(props)\n // 非 ref 时 keys 固定,缓存避免每次 render 重复计算\n const staticKeys = isPropsRef ? null : Object.keys(props)\n\n const mergeNodesProps = (commonProps: Record<string, any>) => {\n const nodes = extractNormalVNodes(slots.default?.() ?? [])\n if (!nodes?.length) return undefined\n\n const keys = staticKeys ?? Object.keys(commonProps)\n return nodes.map((node) => {\n const mergedProps: Record<string, any> = {}\n let count = 0\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i]!\n // node中已定义的属性优先\n if (node.props?.[key] !== undefined) continue\n mergedProps[key] = attrs[key] !== undefined ? attrs[key] : commonProps[key]\n count++\n }\n return count > 0 ? cloneVNode(node, mergedProps) : node\n })\n }\n\n return () => {\n const _props = isPropsRef ? props.value : props\n const nodes = mergeNodesProps(_props)\n\n if (componentProps.tag) {\n if (!nodes) return undefined\n const tagProps = Object.keys(attrs).reduce<Record<string, any>>((acc, cur) => {\n if (!(cur in _props)) {\n acc[cur] = attrs[cur]\n }\n return acc\n }, {})\n return createVNode(componentProps.tag, tagProps, nodes)\n }\n return nodes\n }\n }\n })\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,kBACd,OACW;AACX,QAAO,gBAAgB;EACrB,MAAM;EACN,cAAc;EAEd,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-component-props/index.ts"],"sourcesContent":["import { extractNormalVNodes } from '@veltra/utils'\nimport { defineComponent, isRef, type MaybeRef, createVNode, cloneVNode, type Component } from 'vue'\n\n/**\n * 生成一个用于设置组件通用属性的组件\n * @param props 组件通用的属性\n * @returns\n */\nexport function useComponentProps<T extends Record<string, any>>(\n props: MaybeRef<T & Record<string, any>>\n): Component {\n return defineComponent({\n name: 'ComponentCommonProps',\n inheritAttrs: false,\n\n props: {\n /** 渲染一个标准html5标签 */\n tag: { type: String }\n },\n\n setup(componentProps, { slots, attrs }) {\n const isPropsRef = isRef(props)\n // 非 ref 时 keys 固定,缓存避免每次 render 重复计算\n const staticKeys = isPropsRef ? null : Object.keys(props)\n\n const mergeNodesProps = (commonProps: Record<string, any>) => {\n const nodes = extractNormalVNodes(slots.default?.() ?? [])\n if (!nodes?.length) return undefined\n\n const keys = staticKeys ?? Object.keys(commonProps)\n return nodes.map((node) => {\n const mergedProps: Record<string, any> = {}\n let count = 0\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i]!\n // node中已定义的属性优先\n if (node.props?.[key] !== undefined) continue\n mergedProps[key] = attrs[key] !== undefined ? attrs[key] : commonProps[key]\n count++\n }\n return count > 0 ? cloneVNode(node, mergedProps) : node\n })\n }\n\n return () => {\n const _props = isPropsRef ? props.value : props\n const nodes = mergeNodesProps(_props)\n\n if (componentProps.tag) {\n if (!nodes) return undefined\n const tagProps = Object.keys(attrs).reduce<Record<string, any>>((acc, cur) => {\n if (!(cur in _props)) {\n acc[cur] = attrs[cur]\n }\n return acc\n }, {})\n return createVNode(componentProps.tag, tagProps, nodes)\n }\n return nodes\n }\n }\n })\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,kBACd,OACW;AACX,QAAO,gBAAgB;EACrB,MAAM;EACN,cAAc;EAEd,OAAO;;AAEL,KAAK,EAAE,MAAM,QAAQ,EACtB;EAED,MAAM,gBAAgB,EAAE,OAAO,SAAS;GACtC,MAAM,aAAa,MAAM,MAAM;GAE/B,MAAM,aAAa,aAAa,OAAO,OAAO,KAAK,MAAM;GAEzD,MAAM,mBAAmB,gBAAqC;IAC5D,MAAM,QAAQ,oBAAoB,MAAM,WAAW,IAAI,EAAE,CAAC;AAC1D,QAAI,CAAC,OAAO,OAAQ,QAAO,KAAA;IAE3B,MAAM,OAAO,cAAc,OAAO,KAAK,YAAY;AACnD,WAAO,MAAM,KAAK,SAAS;KACzB,MAAM,cAAmC,EAAE;KAC3C,IAAI,QAAQ;AACZ,UAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;MACpC,MAAM,MAAM,KAAK;AAEjB,UAAI,KAAK,QAAQ,SAAS,KAAA,EAAW;AACrC,kBAAY,OAAO,MAAM,SAAS,KAAA,IAAY,MAAM,OAAO,YAAY;AACvE;;AAEF,YAAO,QAAQ,IAAI,WAAW,MAAM,YAAY,GAAG;MACnD;;AAGJ,gBAAa;IACX,MAAM,SAAS,aAAa,MAAM,QAAQ;IAC1C,MAAM,QAAQ,gBAAgB,OAAO;AAErC,QAAI,eAAe,KAAK;AACtB,SAAI,CAAC,MAAO,QAAO,KAAA;KACnB,MAAM,WAAW,OAAO,KAAK,MAAM,CAAC,QAA6B,KAAK,QAAQ;AAC5E,UAAI,EAAE,OAAO,QACX,KAAI,OAAO,MAAM;AAEnB,aAAO;QACN,EAAE,CAAC;AACN,YAAO,YAAY,eAAe,KAAK,UAAU,MAAM;;AAEzD,WAAO;;;EAGZ,CAAC"}
|
package/dist/use-config/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-config/index.ts"],"sourcesContent":["import { isObj } from '@cat-kit/core'\nimport type { ComponentSize } from '@veltra/utils'\nimport { reactive, readonly, watch } from 'vue'\n\ninterface State {\n /** 是否开启动画,机器老可以关闭动画来获得性能 */\n animation: boolean\n /** 组件尺寸大小 */\n size: ComponentSize\n /** 表单 */\n form: {\n /** 标签宽度 */\n labelWidth?: number | number\n }\n paginator: { pageSize: number; pageSizeOptions: number[] }\n}\n\nconst state = reactive<State>({\n animation: true,\n size: 'default',\n form: { labelWidth: 80 },\n paginator: { pageSize: 40, pageSizeOptions: [40, 100, 200, 500, 1000] }\n})\n\nexport function setDocumentSize(size: ComponentSize, oldSize?: ComponentSize): void {\n if (typeof document === 'undefined') return\n if (oldSize) {\n document.documentElement.classList.remove(oldSize)\n }\n document.documentElement.classList.add(size)\n}\n\nlet stopDocumentSizeSync: (() => void) | null = null\n\nfunction ensureDocumentSizeSync(): void {\n if (stopDocumentSizeSync) return\n if (typeof document === 'undefined') return\n\n stopDocumentSizeSync = watch(\n () => state.size,\n (size, oldSize) => setDocumentSize(size, oldSize)\n )\n}\n\nfunction deepSet(original: Record<string, any>, extend: Record<string, any>) {\n Object.keys(extend).forEach((key) => {\n const val = original[key]\n const targetVal = extend[key]\n if (isObj(val)) {\n if (isObj(targetVal)) {\n deepSet(val, targetVal)\n } else {\n console.warn(`extend['${key}']应该是一个对象`)\n }\n } else {\n original[key] = targetVal\n }\n })\n}\n\nexport function useConfig(): {\n config: Readonly<State>\n setConfig: (conf: Partial<State>) => void\n} {\n ensureDocumentSizeSync()\n return {\n /** 全局配置 */\n config: readonly(state) as Readonly<State>,\n /**\n * 设置全局配置项\n * @param conf\n */\n setConfig(conf: Partial<State>) {\n deepSet(state, conf)\n }\n }\n}\n"],"mappings":";;;AAiBA,MAAM,QAAQ,SAAgB;CAC5B,WAAW;CACX,MAAM;CACN,MAAM,EAAE,YAAY,IAAI;CACxB,WAAW;EAAE,UAAU;EAAI,iBAAiB;GAAC;GAAI;GAAK;GAAK;GAAK;GAAK;EAAE;CACxE,CAAC;AAEF,SAAgB,gBAAgB,MAAqB,SAA+B;AAClF,KAAI,OAAO,aAAa,YAAa;AACrC,KAAI,QACF,UAAS,gBAAgB,UAAU,OAAO,QAAQ;AAEpD,UAAS,gBAAgB,UAAU,IAAI,KAAK;;AAG9C,IAAI,uBAA4C;AAEhD,SAAS,yBAA+B;AACtC,KAAI,qBAAsB;AAC1B,KAAI,OAAO,aAAa,YAAa;AAErC,wBAAuB,YACf,MAAM,OACX,MAAM,YAAY,gBAAgB,MAAM,QAAQ,CAClD;;AAGH,SAAS,QAAQ,UAA+B,QAA6B;AAC3E,QAAO,KAAK,OAAO,CAAC,SAAS,QAAQ;EACnC,MAAM,MAAM,SAAS;EACrB,MAAM,YAAY,OAAO;AACzB,MAAI,MAAM,IAAI,CACZ,KAAI,MAAM,UAAU,CAClB,SAAQ,KAAK,UAAU;MAEvB,SAAQ,KAAK,WAAW,IAAI,WAAW;MAGzC,UAAS,OAAO;GAElB;;AAGJ,SAAgB,YAGd;AACA,yBAAwB;AACxB,QAAO
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-config/index.ts"],"sourcesContent":["import { isObj } from '@cat-kit/core'\nimport type { ComponentSize } from '@veltra/utils'\nimport { reactive, readonly, watch } from 'vue'\n\ninterface State {\n /** 是否开启动画,机器老可以关闭动画来获得性能 */\n animation: boolean\n /** 组件尺寸大小 */\n size: ComponentSize\n /** 表单 */\n form: {\n /** 标签宽度 */\n labelWidth?: number | number\n }\n paginator: { pageSize: number; pageSizeOptions: number[] }\n}\n\nconst state = reactive<State>({\n animation: true,\n size: 'default',\n form: { labelWidth: 80 },\n paginator: { pageSize: 40, pageSizeOptions: [40, 100, 200, 500, 1000] }\n})\n\nexport function setDocumentSize(size: ComponentSize, oldSize?: ComponentSize): void {\n if (typeof document === 'undefined') return\n if (oldSize) {\n document.documentElement.classList.remove(oldSize)\n }\n document.documentElement.classList.add(size)\n}\n\nlet stopDocumentSizeSync: (() => void) | null = null\n\nfunction ensureDocumentSizeSync(): void {\n if (stopDocumentSizeSync) return\n if (typeof document === 'undefined') return\n\n stopDocumentSizeSync = watch(\n () => state.size,\n (size, oldSize) => setDocumentSize(size, oldSize)\n )\n}\n\nfunction deepSet(original: Record<string, any>, extend: Record<string, any>) {\n Object.keys(extend).forEach((key) => {\n const val = original[key]\n const targetVal = extend[key]\n if (isObj(val)) {\n if (isObj(targetVal)) {\n deepSet(val, targetVal)\n } else {\n console.warn(`extend['${key}']应该是一个对象`)\n }\n } else {\n original[key] = targetVal\n }\n })\n}\n\nexport function useConfig(): {\n config: Readonly<State>\n setConfig: (conf: Partial<State>) => void\n} {\n ensureDocumentSizeSync()\n return {\n /** 全局配置 */\n config: readonly(state) as Readonly<State>,\n /**\n * 设置全局配置项\n * @param conf\n */\n setConfig(conf: Partial<State>) {\n deepSet(state, conf)\n }\n }\n}\n"],"mappings":";;;AAiBA,MAAM,QAAQ,SAAgB;CAC5B,WAAW;CACX,MAAM;CACN,MAAM,EAAE,YAAY,IAAI;CACxB,WAAW;EAAE,UAAU;EAAI,iBAAiB;GAAC;GAAI;GAAK;GAAK;GAAK;GAAK;EAAE;CACxE,CAAC;AAEF,SAAgB,gBAAgB,MAAqB,SAA+B;AAClF,KAAI,OAAO,aAAa,YAAa;AACrC,KAAI,QACF,UAAS,gBAAgB,UAAU,OAAO,QAAQ;AAEpD,UAAS,gBAAgB,UAAU,IAAI,KAAK;;AAG9C,IAAI,uBAA4C;AAEhD,SAAS,yBAA+B;AACtC,KAAI,qBAAsB;AAC1B,KAAI,OAAO,aAAa,YAAa;AAErC,wBAAuB,YACf,MAAM,OACX,MAAM,YAAY,gBAAgB,MAAM,QAAQ,CAClD;;AAGH,SAAS,QAAQ,UAA+B,QAA6B;AAC3E,QAAO,KAAK,OAAO,CAAC,SAAS,QAAQ;EACnC,MAAM,MAAM,SAAS;EACrB,MAAM,YAAY,OAAO;AACzB,MAAI,MAAM,IAAI,CACZ,KAAI,MAAM,UAAU,CAClB,SAAQ,KAAK,UAAU;MAEvB,SAAQ,KAAK,WAAW,IAAI,WAAW;MAGzC,UAAS,OAAO;GAElB;;AAGJ,SAAgB,YAGd;AACA,yBAAwB;AACxB,QAAO;;EAEL,QAAQ,SAAS,MAAM;;;;;EAKvB,UAAU,MAAsB;AAC9B,WAAQ,OAAO,KAAK;;EAEvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-fallback-props/index.ts"],"sourcesContent":["import type { ComponentSize } from '@veltra/utils'\nimport { computed, type ComputedRef } from 'vue'\n\nimport { useConfig } from '../use-config'\n\n/**\n * 使用回滚属性,用于控制多级属性的使用优先级,如果多级属性中不存在该值,则使用全局配置中的属性,如再不存在则为undefined\n * @param propsList 属性列表,最右边的属性优先级最高\n * @param fallbackProps 要回滚的属性和默认值\n */\nexport function useFallbackProps<\n F extends Record<string, any>,\n R extends {\n [key in keyof F]: ComputedRef<F[key]>\n }\n>(propsList: Record<string, any>[], fallbackProps: F): R {\n const { config } = useConfig()\n\n let result = {} as R\n\n for (const key in fallbackProps) {\n if (fallbackProps.hasOwnProperty(key)) {\n const defaultValue = fallbackProps[key]\n const ref = computed<any>(() => {\n for (let i = propsList.length - 1; i > -1; --i) {\n const props = propsList[i]!\n\n if (props[key] !== undefined) {\n return props[key]\n }\n }\n\n return config[key as string] ?? defaultValue\n })\n\n result[key as keyof F] = ref as R[keyof F]\n }\n }\n\n return result\n}\n\ntype FormFallbackProps = { size: ComponentSize; disabled: boolean; readonly: boolean }\n\n/**\n * 表单组件的回滚属性\n * @param propsList props列表\n * @returns\n */\nexport function useFormFallbackProps(propsList: Record<string, any>[]): {\n [key in keyof FormFallbackProps]: ComputedRef<FormFallbackProps[key]>\n}\n\n/**\n * 表单组件的回滚属性\n * @param propsList props列表\n * @param fallbackProps 回滚属性,可以只指定部分表单属性\n * @returns\n */\nexport function useFormFallbackProps<F extends Partial<FormFallbackProps>>(\n propsList: Record<string, any>[],\n fallbackProps: F\n): {\n [key in keyof F]: key extends keyof FormFallbackProps\n ? ComputedRef<FormFallbackProps[key]>\n : never\n}\nexport function useFormFallbackProps(\n propsList: Record<string, any>[],\n fallbackProps?: Record<string, any>\n): any {\n if (!fallbackProps) {\n fallbackProps = { size: 'default', disabled: false, readonly: false }\n }\n return useFallbackProps(propsList, fallbackProps)\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,iBAKd,WAAkC,eAAqB;CACvD,MAAM,EAAE,WAAW,WAAW;CAE9B,IAAI,SAAS,EAAE;AAEf,MAAK,MAAM,OAAO,cAChB,KAAI,cAAc,eAAe,IAAI,EAAE;EACrC,MAAM,eAAe,cAAc;AAanC,SAAO,OAZK,eAAoB;AAC9B,QAAK,IAAI,IAAI,UAAU,SAAS,GAAG,IAAI,IAAI,EAAE,GAAG;IAC9C,MAAM,QAAQ,UAAU;AAExB,QAAI,MAAM,SAAS,KAAA,EACjB,QAAO,MAAM;;AAIjB,UAAO,OAAO,QAAkB;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-fallback-props/index.ts"],"sourcesContent":["import type { ComponentSize } from '@veltra/utils'\nimport { computed, type ComputedRef } from 'vue'\n\nimport { useConfig } from '../use-config'\n\n/**\n * 使用回滚属性,用于控制多级属性的使用优先级,如果多级属性中不存在该值,则使用全局配置中的属性,如再不存在则为undefined\n * @param propsList 属性列表,最右边的属性优先级最高\n * @param fallbackProps 要回滚的属性和默认值\n */\nexport function useFallbackProps<\n F extends Record<string, any>,\n R extends {\n [key in keyof F]: ComputedRef<F[key]>\n }\n>(propsList: Record<string, any>[], fallbackProps: F): R {\n const { config } = useConfig()\n\n let result = {} as R\n\n for (const key in fallbackProps) {\n if (fallbackProps.hasOwnProperty(key)) {\n const defaultValue = fallbackProps[key]\n const ref = computed<any>(() => {\n for (let i = propsList.length - 1; i > -1; --i) {\n const props = propsList[i]!\n\n if (props[key] !== undefined) {\n return props[key]\n }\n }\n\n return config[key as string] ?? defaultValue\n })\n\n result[key as keyof F] = ref as R[keyof F]\n }\n }\n\n return result\n}\n\ntype FormFallbackProps = { size: ComponentSize; disabled: boolean; readonly: boolean }\n\n/**\n * 表单组件的回滚属性\n * @param propsList props列表\n * @returns\n */\nexport function useFormFallbackProps(propsList: Record<string, any>[]): {\n [key in keyof FormFallbackProps]: ComputedRef<FormFallbackProps[key]>\n}\n\n/**\n * 表单组件的回滚属性\n * @param propsList props列表\n * @param fallbackProps 回滚属性,可以只指定部分表单属性\n * @returns\n */\nexport function useFormFallbackProps<F extends Partial<FormFallbackProps>>(\n propsList: Record<string, any>[],\n fallbackProps: F\n): {\n [key in keyof F]: key extends keyof FormFallbackProps\n ? ComputedRef<FormFallbackProps[key]>\n : never\n}\nexport function useFormFallbackProps(\n propsList: Record<string, any>[],\n fallbackProps?: Record<string, any>\n): any {\n if (!fallbackProps) {\n fallbackProps = { size: 'default', disabled: false, readonly: false }\n }\n return useFallbackProps(propsList, fallbackProps)\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,iBAKd,WAAkC,eAAqB;CACvD,MAAM,EAAE,WAAW,WAAW;CAE9B,IAAI,SAAS,EAAE;AAEf,MAAK,MAAM,OAAO,cAChB,KAAI,cAAc,eAAe,IAAI,EAAE;EACrC,MAAM,eAAe,cAAc;AAanC,SAAO,OAZK,eAAoB;AAC9B,QAAK,IAAI,IAAI,UAAU,SAAS,GAAG,IAAI,IAAI,EAAE,GAAG;IAC9C,MAAM,QAAQ,UAAU;AAExB,QAAI,MAAM,SAAS,KAAA,EACjB,QAAO,MAAM;;AAIjB,UAAO,OAAO,QAAkB;IAGN;;AAIhC,QAAO;;AA4BT,SAAgB,qBACd,WACA,eACK;AACL,KAAI,CAAC,cACH,iBAAgB;EAAE,MAAM;EAAW,UAAU;EAAO,UAAU;EAAO;AAEvE,QAAO,iBAAiB,WAAW,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-model/index.ts"],"sourcesContent":["import { type Ref, ref, watch, shallowRef } from 'vue'\n\ninterface ModelOptions<Props extends Record<string, unknown>, Name extends keyof Props> {\n /** 组件定义的属性 */\n props: Props\n /** 属性名称 */\n propName?: Name\n /** 事件触发函数 */\n emit: (...args: any[]) => void\n /** 是否为本地模式, 默认为true, 本地模式允许组件不受控来触发视图更新 */\n local?: boolean | (() => boolean)\n /** 默认值 */\n defaultValue?: Props[Name]\n /**\n * 是否浅层响应\n * @default false\n */\n shallow?: boolean\n}\n\n/**\n * 返回一个基于提供的选项的响应式模型值。\n * 该方法在将来可能会被替代, 目前使用是为了类型提示可用\n * 如果 local 选项为true, 模型值将是响应式的,并与属性值同步。\n * 如果 local 选项为 false,则模型值将是一个代理对象,具有 getter 和 setter。当值发生更改时,它会触发一个更新事件。\n * @param options - 选项\n * @returns - 一个模型值\n */\n\nexport function useModel<\n Props extends Record<string, any>,\n Name extends keyof Props = 'modelValue'\n>(\n options: ModelOptions<Props, Name>\n): Ref<Props[Name] | undefined> | { __v_isRef: boolean; value: Props[Name] } {\n const {\n props,\n propName = 'modelValue',\n emit,\n local = true,\n defaultValue,\n shallow = false\n } = options\n\n if (local) {\n const _default = props[propName] ?? defaultValue\n const r = shallow ? shallowRef : ref\n\n // 创建一个响应式对象\n const _value = r(_default)\n\n // 监听属性的变更\n watch(\n () => props[propName],\n (v) => {\n _value.value = v\n }\n )\n\n const getLocal = () => {\n return typeof local === 'function' ? local() : local\n }\n\n const value = {\n __v_isRef: true,\n get value() {\n return _value.value\n },\n set value(v) {\n if (v !== _value.value) {\n emit(`update:${propName as string}`, v)\n }\n if (getLocal()) {\n _value.value = v\n }\n }\n }\n\n return value\n }\n\n // 创建一个拥有getter和setter的对象\n const value = {\n __v_isRef: true,\n\n get value(): Props[Name] {\n return (props[propName] ?? defaultValue) as Props[Name]\n },\n\n set value(v: Props[Name]) {\n emit(`update:${propName as string}`, v)\n }\n }\n\n return value\n}\n"],"mappings":";;;;;;;;;;AA6BA,SAAgB,SAId,SAC2E;CAC3E,MAAM,EACJ,OACA,WAAW,cACX,MACA,QAAQ,MACR,cACA,UAAU,UACR;AAEJ,KAAI,OAAO;EACT,MAAM,WAAW,MAAM,aAAa;EAIpC,MAAM,UAHI,UAAU,aAAa,KAGhB,SAAS;AAG1B,cACQ,MAAM,YACX,MAAM;AACL,UAAO,QAAQ;IAElB;EAED,MAAM,iBAAiB;AACrB,UAAO,OAAO,UAAU,aAAa,OAAO,GAAG;;AAkBjD,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-model/index.ts"],"sourcesContent":["import { type Ref, ref, watch, shallowRef } from 'vue'\n\ninterface ModelOptions<Props extends Record<string, unknown>, Name extends keyof Props> {\n /** 组件定义的属性 */\n props: Props\n /** 属性名称 */\n propName?: Name\n /** 事件触发函数 */\n emit: (...args: any[]) => void\n /** 是否为本地模式, 默认为true, 本地模式允许组件不受控来触发视图更新 */\n local?: boolean | (() => boolean)\n /** 默认值 */\n defaultValue?: Props[Name]\n /**\n * 是否浅层响应\n * @default false\n */\n shallow?: boolean\n}\n\n/**\n * 返回一个基于提供的选项的响应式模型值。\n * 该方法在将来可能会被替代, 目前使用是为了类型提示可用\n * 如果 local 选项为true, 模型值将是响应式的,并与属性值同步。\n * 如果 local 选项为 false,则模型值将是一个代理对象,具有 getter 和 setter。当值发生更改时,它会触发一个更新事件。\n * @param options - 选项\n * @returns - 一个模型值\n */\n\nexport function useModel<\n Props extends Record<string, any>,\n Name extends keyof Props = 'modelValue'\n>(\n options: ModelOptions<Props, Name>\n): Ref<Props[Name] | undefined> | { __v_isRef: boolean; value: Props[Name] } {\n const {\n props,\n propName = 'modelValue',\n emit,\n local = true,\n defaultValue,\n shallow = false\n } = options\n\n if (local) {\n const _default = props[propName] ?? defaultValue\n const r = shallow ? shallowRef : ref\n\n // 创建一个响应式对象\n const _value = r(_default)\n\n // 监听属性的变更\n watch(\n () => props[propName],\n (v) => {\n _value.value = v\n }\n )\n\n const getLocal = () => {\n return typeof local === 'function' ? local() : local\n }\n\n const value = {\n __v_isRef: true,\n get value() {\n return _value.value\n },\n set value(v) {\n if (v !== _value.value) {\n emit(`update:${propName as string}`, v)\n }\n if (getLocal()) {\n _value.value = v\n }\n }\n }\n\n return value\n }\n\n // 创建一个拥有getter和setter的对象\n const value = {\n __v_isRef: true,\n\n get value(): Props[Name] {\n return (props[propName] ?? defaultValue) as Props[Name]\n },\n\n set value(v: Props[Name]) {\n emit(`update:${propName as string}`, v)\n }\n }\n\n return value\n}\n"],"mappings":";;;;;;;;;;AA6BA,SAAgB,SAId,SAC2E;CAC3E,MAAM,EACJ,OACA,WAAW,cACX,MACA,QAAQ,MACR,cACA,UAAU,UACR;AAEJ,KAAI,OAAO;EACT,MAAM,WAAW,MAAM,aAAa;EAIpC,MAAM,UAHI,UAAU,aAAa,KAGhB,SAAS;AAG1B,cACQ,MAAM,YACX,MAAM;AACL,UAAO,QAAQ;IAElB;EAED,MAAM,iBAAiB;AACrB,UAAO,OAAO,UAAU,aAAa,OAAO,GAAG;;AAkBjD,SAAO;GAdL,WAAW;GACX,IAAI,QAAQ;AACV,WAAO,OAAO;;GAEhB,IAAI,MAAM,GAAG;AACX,QAAI,MAAM,OAAO,MACf,MAAK,UAAU,YAAsB,EAAE;AAEzC,QAAI,UAAU,CACZ,QAAO,QAAQ;;GAKT;;AAgBd,QAAO;EAXL,WAAW;EAEX,IAAI,QAAqB;AACvB,UAAQ,MAAM,aAAa;;EAG7B,IAAI,MAAM,GAAgB;AACxB,QAAK,UAAU,YAAsB,EAAE;;EAI/B"}
|
package/dist/use-pop/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-pop/index.ts"],"sourcesContent":["import {\n computePosition,\n flip,\n shift,\n arrow,\n offset,\n type ComputePositionReturn,\n type Placement\n} from '@floating-ui/dom'\nimport { getScrollParents, setStyles } from '@veltra/utils'\nimport { isRef, onBeforeUnmount, watch, type Ref, type ShallowRef } from 'vue'\n\ntype TipDirection = 'top' | 'bottom' | 'left' | 'right'\ntype TipAlign = 'center' | 'start' | 'end'\n\ninterface Options {\n /** 触发元素 */\n triggerRef: ShallowRef<HTMLElement | undefined>\n /** 内容元素 */\n contentRef: ShallowRef<HTMLElement | undefined>\n /**\n * 箭头元素,如果存在,则会在弹框的箭头位置显示箭头\n */\n arrowRef?: ShallowRef<HTMLElement | undefined>\n /** 方向 */\n direction?: ShallowRef<TipDirection> | TipDirection\n /** 对齐方式 */\n alignment?: ShallowRef<TipAlign> | TipAlign\n /**\n * 箭头大小\n * @default 10\n */\n arrowSize?: number\n /**\n * 触发器元素位置变更时回调,\n * 一般用于在触发器元素位置变更时更新弹框位置\n */\n onTriggerPositionChange?: () => void\n /** 更新元素前回调 */\n onBeforeUpdate?: (triggerEl: HTMLElement, contentEl: HTMLElement) => void\n /** 更新元素后回调 */\n onAfterUpdate?: (position: ComputePositionReturn) => void\n /** 弹框弹出时回调 */\n onPop?: (position: ComputePositionReturn) => void\n}\n\ninterface PopResult {\n /**\n * 更新弹框位置\n */\n update: () => Promise<void>\n /** 浮框容器id */\n popperContainerId: string\n}\n\nlet popperContainer: HTMLElement | null = null\n\nconst popperContainerId = 'pop-container'\n\n/**\n * 浮框组合式函数\n * @param options 选项\n * @returns\n */\nexport function usePop(options: Options): PopResult {\n if (!popperContainer) {\n popperContainer = document.createElement('div')\n popperContainer.id = popperContainerId\n document.body.appendChild(popperContainer)\n }\n\n const {\n triggerRef,\n contentRef,\n arrowRef,\n arrowSize = 10,\n onTriggerPositionChange,\n onAfterUpdate,\n onBeforeUpdate,\n direction,\n alignment,\n onPop\n } = options\n\n /** 箭头位置 */\n const arrowPlacementDict = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }\n\n function getMaybeRefValue<T>(value?: Ref<T> | T) {\n return isRef(value) ? value.value : value\n }\n\n /**\n * 更新浮框位置\n * @param callbackOnPop 是否在弹出时回调\n */\n async function update(callbackOnPop = false) {\n const triggerEl = triggerRef.value\n const contentEl = contentRef.value\n\n if (!(triggerEl instanceof HTMLElement) || !(contentEl instanceof HTMLElement)) {\n return\n }\n\n onBeforeUpdate?.(triggerEl, contentEl)\n\n // 计算位置 ↓↓↓\n const middleware = [offset(arrowRef?.value ? arrowSize : 6), flip(), shift()]\n\n if (arrowRef?.value) {\n middleware.push(arrow({ element: arrowRef.value }))\n }\n\n const _direction = getMaybeRefValue(direction) ?? 'top'\n const _alignment = getMaybeRefValue(alignment) ?? 'center'\n\n const position = await computePosition(triggerEl, contentEl, {\n middleware,\n\n placement: `${_direction}${_alignment === 'center' ? '' : `-${_alignment}`}` as Placement\n })\n\n const { x, y, middlewareData, placement } = position\n\n setStyles(contentEl, { left: `${x}px`, top: `${y}px` })\n callbackOnPop && onPop?.(position)\n onAfterUpdate?.(position)\n\n // 设置箭头位置 ↓↓↓\n if (middlewareData.arrow) {\n const { x: arrowX, y: arrowY } = middlewareData.arrow\n\n const arrowPlacement =\n arrowPlacementDict[placement.split('-')[0]! as keyof typeof arrowPlacementDict]\n const size = `${arrowSize}px`\n // 箭头半径\n const arrowRadius = arrowSize / 2\n\n setStyles(arrowRef!.value!, {\n width: size,\n height: size,\n left: arrowX && `${arrowX - arrowRadius}px`,\n top: arrowY && `${arrowY - arrowRadius}px`,\n [arrowPlacement]: `-${arrowRadius}px`\n })\n }\n // 设置箭头位置 ↑↑↑\n }\n\n let scrollParents: HTMLElement[] = []\n\n /** 为触发器元素的祖先元素添加滚动事件 */\n function addScrollEvents() {\n if (!triggerRef.value) return\n if (onTriggerPositionChange) {\n scrollParents = getScrollParents(triggerRef.value)\n scrollParents.forEach((el) => {\n el.addEventListener('scroll', onTriggerPositionChange)\n })\n }\n }\n\n /** 移除触发器元素祖先元素的滚动事件 */\n function removeScrollEvents() {\n if (onTriggerPositionChange) {\n scrollParents.forEach((el) => {\n el.removeEventListener('scroll', onTriggerPositionChange)\n })\n }\n\n scrollParents = []\n }\n\n function addResizeEvents() {\n onTriggerPositionChange && window.addEventListener('resize', onTriggerPositionChange)\n }\n\n function removeResizeEvents() {\n onTriggerPositionChange && window.removeEventListener('resize', onTriggerPositionChange)\n }\n\n watch(\n [contentRef, () => getMaybeRefValue(direction), () => getMaybeRefValue(alignment)],\n ([content]) => {\n if (content) {\n update(true)\n addScrollEvents()\n addResizeEvents()\n return\n }\n removeScrollEvents()\n removeResizeEvents()\n }\n )\n\n onBeforeUnmount(() => {\n removeScrollEvents()\n removeResizeEvents()\n })\n\n return {\n /** 更新浮框位置 */\n update,\n /** 浮框容器id */\n popperContainerId\n }\n}\n"],"mappings":";;;;AAuDA,IAAI,kBAAsC;AAE1C,MAAM,oBAAoB;;;;;;AAO1B,SAAgB,OAAO,SAA6B;AAClD,KAAI,CAAC,iBAAiB;AACpB,oBAAkB,SAAS,cAAc,MAAM;AAC/C,kBAAgB,KAAK;AACrB,WAAS,KAAK,YAAY,gBAAgB;;CAG5C,MAAM,EACJ,YACA,YACA,UACA,YAAY,IACZ,yBACA,eACA,gBACA,WACA,WACA,UACE;;CAGJ,MAAM,qBAAqB;EAAE,KAAK;EAAU,OAAO;EAAQ,QAAQ;EAAO,MAAM;EAAS;CAEzF,SAAS,iBAAoB,OAAoB;AAC/C,SAAO,MAAM,MAAM,GAAG,MAAM,QAAQ;;;;;;CAOtC,eAAe,OAAO,gBAAgB,OAAO;EAC3C,MAAM,YAAY,WAAW;EAC7B,MAAM,YAAY,WAAW;AAE7B,MAAI,EAAE,qBAAqB,gBAAgB,EAAE,qBAAqB,aAChE;AAGF,mBAAiB,WAAW,UAAU;EAGtC,MAAM,aAAa;GAAC,OAAO,UAAU,QAAQ,YAAY,EAAE;GAAE,MAAM;GAAE,OAAO;GAAC;AAE7E,MAAI,UAAU,MACZ,YAAW,KAAK,MAAM,EAAE,SAAS,SAAS,OAAO,CAAC,CAAC;EAGrD,MAAM,aAAa,iBAAiB,UAAU,IAAI;EAClD,MAAM,aAAa,iBAAiB,UAAU,IAAI;EAElD,MAAM,WAAW,MAAM,gBAAgB,WAAW,WAAW;GAC3D;GAEA,WAAW,GAAG,aAAa,eAAe,WAAW,KAAK,IAAI;GAC/D,CAAC;EAEF,MAAM,EAAE,GAAG,GAAG,gBAAgB,cAAc;AAE5C,YAAU,WAAW;GAAE,MAAM,GAAG,EAAE;GAAK,KAAK,GAAG,EAAE;GAAK,CAAC;AACvD,mBAAiB,QAAQ,SAAS;AAClC,kBAAgB,SAAS;AAGzB,MAAI,eAAe,OAAO;GACxB,MAAM,EAAE,GAAG,QAAQ,GAAG,WAAW,eAAe;GAEhD,MAAM,iBACJ,mBAAmB,UAAU,MAAM,IAAI,CAAC;GAC1C,MAAM,OAAO,GAAG,UAAU;GAE1B,MAAM,cAAc,YAAY;AAEhC,aAAU,SAAU,OAAQ;IAC1B,OAAO;IACP,QAAQ;IACR,MAAM,UAAU,GAAG,SAAS,YAAY;IACxC,KAAK,UAAU,GAAG,SAAS,YAAY;KACtC,iBAAiB,IAAI,YAAY;IACnC,CAAC;;;CAKN,IAAI,gBAA+B,EAAE;;CAGrC,SAAS,kBAAkB;AACzB,MAAI,CAAC,WAAW,MAAO;AACvB,MAAI,yBAAyB;AAC3B,mBAAgB,iBAAiB,WAAW,MAAM;AAClD,iBAAc,SAAS,OAAO;AAC5B,OAAG,iBAAiB,UAAU,wBAAwB;KACtD;;;;CAKN,SAAS,qBAAqB;AAC5B,MAAI,wBACF,eAAc,SAAS,OAAO;AAC5B,MAAG,oBAAoB,UAAU,wBAAwB;IACzD;AAGJ,kBAAgB,EAAE;;CAGpB,SAAS,kBAAkB;AACzB,6BAA2B,OAAO,iBAAiB,UAAU,wBAAwB;;CAGvF,SAAS,qBAAqB;AAC5B,6BAA2B,OAAO,oBAAoB,UAAU,wBAAwB;;AAG1F,OACE;EAAC;QAAkB,iBAAiB,UAAU;QAAQ,iBAAiB,UAAU;EAAC,GACjF,CAAC,aAAa;AACb,MAAI,SAAS;AACX,UAAO,KAAK;AACZ,oBAAiB;AACjB,oBAAiB;AACjB;;AAEF,sBAAoB;AACpB,sBAAoB;GAEvB;AAED,uBAAsB;AACpB,sBAAoB;AACpB,sBAAoB;GACpB;AAEF,QAAO
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-pop/index.ts"],"sourcesContent":["import {\n computePosition,\n flip,\n shift,\n arrow,\n offset,\n type ComputePositionReturn,\n type Placement\n} from '@floating-ui/dom'\nimport { getScrollParents, setStyles } from '@veltra/utils'\nimport { isRef, onBeforeUnmount, watch, type Ref, type ShallowRef } from 'vue'\n\ntype TipDirection = 'top' | 'bottom' | 'left' | 'right'\ntype TipAlign = 'center' | 'start' | 'end'\n\ninterface Options {\n /** 触发元素 */\n triggerRef: ShallowRef<HTMLElement | undefined>\n /** 内容元素 */\n contentRef: ShallowRef<HTMLElement | undefined>\n /**\n * 箭头元素,如果存在,则会在弹框的箭头位置显示箭头\n */\n arrowRef?: ShallowRef<HTMLElement | undefined>\n /** 方向 */\n direction?: ShallowRef<TipDirection> | TipDirection\n /** 对齐方式 */\n alignment?: ShallowRef<TipAlign> | TipAlign\n /**\n * 箭头大小\n * @default 10\n */\n arrowSize?: number\n /**\n * 触发器元素位置变更时回调,\n * 一般用于在触发器元素位置变更时更新弹框位置\n */\n onTriggerPositionChange?: () => void\n /** 更新元素前回调 */\n onBeforeUpdate?: (triggerEl: HTMLElement, contentEl: HTMLElement) => void\n /** 更新元素后回调 */\n onAfterUpdate?: (position: ComputePositionReturn) => void\n /** 弹框弹出时回调 */\n onPop?: (position: ComputePositionReturn) => void\n}\n\ninterface PopResult {\n /**\n * 更新弹框位置\n */\n update: () => Promise<void>\n /** 浮框容器id */\n popperContainerId: string\n}\n\nlet popperContainer: HTMLElement | null = null\n\nconst popperContainerId = 'pop-container'\n\n/**\n * 浮框组合式函数\n * @param options 选项\n * @returns\n */\nexport function usePop(options: Options): PopResult {\n if (!popperContainer) {\n popperContainer = document.createElement('div')\n popperContainer.id = popperContainerId\n document.body.appendChild(popperContainer)\n }\n\n const {\n triggerRef,\n contentRef,\n arrowRef,\n arrowSize = 10,\n onTriggerPositionChange,\n onAfterUpdate,\n onBeforeUpdate,\n direction,\n alignment,\n onPop\n } = options\n\n /** 箭头位置 */\n const arrowPlacementDict = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }\n\n function getMaybeRefValue<T>(value?: Ref<T> | T) {\n return isRef(value) ? value.value : value\n }\n\n /**\n * 更新浮框位置\n * @param callbackOnPop 是否在弹出时回调\n */\n async function update(callbackOnPop = false) {\n const triggerEl = triggerRef.value\n const contentEl = contentRef.value\n\n if (!(triggerEl instanceof HTMLElement) || !(contentEl instanceof HTMLElement)) {\n return\n }\n\n onBeforeUpdate?.(triggerEl, contentEl)\n\n // 计算位置 ↓↓↓\n const middleware = [offset(arrowRef?.value ? arrowSize : 6), flip(), shift()]\n\n if (arrowRef?.value) {\n middleware.push(arrow({ element: arrowRef.value }))\n }\n\n const _direction = getMaybeRefValue(direction) ?? 'top'\n const _alignment = getMaybeRefValue(alignment) ?? 'center'\n\n const position = await computePosition(triggerEl, contentEl, {\n middleware,\n\n placement: `${_direction}${_alignment === 'center' ? '' : `-${_alignment}`}` as Placement\n })\n\n const { x, y, middlewareData, placement } = position\n\n setStyles(contentEl, { left: `${x}px`, top: `${y}px` })\n callbackOnPop && onPop?.(position)\n onAfterUpdate?.(position)\n\n // 设置箭头位置 ↓↓↓\n if (middlewareData.arrow) {\n const { x: arrowX, y: arrowY } = middlewareData.arrow\n\n const arrowPlacement =\n arrowPlacementDict[placement.split('-')[0]! as keyof typeof arrowPlacementDict]\n const size = `${arrowSize}px`\n // 箭头半径\n const arrowRadius = arrowSize / 2\n\n setStyles(arrowRef!.value!, {\n width: size,\n height: size,\n left: arrowX && `${arrowX - arrowRadius}px`,\n top: arrowY && `${arrowY - arrowRadius}px`,\n [arrowPlacement]: `-${arrowRadius}px`\n })\n }\n // 设置箭头位置 ↑↑↑\n }\n\n let scrollParents: HTMLElement[] = []\n\n /** 为触发器元素的祖先元素添加滚动事件 */\n function addScrollEvents() {\n if (!triggerRef.value) return\n if (onTriggerPositionChange) {\n scrollParents = getScrollParents(triggerRef.value)\n scrollParents.forEach((el) => {\n el.addEventListener('scroll', onTriggerPositionChange)\n })\n }\n }\n\n /** 移除触发器元素祖先元素的滚动事件 */\n function removeScrollEvents() {\n if (onTriggerPositionChange) {\n scrollParents.forEach((el) => {\n el.removeEventListener('scroll', onTriggerPositionChange)\n })\n }\n\n scrollParents = []\n }\n\n function addResizeEvents() {\n onTriggerPositionChange && window.addEventListener('resize', onTriggerPositionChange)\n }\n\n function removeResizeEvents() {\n onTriggerPositionChange && window.removeEventListener('resize', onTriggerPositionChange)\n }\n\n watch(\n [contentRef, () => getMaybeRefValue(direction), () => getMaybeRefValue(alignment)],\n ([content]) => {\n if (content) {\n update(true)\n addScrollEvents()\n addResizeEvents()\n return\n }\n removeScrollEvents()\n removeResizeEvents()\n }\n )\n\n onBeforeUnmount(() => {\n removeScrollEvents()\n removeResizeEvents()\n })\n\n return {\n /** 更新浮框位置 */\n update,\n /** 浮框容器id */\n popperContainerId\n }\n}\n"],"mappings":";;;;AAuDA,IAAI,kBAAsC;AAE1C,MAAM,oBAAoB;;;;;;AAO1B,SAAgB,OAAO,SAA6B;AAClD,KAAI,CAAC,iBAAiB;AACpB,oBAAkB,SAAS,cAAc,MAAM;AAC/C,kBAAgB,KAAK;AACrB,WAAS,KAAK,YAAY,gBAAgB;;CAG5C,MAAM,EACJ,YACA,YACA,UACA,YAAY,IACZ,yBACA,eACA,gBACA,WACA,WACA,UACE;;CAGJ,MAAM,qBAAqB;EAAE,KAAK;EAAU,OAAO;EAAQ,QAAQ;EAAO,MAAM;EAAS;CAEzF,SAAS,iBAAoB,OAAoB;AAC/C,SAAO,MAAM,MAAM,GAAG,MAAM,QAAQ;;;;;;CAOtC,eAAe,OAAO,gBAAgB,OAAO;EAC3C,MAAM,YAAY,WAAW;EAC7B,MAAM,YAAY,WAAW;AAE7B,MAAI,EAAE,qBAAqB,gBAAgB,EAAE,qBAAqB,aAChE;AAGF,mBAAiB,WAAW,UAAU;EAGtC,MAAM,aAAa;GAAC,OAAO,UAAU,QAAQ,YAAY,EAAE;GAAE,MAAM;GAAE,OAAO;GAAC;AAE7E,MAAI,UAAU,MACZ,YAAW,KAAK,MAAM,EAAE,SAAS,SAAS,OAAO,CAAC,CAAC;EAGrD,MAAM,aAAa,iBAAiB,UAAU,IAAI;EAClD,MAAM,aAAa,iBAAiB,UAAU,IAAI;EAElD,MAAM,WAAW,MAAM,gBAAgB,WAAW,WAAW;GAC3D;GAEA,WAAW,GAAG,aAAa,eAAe,WAAW,KAAK,IAAI;GAC/D,CAAC;EAEF,MAAM,EAAE,GAAG,GAAG,gBAAgB,cAAc;AAE5C,YAAU,WAAW;GAAE,MAAM,GAAG,EAAE;GAAK,KAAK,GAAG,EAAE;GAAK,CAAC;AACvD,mBAAiB,QAAQ,SAAS;AAClC,kBAAgB,SAAS;AAGzB,MAAI,eAAe,OAAO;GACxB,MAAM,EAAE,GAAG,QAAQ,GAAG,WAAW,eAAe;GAEhD,MAAM,iBACJ,mBAAmB,UAAU,MAAM,IAAI,CAAC;GAC1C,MAAM,OAAO,GAAG,UAAU;GAE1B,MAAM,cAAc,YAAY;AAEhC,aAAU,SAAU,OAAQ;IAC1B,OAAO;IACP,QAAQ;IACR,MAAM,UAAU,GAAG,SAAS,YAAY;IACxC,KAAK,UAAU,GAAG,SAAS,YAAY;KACtC,iBAAiB,IAAI,YAAY;IACnC,CAAC;;;CAKN,IAAI,gBAA+B,EAAE;;CAGrC,SAAS,kBAAkB;AACzB,MAAI,CAAC,WAAW,MAAO;AACvB,MAAI,yBAAyB;AAC3B,mBAAgB,iBAAiB,WAAW,MAAM;AAClD,iBAAc,SAAS,OAAO;AAC5B,OAAG,iBAAiB,UAAU,wBAAwB;KACtD;;;;CAKN,SAAS,qBAAqB;AAC5B,MAAI,wBACF,eAAc,SAAS,OAAO;AAC5B,MAAG,oBAAoB,UAAU,wBAAwB;IACzD;AAGJ,kBAAgB,EAAE;;CAGpB,SAAS,kBAAkB;AACzB,6BAA2B,OAAO,iBAAiB,UAAU,wBAAwB;;CAGvF,SAAS,qBAAqB;AAC5B,6BAA2B,OAAO,oBAAoB,UAAU,wBAAwB;;AAG1F,OACE;EAAC;QAAkB,iBAAiB,UAAU;QAAQ,iBAAiB,UAAU;EAAC,GACjF,CAAC,aAAa;AACb,MAAI,SAAS;AACX,UAAO,KAAK;AACZ,oBAAiB;AACjB,oBAAiB;AACjB;;AAEF,sBAAoB;AACpB,sBAAoB;GAEvB;AAED,uBAAsB;AACpB,sBAAoB;AACpB,sBAAoB;GACpB;AAEF,QAAO;;EAEL;;EAEA;EACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-resize-observer/index.ts"],"sourcesContent":["import { type Ref, type ShallowRef, onBeforeUnmount, watch } from 'vue'\n\nexport type RefElement =\n | ShallowRef<HTMLElement | undefined | null>\n | Ref<HTMLElement | undefined | null>\n\ninterface ResizeObserverOptions {\n /** 目标节点 */\n targets: RefElement | RefElement[]\n /** resize事件 */\n onResize: ResizeObserverCallback\n /** 指定观察条件 */\n when?: () => boolean\n}\n\n/** 监听器 */\nexport type ResizeObserverReturn = {\n /** 终止监听 */\n disconnect: () => void\n}\n\n/**\n * 取消监听\n * @param targets 目标节点\n * @param observer 观察器\n */\nfunction unobserve(targets: RefElement | RefElement[], observer?: ResizeObserver) {\n if (Array.isArray(targets)) {\n return targets.forEach((target) => unobserve(target, observer))\n }\n if (!targets.value || !observer) return\n observer.unobserve(targets.value)\n observer.disconnect()\n}\n\n/**\n * 监听目标尺寸变化\n * @param options 选项\n */\nexport function useResizeObserver(options: ResizeObserverOptions): ResizeObserverReturn {\n const { targets, onResize } = options\n\n let observer: ResizeObserver | undefined\n\n if (Array.isArray(targets)) {\n watch(\n targets,\n (val, oldVal) => {\n // 清除旧的观察\n oldVal.forEach((target) => {\n target && observer?.unobserve(target)\n })\n\n if (!observer && !!val.length) {\n observer = new ResizeObserver(onResize)\n }\n\n val.forEach((target) => {\n target && observer?.observe(target)\n })\n },\n { immediate: true }\n )\n } else {\n watch(\n targets,\n (val, oldVal) => {\n oldVal && observer?.unobserve(oldVal)\n if (!observer && val) {\n observer = new ResizeObserver(onResize)\n }\n val && observer?.observe(val)\n },\n { immediate: true }\n )\n }\n\n onBeforeUnmount(() => {\n unobserve(targets, observer)\n observer = undefined\n })\n\n return {\n disconnect() {\n unobserve(targets, observer)\n observer = undefined\n }\n }\n}\n\n/**\n * 监听元素尺寸变化\n */\nexport function useObserverCallback(): {\n observeEl: <El extends HTMLElement>(\n el: El,\n cb: (entry: Omit<ResizeObserverEntry, 'target'> & { target: El }) => void\n ) => void\n unobserveEl: (el: HTMLElement) => void\n} {\n const observerElMap = new Map<HTMLElement, Function>()\n\n const observer = new ResizeObserver((entries) => {\n entries.forEach((entry) => {\n const target = entry.target as HTMLElement\n if (!target.dataset.ob) {\n target.dataset.ob = 'true'\n return\n }\n const fn = observerElMap.get(target)\n\n fn?.(entry)\n })\n })\n\n /**\n * 监听元素尺寸\n * @param el 元素\n * @param cb 回调\n */\n function observeEl<El extends HTMLElement>(\n el: El,\n cb: (entry: Omit<ResizeObserverEntry, 'target'> & { target: El }) => void\n ) {\n observer.observe(el)\n observerElMap.set(el, cb)\n }\n\n /**\n * 取消监听元素尺寸\n * @param el 元素\n */\n function unobserveEl(el: HTMLElement) {\n observer.unobserve(el)\n delete el.dataset.ob\n observerElMap.delete(el)\n }\n\n onBeforeUnmount(() => {\n observerElMap.forEach((_, el) => {\n observer.unobserve(el)\n })\n observerElMap.clear()\n observer.disconnect()\n })\n\n return { observeEl, unobserveEl }\n}\n"],"mappings":";;;;;;;AA0BA,SAAS,UAAU,SAAoC,UAA2B;AAChF,KAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,SAAS,WAAW,UAAU,QAAQ,SAAS,CAAC;AAEjE,KAAI,CAAC,QAAQ,SAAS,CAAC,SAAU;AACjC,UAAS,UAAU,QAAQ,MAAM;AACjC,UAAS,YAAY;;;;;;AAOvB,SAAgB,kBAAkB,SAAsD;CACtF,MAAM,EAAE,SAAS,aAAa;CAE9B,IAAI;AAEJ,KAAI,MAAM,QAAQ,QAAQ,CACxB,OACE,UACC,KAAK,WAAW;AAEf,SAAO,SAAS,WAAW;AACzB,aAAU,UAAU,UAAU,OAAO;IACrC;AAEF,MAAI,CAAC,YAAY,CAAC,CAAC,IAAI,OACrB,YAAW,IAAI,eAAe,SAAS;AAGzC,MAAI,SAAS,WAAW;AACtB,aAAU,UAAU,QAAQ,OAAO;IACnC;IAEJ,EAAE,WAAW,MAAM,CACpB;KAED,OACE,UACC,KAAK,WAAW;AACf,YAAU,UAAU,UAAU,OAAO;AACrC,MAAI,CAAC,YAAY,IACf,YAAW,IAAI,eAAe,SAAS;AAEzC,SAAO,UAAU,QAAQ,IAAI;IAE/B,EAAE,WAAW,MAAM,CACpB;AAGH,uBAAsB;AACpB,YAAU,SAAS,SAAS;AAC5B,aAAW,KAAA;GACX;AAEF,QAAO,EACL,aAAa;AACX,YAAU,SAAS,SAAS;AAC5B,aAAW,KAAA;IAEd;;;;;AAMH,SAAgB,sBAMd;CACA,MAAM,gCAAgB,IAAI,KAA4B;CAEtD,MAAM,WAAW,IAAI,gBAAgB,YAAY;AAC/C,UAAQ,SAAS,UAAU;GACzB,MAAM,SAAS,MAAM;AACrB,OAAI,CAAC,OAAO,QAAQ,IAAI;AACtB,WAAO,QAAQ,KAAK;AACpB;;AAES,iBAAc,IAAI,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-resize-observer/index.ts"],"sourcesContent":["import { type Ref, type ShallowRef, onBeforeUnmount, watch } from 'vue'\n\nexport type RefElement =\n | ShallowRef<HTMLElement | undefined | null>\n | Ref<HTMLElement | undefined | null>\n\ninterface ResizeObserverOptions {\n /** 目标节点 */\n targets: RefElement | RefElement[]\n /** resize事件 */\n onResize: ResizeObserverCallback\n /** 指定观察条件 */\n when?: () => boolean\n}\n\n/** 监听器 */\nexport type ResizeObserverReturn = {\n /** 终止监听 */\n disconnect: () => void\n}\n\n/**\n * 取消监听\n * @param targets 目标节点\n * @param observer 观察器\n */\nfunction unobserve(targets: RefElement | RefElement[], observer?: ResizeObserver) {\n if (Array.isArray(targets)) {\n return targets.forEach((target) => unobserve(target, observer))\n }\n if (!targets.value || !observer) return\n observer.unobserve(targets.value)\n observer.disconnect()\n}\n\n/**\n * 监听目标尺寸变化\n * @param options 选项\n */\nexport function useResizeObserver(options: ResizeObserverOptions): ResizeObserverReturn {\n const { targets, onResize } = options\n\n let observer: ResizeObserver | undefined\n\n if (Array.isArray(targets)) {\n watch(\n targets,\n (val, oldVal) => {\n // 清除旧的观察\n oldVal.forEach((target) => {\n target && observer?.unobserve(target)\n })\n\n if (!observer && !!val.length) {\n observer = new ResizeObserver(onResize)\n }\n\n val.forEach((target) => {\n target && observer?.observe(target)\n })\n },\n { immediate: true }\n )\n } else {\n watch(\n targets,\n (val, oldVal) => {\n oldVal && observer?.unobserve(oldVal)\n if (!observer && val) {\n observer = new ResizeObserver(onResize)\n }\n val && observer?.observe(val)\n },\n { immediate: true }\n )\n }\n\n onBeforeUnmount(() => {\n unobserve(targets, observer)\n observer = undefined\n })\n\n return {\n disconnect() {\n unobserve(targets, observer)\n observer = undefined\n }\n }\n}\n\n/**\n * 监听元素尺寸变化\n */\nexport function useObserverCallback(): {\n observeEl: <El extends HTMLElement>(\n el: El,\n cb: (entry: Omit<ResizeObserverEntry, 'target'> & { target: El }) => void\n ) => void\n unobserveEl: (el: HTMLElement) => void\n} {\n const observerElMap = new Map<HTMLElement, Function>()\n\n const observer = new ResizeObserver((entries) => {\n entries.forEach((entry) => {\n const target = entry.target as HTMLElement\n if (!target.dataset.ob) {\n target.dataset.ob = 'true'\n return\n }\n const fn = observerElMap.get(target)\n\n fn?.(entry)\n })\n })\n\n /**\n * 监听元素尺寸\n * @param el 元素\n * @param cb 回调\n */\n function observeEl<El extends HTMLElement>(\n el: El,\n cb: (entry: Omit<ResizeObserverEntry, 'target'> & { target: El }) => void\n ) {\n observer.observe(el)\n observerElMap.set(el, cb)\n }\n\n /**\n * 取消监听元素尺寸\n * @param el 元素\n */\n function unobserveEl(el: HTMLElement) {\n observer.unobserve(el)\n delete el.dataset.ob\n observerElMap.delete(el)\n }\n\n onBeforeUnmount(() => {\n observerElMap.forEach((_, el) => {\n observer.unobserve(el)\n })\n observerElMap.clear()\n observer.disconnect()\n })\n\n return { observeEl, unobserveEl }\n}\n"],"mappings":";;;;;;;AA0BA,SAAS,UAAU,SAAoC,UAA2B;AAChF,KAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,SAAS,WAAW,UAAU,QAAQ,SAAS,CAAC;AAEjE,KAAI,CAAC,QAAQ,SAAS,CAAC,SAAU;AACjC,UAAS,UAAU,QAAQ,MAAM;AACjC,UAAS,YAAY;;;;;;AAOvB,SAAgB,kBAAkB,SAAsD;CACtF,MAAM,EAAE,SAAS,aAAa;CAE9B,IAAI;AAEJ,KAAI,MAAM,QAAQ,QAAQ,CACxB,OACE,UACC,KAAK,WAAW;AAEf,SAAO,SAAS,WAAW;AACzB,aAAU,UAAU,UAAU,OAAO;IACrC;AAEF,MAAI,CAAC,YAAY,CAAC,CAAC,IAAI,OACrB,YAAW,IAAI,eAAe,SAAS;AAGzC,MAAI,SAAS,WAAW;AACtB,aAAU,UAAU,QAAQ,OAAO;IACnC;IAEJ,EAAE,WAAW,MAAM,CACpB;KAED,OACE,UACC,KAAK,WAAW;AACf,YAAU,UAAU,UAAU,OAAO;AACrC,MAAI,CAAC,YAAY,IACf,YAAW,IAAI,eAAe,SAAS;AAEzC,SAAO,UAAU,QAAQ,IAAI;IAE/B,EAAE,WAAW,MAAM,CACpB;AAGH,uBAAsB;AACpB,YAAU,SAAS,SAAS;AAC5B,aAAW,KAAA;GACX;AAEF,QAAO,EACL,aAAa;AACX,YAAU,SAAS,SAAS;AAC5B,aAAW,KAAA;IAEd;;;;;AAMH,SAAgB,sBAMd;CACA,MAAM,gCAAgB,IAAI,KAA4B;CAEtD,MAAM,WAAW,IAAI,gBAAgB,YAAY;AAC/C,UAAQ,SAAS,UAAU;GACzB,MAAM,SAAS,MAAM;AACrB,OAAI,CAAC,OAAO,QAAQ,IAAI;AACtB,WAAO,QAAQ,KAAK;AACpB;;AAES,iBAAc,IAAI,OAE3B,GAAG,MAAM;IACX;GACF;;;;;;CAOF,SAAS,UACP,IACA,IACA;AACA,WAAS,QAAQ,GAAG;AACpB,gBAAc,IAAI,IAAI,GAAG;;;;;;CAO3B,SAAS,YAAY,IAAiB;AACpC,WAAS,UAAU,GAAG;AACtB,SAAO,GAAG,QAAQ;AAClB,gBAAc,OAAO,GAAG;;AAG1B,uBAAsB;AACpB,gBAAc,SAAS,GAAG,OAAO;AAC/B,YAAS,UAAU,GAAG;IACtB;AACF,gBAAc,OAAO;AACrB,WAAS,YAAY;GACrB;AAEF,QAAO;EAAE;EAAW;EAAa"}
|
|
@@ -18,11 +18,17 @@ function useCssTransition(options) {
|
|
|
18
18
|
const classes = computed(() => {
|
|
19
19
|
const _name = typeof name === "string" ? name : name.value;
|
|
20
20
|
return {
|
|
21
|
+
/** 进入前的类 */
|
|
21
22
|
enterFrom: `${_name}-enter-from`,
|
|
23
|
+
/** 进入后最终的类 */
|
|
22
24
|
enterTo: `${_name}-enter-to`,
|
|
25
|
+
/** 【进入动画】持续时的类 */
|
|
23
26
|
enterActive: `${_name}-enter-active`,
|
|
27
|
+
/** 离开前的类 */
|
|
24
28
|
leaveFrom: `${_name}-leave-from`,
|
|
29
|
+
/** 离开类 */
|
|
25
30
|
leaveTo: `${_name}-leave-to`,
|
|
31
|
+
/** 【离开动画】持续时的类 */
|
|
26
32
|
leaveActive: `${_name}-leave-active`
|
|
27
33
|
};
|
|
28
34
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-css-transition.js","names":[],"sources":["../../src/use-transition/use-css-transition.ts"],"sourcesContent":["import { createToggle } from '@veltra/utils'\nimport { isRef, watch, onBeforeUnmount, computed } from 'vue'\n\nimport type { Returned, CssTransitionOptions } from './type'\n\nconst increaseTransitionCount = (el: HTMLElement & { _count?: number }) => {\n el._count = (el._count ?? 0) + 1\n}\n\nconst decreaseTransitionCount = (el: HTMLElement & { _count?: number }) => {\n el._count = (el._count ?? 0) - 1\n if (el._count <= 0) {\n delete el._count\n }\n}\n\n/**\n * 使用css过渡\n * @param options 过渡选项\n */\nexport function useCssTransition(options: CssTransitionOptions): Returned {\n const {\n name,\n target,\n afterEnter,\n afterLeave,\n enterCanceled,\n leaveCanceled,\n keepEnterTo = false\n } = options\n\n const getDom = (): (HTMLElement & { _count?: number }) | undefined =>\n isRef(target) ? target.value : target\n\n const classes = computed(() => {\n const _name = typeof name === 'string' ? name : name.value\n return {\n /** 进入前的类 */\n enterFrom: `${_name}-enter-from`,\n /** 进入后最终的类 */\n enterTo: `${_name}-enter-to`,\n /** 【进入动画】持续时的类 */\n enterActive: `${_name}-enter-active`,\n /** 离开前的类 */\n leaveFrom: `${_name}-leave-from`,\n /** 离开类 */\n leaveTo: `${_name}-leave-to`,\n /** 【离开动画】持续时的类 */\n leaveActive: `${_name}-leave-active`\n }\n })\n\n /** 开始进入动画 */\n const startTransitionIn = () => {\n const { enterActive, enterTo, enterFrom } = classes.value\n const dom = getDom()\n\n dom?.classList.add(enterFrom)\n\n requestAnimationFrame(() => {\n dom?.classList.add(enterActive)\n requestAnimationFrame(() => {\n dom?.classList.remove(enterFrom)\n dom?.classList.add(enterTo)\n })\n })\n }\n\n /** 开始离开动画 */\n const startTransitionOut = () => {\n const { leaveTo, leaveActive, leaveFrom, enterTo } = classes.value\n const dom = getDom()\n\n // 标记动画进入离开状态\n if (keepEnterTo) {\n dom?.classList.remove(enterTo)\n }\n dom?.classList.add(leaveFrom, leaveActive)\n\n requestAnimationFrame(() => {\n dom?.classList.add(leaveActive)\n requestAnimationFrame(() => {\n dom?.classList.remove(leaveFrom)\n dom?.classList.add(leaveTo)\n })\n })\n }\n\n const [active, toggle] = createToggle(false, (_active) => {\n _active ? startTransitionIn() : startTransitionOut()\n })\n\n const transitionEndHandler = (e: TransitionEvent) => {\n e.stopPropagation()\n\n const { leaveActive, enterActive, enterTo, leaveTo } = classes.value\n const dom = getDom()\n\n if (dom !== e.target) return\n\n decreaseTransitionCount(dom)\n\n if (dom._count) return\n\n // 激活状态,移除enter-active类\n if (active.value) {\n if (keepEnterTo) {\n dom?.classList.remove(enterActive)\n } else {\n dom?.classList.remove(enterActive, enterTo)\n }\n afterEnter?.()\n } else {\n dom?.classList.remove(leaveActive, leaveTo)\n afterLeave?.()\n }\n }\n\n const transitionRunHandler = (e: TransitionEvent) => {\n e.stopPropagation()\n const dom = getDom()\n if (dom !== e.target) return\n increaseTransitionCount(dom)\n }\n\n const transitionCancelHandler = (e: TransitionEvent) => {\n e.stopPropagation()\n const dom = getDom()\n\n if (dom !== e.target) return\n decreaseTransitionCount(dom)\n\n if (dom._count) return\n\n const { leaveActive, enterActive } = classes.value\n\n // 激活状态,移除enter-active类\n if (active.value) {\n dom?.classList.remove(enterActive)\n enterCanceled?.()\n } else {\n dom?.classList.remove(leaveActive)\n leaveCanceled?.()\n }\n }\n\n /** 添加事件 */\n const addEvent = (el?: HTMLElement) => {\n el?.addEventListener('transitioncancel', transitionCancelHandler)\n el?.addEventListener('transitionend', transitionEndHandler)\n el?.addEventListener('transitionrun', transitionRunHandler)\n }\n\n /** 移除事件 */\n const removeEvent = (el?: HTMLElement) => {\n el?.removeEventListener('transitioncancel', transitionCancelHandler)\n el?.removeEventListener('transitionend', transitionEndHandler)\n el?.removeEventListener('transitionrun', transitionRunHandler)\n }\n\n if (isRef(target)) {\n watch(target, (_target, oldTarget) => {\n if (oldTarget) {\n removeEvent(oldTarget)\n }\n _target && addEvent(_target)\n })\n } else if (target) {\n addEvent(target)\n }\n\n onBeforeUnmount(() => {\n const dom = getDom()\n removeEvent(dom)\n })\n\n return {\n toggle,\n enter() {\n toggle(true)\n },\n leave() {\n toggle(false)\n }\n }\n}\n"],"mappings":";;;AAKA,MAAM,2BAA2B,OAA0C;AACzE,IAAG,UAAU,GAAG,UAAU,KAAK;;AAGjC,MAAM,2BAA2B,OAA0C;AACzE,IAAG,UAAU,GAAG,UAAU,KAAK;AAC/B,KAAI,GAAG,UAAU,EACf,QAAO,GAAG;;;;;;AAQd,SAAgB,iBAAiB,SAAyC;CACxE,MAAM,EACJ,MACA,QACA,YACA,YACA,eACA,eACA,cAAc,UACZ;CAEJ,MAAM,eACJ,MAAM,OAAO,GAAG,OAAO,QAAQ;CAEjC,MAAM,UAAU,eAAe;EAC7B,MAAM,QAAQ,OAAO,SAAS,WAAW,OAAO,KAAK;AACrD,SAAO
|
|
1
|
+
{"version":3,"file":"use-css-transition.js","names":[],"sources":["../../src/use-transition/use-css-transition.ts"],"sourcesContent":["import { createToggle } from '@veltra/utils'\nimport { isRef, watch, onBeforeUnmount, computed } from 'vue'\n\nimport type { Returned, CssTransitionOptions } from './type'\n\nconst increaseTransitionCount = (el: HTMLElement & { _count?: number }) => {\n el._count = (el._count ?? 0) + 1\n}\n\nconst decreaseTransitionCount = (el: HTMLElement & { _count?: number }) => {\n el._count = (el._count ?? 0) - 1\n if (el._count <= 0) {\n delete el._count\n }\n}\n\n/**\n * 使用css过渡\n * @param options 过渡选项\n */\nexport function useCssTransition(options: CssTransitionOptions): Returned {\n const {\n name,\n target,\n afterEnter,\n afterLeave,\n enterCanceled,\n leaveCanceled,\n keepEnterTo = false\n } = options\n\n const getDom = (): (HTMLElement & { _count?: number }) | undefined =>\n isRef(target) ? target.value : target\n\n const classes = computed(() => {\n const _name = typeof name === 'string' ? name : name.value\n return {\n /** 进入前的类 */\n enterFrom: `${_name}-enter-from`,\n /** 进入后最终的类 */\n enterTo: `${_name}-enter-to`,\n /** 【进入动画】持续时的类 */\n enterActive: `${_name}-enter-active`,\n /** 离开前的类 */\n leaveFrom: `${_name}-leave-from`,\n /** 离开类 */\n leaveTo: `${_name}-leave-to`,\n /** 【离开动画】持续时的类 */\n leaveActive: `${_name}-leave-active`\n }\n })\n\n /** 开始进入动画 */\n const startTransitionIn = () => {\n const { enterActive, enterTo, enterFrom } = classes.value\n const dom = getDom()\n\n dom?.classList.add(enterFrom)\n\n requestAnimationFrame(() => {\n dom?.classList.add(enterActive)\n requestAnimationFrame(() => {\n dom?.classList.remove(enterFrom)\n dom?.classList.add(enterTo)\n })\n })\n }\n\n /** 开始离开动画 */\n const startTransitionOut = () => {\n const { leaveTo, leaveActive, leaveFrom, enterTo } = classes.value\n const dom = getDom()\n\n // 标记动画进入离开状态\n if (keepEnterTo) {\n dom?.classList.remove(enterTo)\n }\n dom?.classList.add(leaveFrom, leaveActive)\n\n requestAnimationFrame(() => {\n dom?.classList.add(leaveActive)\n requestAnimationFrame(() => {\n dom?.classList.remove(leaveFrom)\n dom?.classList.add(leaveTo)\n })\n })\n }\n\n const [active, toggle] = createToggle(false, (_active) => {\n _active ? startTransitionIn() : startTransitionOut()\n })\n\n const transitionEndHandler = (e: TransitionEvent) => {\n e.stopPropagation()\n\n const { leaveActive, enterActive, enterTo, leaveTo } = classes.value\n const dom = getDom()\n\n if (dom !== e.target) return\n\n decreaseTransitionCount(dom)\n\n if (dom._count) return\n\n // 激活状态,移除enter-active类\n if (active.value) {\n if (keepEnterTo) {\n dom?.classList.remove(enterActive)\n } else {\n dom?.classList.remove(enterActive, enterTo)\n }\n afterEnter?.()\n } else {\n dom?.classList.remove(leaveActive, leaveTo)\n afterLeave?.()\n }\n }\n\n const transitionRunHandler = (e: TransitionEvent) => {\n e.stopPropagation()\n const dom = getDom()\n if (dom !== e.target) return\n increaseTransitionCount(dom)\n }\n\n const transitionCancelHandler = (e: TransitionEvent) => {\n e.stopPropagation()\n const dom = getDom()\n\n if (dom !== e.target) return\n decreaseTransitionCount(dom)\n\n if (dom._count) return\n\n const { leaveActive, enterActive } = classes.value\n\n // 激活状态,移除enter-active类\n if (active.value) {\n dom?.classList.remove(enterActive)\n enterCanceled?.()\n } else {\n dom?.classList.remove(leaveActive)\n leaveCanceled?.()\n }\n }\n\n /** 添加事件 */\n const addEvent = (el?: HTMLElement) => {\n el?.addEventListener('transitioncancel', transitionCancelHandler)\n el?.addEventListener('transitionend', transitionEndHandler)\n el?.addEventListener('transitionrun', transitionRunHandler)\n }\n\n /** 移除事件 */\n const removeEvent = (el?: HTMLElement) => {\n el?.removeEventListener('transitioncancel', transitionCancelHandler)\n el?.removeEventListener('transitionend', transitionEndHandler)\n el?.removeEventListener('transitionrun', transitionRunHandler)\n }\n\n if (isRef(target)) {\n watch(target, (_target, oldTarget) => {\n if (oldTarget) {\n removeEvent(oldTarget)\n }\n _target && addEvent(_target)\n })\n } else if (target) {\n addEvent(target)\n }\n\n onBeforeUnmount(() => {\n const dom = getDom()\n removeEvent(dom)\n })\n\n return {\n toggle,\n enter() {\n toggle(true)\n },\n leave() {\n toggle(false)\n }\n }\n}\n"],"mappings":";;;AAKA,MAAM,2BAA2B,OAA0C;AACzE,IAAG,UAAU,GAAG,UAAU,KAAK;;AAGjC,MAAM,2BAA2B,OAA0C;AACzE,IAAG,UAAU,GAAG,UAAU,KAAK;AAC/B,KAAI,GAAG,UAAU,EACf,QAAO,GAAG;;;;;;AAQd,SAAgB,iBAAiB,SAAyC;CACxE,MAAM,EACJ,MACA,QACA,YACA,YACA,eACA,eACA,cAAc,UACZ;CAEJ,MAAM,eACJ,MAAM,OAAO,GAAG,OAAO,QAAQ;CAEjC,MAAM,UAAU,eAAe;EAC7B,MAAM,QAAQ,OAAO,SAAS,WAAW,OAAO,KAAK;AACrD,SAAO;;GAEL,WAAW,GAAG,MAAM;;GAEpB,SAAS,GAAG,MAAM;;GAElB,aAAa,GAAG,MAAM;;GAEtB,WAAW,GAAG,MAAM;;GAEpB,SAAS,GAAG,MAAM;;GAElB,aAAa,GAAG,MAAM;GACvB;GACD;;CAGF,MAAM,0BAA0B;EAC9B,MAAM,EAAE,aAAa,SAAS,cAAc,QAAQ;EACpD,MAAM,MAAM,QAAQ;AAEpB,OAAK,UAAU,IAAI,UAAU;AAE7B,8BAA4B;AAC1B,QAAK,UAAU,IAAI,YAAY;AAC/B,+BAA4B;AAC1B,SAAK,UAAU,OAAO,UAAU;AAChC,SAAK,UAAU,IAAI,QAAQ;KAC3B;IACF;;;CAIJ,MAAM,2BAA2B;EAC/B,MAAM,EAAE,SAAS,aAAa,WAAW,YAAY,QAAQ;EAC7D,MAAM,MAAM,QAAQ;AAGpB,MAAI,YACF,MAAK,UAAU,OAAO,QAAQ;AAEhC,OAAK,UAAU,IAAI,WAAW,YAAY;AAE1C,8BAA4B;AAC1B,QAAK,UAAU,IAAI,YAAY;AAC/B,+BAA4B;AAC1B,SAAK,UAAU,OAAO,UAAU;AAChC,SAAK,UAAU,IAAI,QAAQ;KAC3B;IACF;;CAGJ,MAAM,CAAC,QAAQ,UAAU,aAAa,QAAQ,YAAY;AACxD,YAAU,mBAAmB,GAAG,oBAAoB;GACpD;CAEF,MAAM,wBAAwB,MAAuB;AACnD,IAAE,iBAAiB;EAEnB,MAAM,EAAE,aAAa,aAAa,SAAS,YAAY,QAAQ;EAC/D,MAAM,MAAM,QAAQ;AAEpB,MAAI,QAAQ,EAAE,OAAQ;AAEtB,0BAAwB,IAAI;AAE5B,MAAI,IAAI,OAAQ;AAGhB,MAAI,OAAO,OAAO;AAChB,OAAI,YACF,MAAK,UAAU,OAAO,YAAY;OAElC,MAAK,UAAU,OAAO,aAAa,QAAQ;AAE7C,iBAAc;SACT;AACL,QAAK,UAAU,OAAO,aAAa,QAAQ;AAC3C,iBAAc;;;CAIlB,MAAM,wBAAwB,MAAuB;AACnD,IAAE,iBAAiB;EACnB,MAAM,MAAM,QAAQ;AACpB,MAAI,QAAQ,EAAE,OAAQ;AACtB,0BAAwB,IAAI;;CAG9B,MAAM,2BAA2B,MAAuB;AACtD,IAAE,iBAAiB;EACnB,MAAM,MAAM,QAAQ;AAEpB,MAAI,QAAQ,EAAE,OAAQ;AACtB,0BAAwB,IAAI;AAE5B,MAAI,IAAI,OAAQ;EAEhB,MAAM,EAAE,aAAa,gBAAgB,QAAQ;AAG7C,MAAI,OAAO,OAAO;AAChB,QAAK,UAAU,OAAO,YAAY;AAClC,oBAAiB;SACZ;AACL,QAAK,UAAU,OAAO,YAAY;AAClC,oBAAiB;;;;CAKrB,MAAM,YAAY,OAAqB;AACrC,MAAI,iBAAiB,oBAAoB,wBAAwB;AACjE,MAAI,iBAAiB,iBAAiB,qBAAqB;AAC3D,MAAI,iBAAiB,iBAAiB,qBAAqB;;;CAI7D,MAAM,eAAe,OAAqB;AACxC,MAAI,oBAAoB,oBAAoB,wBAAwB;AACpE,MAAI,oBAAoB,iBAAiB,qBAAqB;AAC9D,MAAI,oBAAoB,iBAAiB,qBAAqB;;AAGhE,KAAI,MAAM,OAAO,CACf,OAAM,SAAS,SAAS,cAAc;AACpC,MAAI,UACF,aAAY,UAAU;AAExB,aAAW,SAAS,QAAQ;GAC5B;UACO,OACT,UAAS,OAAO;AAGlB,uBAAsB;AAEpB,cADY,QACG,CAAC;GAChB;AAEF,QAAO;EACL;EACA,QAAQ;AACN,UAAO,KAAK;;EAEd,QAAQ;AACN,UAAO,MAAM;;EAEhB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/use-user-action/index.d.ts
|
|
2
|
+
type UserAction = <T extends (...args: any[]) => void | Promise<void>>(fn: T) => (...args: Parameters<T>) => Promise<void>;
|
|
3
|
+
interface UserActionResult {
|
|
4
|
+
isUserActive: () => boolean;
|
|
5
|
+
userAction: UserAction;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 创建用户活动
|
|
9
|
+
* @description
|
|
10
|
+
* 解决"组件内部 emit 更新 → 外部 props 回传 → 监听 props 的副作用再次触发
|
|
11
|
+
* 内部状态变更"的循环更新问题。典型搭配:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const { userAction, isUserActive } = useUserAction()
|
|
15
|
+
*
|
|
16
|
+
* // 将函数标记为一个用户动作
|
|
17
|
+
* const handleSelect = userAction((date: Dater) => {
|
|
18
|
+
* currentDate.value = date
|
|
19
|
+
* emit('update:modelValue', date.format(fmt))
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* // modelValue 回显:用户活动期间跳过
|
|
23
|
+
* watch(() => props.modelValue, (v) => {
|
|
24
|
+
* if (isUserActive()) return
|
|
25
|
+
* currentDate.value = v ? date(v) : undefined
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function useUserAction(): UserActionResult;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { UserAction, UserActionResult, useUserAction };
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { nextTick } from "vue";
|
|
2
|
+
//#region src/use-user-action/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* 创建用户活动
|
|
5
|
+
* @description
|
|
6
|
+
* 解决"组件内部 emit 更新 → 外部 props 回传 → 监听 props 的副作用再次触发
|
|
7
|
+
* 内部状态变更"的循环更新问题。典型搭配:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const { userAction, isUserActive } = useUserAction()
|
|
11
|
+
*
|
|
12
|
+
* // 将函数标记为一个用户动作
|
|
13
|
+
* const handleSelect = userAction((date: Dater) => {
|
|
14
|
+
* currentDate.value = date
|
|
15
|
+
* emit('update:modelValue', date.format(fmt))
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // modelValue 回显:用户活动期间跳过
|
|
19
|
+
* watch(() => props.modelValue, (v) => {
|
|
20
|
+
* if (isUserActive()) return
|
|
21
|
+
* currentDate.value = v ? date(v) : undefined
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function useUserAction() {
|
|
26
|
+
let actionCount = 0;
|
|
27
|
+
const isUserActive = () => {
|
|
28
|
+
return actionCount > 0;
|
|
29
|
+
};
|
|
30
|
+
const userAction = (fn) => {
|
|
31
|
+
return async (...args) => {
|
|
32
|
+
actionCount++;
|
|
33
|
+
try {
|
|
34
|
+
await fn(...args);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(error);
|
|
37
|
+
}
|
|
38
|
+
await nextTick();
|
|
39
|
+
actionCount--;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
isUserActive,
|
|
44
|
+
userAction
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { useUserAction };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-user-action/index.ts"],"sourcesContent":["import { nextTick } from 'vue'\n\nexport type UserAction = <T extends (...args: any[]) => void | Promise<void>>(\n fn: T\n) => (...args: Parameters<T>) => Promise<void>\n\nexport interface UserActionResult {\n isUserActive: () => boolean\n userAction: UserAction\n}\n\n/**\n * 创建用户活动\n * @description\n * 解决\"组件内部 emit 更新 → 外部 props 回传 → 监听 props 的副作用再次触发\n * 内部状态变更\"的循环更新问题。典型搭配:\n *\n * ```ts\n * const { userAction, isUserActive } = useUserAction()\n *\n * // 将函数标记为一个用户动作\n * const handleSelect = userAction((date: Dater) => {\n * currentDate.value = date\n * emit('update:modelValue', date.format(fmt))\n * })\n *\n * // modelValue 回显:用户活动期间跳过\n * watch(() => props.modelValue, (v) => {\n * if (isUserActive()) return\n * currentDate.value = v ? date(v) : undefined\n * })\n * ```\n */\nexport function useUserAction(): UserActionResult {\n let actionCount = 0\n const isUserActive = () => {\n return actionCount > 0\n }\n\n const userAction: UserAction = (fn) => {\n return async (...args) => {\n actionCount++\n\n try {\n await fn(...args)\n } catch (error) {\n console.error(error)\n }\n await nextTick()\n\n actionCount--\n }\n }\n\n return { isUserActive, userAction }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,gBAAkC;CAChD,IAAI,cAAc;CAClB,MAAM,qBAAqB;AACzB,SAAO,cAAc;;CAGvB,MAAM,cAA0B,OAAO;AACrC,SAAO,OAAO,GAAG,SAAS;AACxB;AAEA,OAAI;AACF,UAAM,GAAG,GAAG,KAAK;YACV,OAAO;AACd,YAAQ,MAAM,MAAM;;AAEtB,SAAM,UAAU;AAEhB;;;AAIJ,QAAO;EAAE;EAAc;EAAY"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MaybeRefOrGetter, Ref, ShallowRef } from "vue";
|
|
2
|
+
import { EstimateSize, GetItemKey, VirtualAlign, VirtualItem, VirtualMeasurement, VirtualRange, VirtualScrollOptions, VirtualSnapshot, Virtualizer as Virtualizer$1, VirtualizerOptions } from "@cat-kit/fe";
|
|
3
|
+
|
|
4
|
+
//#region src/use-virtualizer/index.d.ts
|
|
5
|
+
type MaybeEl = HTMLElement | null | undefined;
|
|
6
|
+
interface UseVirtualizerOptions extends Omit<VirtualizerOptions, 'count'> {
|
|
7
|
+
/** 响应式虚拟项总数;内部 `watch` 驱动 `virtualizer.setCount`。 */
|
|
8
|
+
count: Ref<number>;
|
|
9
|
+
/**
|
|
10
|
+
* 响应式滚动容器;变更时自动 `connect` / `disconnect`。
|
|
11
|
+
* `immediate: true`,初始为 `null` 时等价于 `disconnect()`。
|
|
12
|
+
*/
|
|
13
|
+
scrollEl: MaybeRefOrGetter<MaybeEl>;
|
|
14
|
+
/**
|
|
15
|
+
* 可选:内容承接元素。hook 订阅到结构性更新时直接写入
|
|
16
|
+
* `style.height = totalSize + 'px'`(水平模式为 `width`),
|
|
17
|
+
* **不经过 Vue 响应式**——因此 `totalSize` 的变化不会触发模板重渲染。
|
|
18
|
+
* 当引用切换为 `null` 时,会清空此前写入的内联尺寸,恢复 CSS 默认。
|
|
19
|
+
*/
|
|
20
|
+
contentEl?: MaybeRefOrGetter<MaybeEl>;
|
|
21
|
+
/** 可选:首项前占位元素。语义同 `contentEl`,写 `beforeSize`。 */
|
|
22
|
+
beforeEl?: MaybeRefOrGetter<MaybeEl>;
|
|
23
|
+
/** 可选:末项后占位元素。语义同 `contentEl`,写 `afterSize`。 */
|
|
24
|
+
afterEl?: MaybeRefOrGetter<MaybeEl>;
|
|
25
|
+
}
|
|
26
|
+
interface UseVirtualizerReturned {
|
|
27
|
+
/**
|
|
28
|
+
* 底层 `Virtualizer` 实例。
|
|
29
|
+
*
|
|
30
|
+
* 未做包装;消费者可直接调用 `scrollToIndex` / `scrollToOffset` /
|
|
31
|
+
* `reset` / `measureMany` / `setOptions` / `measureElement` 等方法。
|
|
32
|
+
*/
|
|
33
|
+
virtualizer: Virtualizer$1;
|
|
34
|
+
/**
|
|
35
|
+
* 完整快照 shallowRef。任一结构性变化(items/range/isScrolling/各种 size)都会更新。
|
|
36
|
+
* 大多数消费者只需 `items` / `isScrolling`,此 ref 仅在需要 `range` / `totalSize` 等
|
|
37
|
+
* 其它字段时使用。
|
|
38
|
+
*/
|
|
39
|
+
snapshot: ShallowRef<VirtualSnapshot>;
|
|
40
|
+
/**
|
|
41
|
+
* 仅在底层 `items` 引用变化时才更新的 shallowRef。
|
|
42
|
+
*
|
|
43
|
+
* 独立于 `snapshot`,用于模板 `v-for`;这样 `isScrolling` 等字段的切换不会
|
|
44
|
+
* 连带触发 `items.map(...)` 的重新求值。
|
|
45
|
+
*/
|
|
46
|
+
items: ShallowRef<VirtualItem[]>;
|
|
47
|
+
/** 仅在布尔值变化时更新的 `isScrolling` 状态。 */
|
|
48
|
+
isScrolling: ShallowRef<boolean>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Vue 适配层:把 `@cat-kit/fe` 的 `Virtualizer` 生命周期与响应式系统对接。
|
|
52
|
+
*
|
|
53
|
+
* 设计原则:
|
|
54
|
+
* 1. **尺寸写入走 DOM**:`contentEl` / `beforeEl` / `afterEl` 一旦传入,
|
|
55
|
+
* hook 在 `subscribe` 回调中命令式写 `style.height`;消费者模板无需绑定
|
|
56
|
+
* `totalSize` / `beforeSize` / `afterSize`,滚动时 Vue 不会因尺寸变化重渲染。
|
|
57
|
+
* 2. **`items` / `isScrolling` 独立拆分**:底层 subscribe 一次回调可能同时包含
|
|
58
|
+
* items 变化与 isScrolling 切换;拆成两个 shallowRef 后,消费者 `v-for`
|
|
59
|
+
* 的 `computed(() => items.value.map(...))` 不会因 `isScrolling` 变化而重新执行。
|
|
60
|
+
* 3. **业务语义外置**:阈值判定 / key 组装 / `scrollToIndex` 对齐方式由消费者组装。
|
|
61
|
+
*
|
|
62
|
+
* 约束:
|
|
63
|
+
* - `initialOffset` / `initialViewport` 仅构造时生效,后续 `setOptions` 忽略(底层契约)。
|
|
64
|
+
* - 消费者若需运行时切换 `estimateSize` / `getItemKey` 等字段,调用 `virtualizer.setOptions(...)`。
|
|
65
|
+
*/
|
|
66
|
+
declare function useVirtualizer(options: UseVirtualizerOptions): UseVirtualizerReturned;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { type EstimateSize, type GetItemKey, UseVirtualizerOptions, UseVirtualizerReturned, type VirtualAlign, type VirtualItem, type VirtualMeasurement, type VirtualRange, type VirtualScrollOptions, type VirtualSnapshot, type Virtualizer$1 as Virtualizer, type VirtualizerOptions, useVirtualizer };
|
|
69
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { onScopeDispose, shallowRef, toValue, watch } from "vue";
|
|
2
|
+
import { Virtualizer } from "@cat-kit/fe";
|
|
3
|
+
//#region src/use-virtualizer/index.ts
|
|
4
|
+
function writeSize(el, size, horizontal) {
|
|
5
|
+
if (!el) return;
|
|
6
|
+
if (horizontal) el.style.width = `${size}px`;
|
|
7
|
+
else el.style.height = `${size}px`;
|
|
8
|
+
}
|
|
9
|
+
function clearSize(el, horizontal) {
|
|
10
|
+
if (!el) return;
|
|
11
|
+
if (horizontal) el.style.width = "";
|
|
12
|
+
else el.style.height = "";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Vue 适配层:把 `@cat-kit/fe` 的 `Virtualizer` 生命周期与响应式系统对接。
|
|
16
|
+
*
|
|
17
|
+
* 设计原则:
|
|
18
|
+
* 1. **尺寸写入走 DOM**:`contentEl` / `beforeEl` / `afterEl` 一旦传入,
|
|
19
|
+
* hook 在 `subscribe` 回调中命令式写 `style.height`;消费者模板无需绑定
|
|
20
|
+
* `totalSize` / `beforeSize` / `afterSize`,滚动时 Vue 不会因尺寸变化重渲染。
|
|
21
|
+
* 2. **`items` / `isScrolling` 独立拆分**:底层 subscribe 一次回调可能同时包含
|
|
22
|
+
* items 变化与 isScrolling 切换;拆成两个 shallowRef 后,消费者 `v-for`
|
|
23
|
+
* 的 `computed(() => items.value.map(...))` 不会因 `isScrolling` 变化而重新执行。
|
|
24
|
+
* 3. **业务语义外置**:阈值判定 / key 组装 / `scrollToIndex` 对齐方式由消费者组装。
|
|
25
|
+
*
|
|
26
|
+
* 约束:
|
|
27
|
+
* - `initialOffset` / `initialViewport` 仅构造时生效,后续 `setOptions` 忽略(底层契约)。
|
|
28
|
+
* - 消费者若需运行时切换 `estimateSize` / `getItemKey` 等字段,调用 `virtualizer.setOptions(...)`。
|
|
29
|
+
*/
|
|
30
|
+
function useVirtualizer(options) {
|
|
31
|
+
const { count, scrollEl, contentEl, beforeEl, afterEl, ...initOptions } = options;
|
|
32
|
+
const horizontal = initOptions.horizontal === true;
|
|
33
|
+
const virtualizer = new Virtualizer({
|
|
34
|
+
...initOptions,
|
|
35
|
+
count: count.value
|
|
36
|
+
});
|
|
37
|
+
const initialSnapshot = virtualizer.getSnapshot();
|
|
38
|
+
const snapshot = shallowRef(initialSnapshot);
|
|
39
|
+
const items = shallowRef(initialSnapshot.items);
|
|
40
|
+
const isScrolling = shallowRef(initialSnapshot.isScrolling);
|
|
41
|
+
function flushSizes(s) {
|
|
42
|
+
writeSize(toValue(contentEl) ?? null, s.totalSize, horizontal);
|
|
43
|
+
writeSize(toValue(beforeEl) ?? null, s.beforeSize, horizontal);
|
|
44
|
+
writeSize(toValue(afterEl) ?? null, s.afterSize, horizontal);
|
|
45
|
+
}
|
|
46
|
+
flushSizes(initialSnapshot);
|
|
47
|
+
const unsubscribe = virtualizer.subscribe((next) => {
|
|
48
|
+
snapshot.value = next;
|
|
49
|
+
if (next.items !== items.value) items.value = next.items;
|
|
50
|
+
if (next.isScrolling !== isScrolling.value) isScrolling.value = next.isScrolling;
|
|
51
|
+
flushSizes(next);
|
|
52
|
+
});
|
|
53
|
+
watch(count, (c) => {
|
|
54
|
+
virtualizer.setCount(c);
|
|
55
|
+
});
|
|
56
|
+
watch(() => toValue(scrollEl) ?? null, (el) => {
|
|
57
|
+
if (el) virtualizer.connect(el);
|
|
58
|
+
else virtualizer.disconnect();
|
|
59
|
+
}, { immediate: true });
|
|
60
|
+
function bindSizeSlot(source, pick) {
|
|
61
|
+
if (source === void 0) return;
|
|
62
|
+
watch(() => toValue(source) ?? null, (el, oldEl) => {
|
|
63
|
+
if (oldEl && oldEl !== el) clearSize(oldEl, horizontal);
|
|
64
|
+
if (el) writeSize(el, pick(snapshot.value), horizontal);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
bindSizeSlot(contentEl, (s) => s.totalSize);
|
|
68
|
+
bindSizeSlot(beforeEl, (s) => s.beforeSize);
|
|
69
|
+
bindSizeSlot(afterEl, (s) => s.afterSize);
|
|
70
|
+
onScopeDispose(() => {
|
|
71
|
+
unsubscribe();
|
|
72
|
+
virtualizer.destroy();
|
|
73
|
+
clearSize(toValue(contentEl) ?? null, horizontal);
|
|
74
|
+
clearSize(toValue(beforeEl) ?? null, horizontal);
|
|
75
|
+
clearSize(toValue(afterEl) ?? null, horizontal);
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
virtualizer,
|
|
79
|
+
snapshot,
|
|
80
|
+
items,
|
|
81
|
+
isScrolling
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { useVirtualizer };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-virtualizer/index.ts"],"sourcesContent":["import {\n Virtualizer,\n type EstimateSize,\n type GetItemKey,\n type VirtualAlign,\n type VirtualItem,\n type VirtualMeasurement,\n type VirtualRange,\n type VirtualScrollOptions,\n type VirtualSnapshot,\n type VirtualizerOptions\n} from '@cat-kit/fe'\nimport {\n onScopeDispose,\n shallowRef,\n toValue,\n watch,\n type MaybeRefOrGetter,\n type Ref,\n type ShallowRef\n} from 'vue'\n\nexport type {\n EstimateSize,\n GetItemKey,\n VirtualAlign,\n VirtualItem,\n VirtualMeasurement,\n VirtualRange,\n VirtualScrollOptions,\n VirtualSnapshot,\n Virtualizer,\n VirtualizerOptions\n}\n\ntype MaybeEl = HTMLElement | null | undefined\n\nexport interface UseVirtualizerOptions extends Omit<VirtualizerOptions, 'count'> {\n /** 响应式虚拟项总数;内部 `watch` 驱动 `virtualizer.setCount`。 */\n count: Ref<number>\n /**\n * 响应式滚动容器;变更时自动 `connect` / `disconnect`。\n * `immediate: true`,初始为 `null` 时等价于 `disconnect()`。\n */\n scrollEl: MaybeRefOrGetter<MaybeEl>\n /**\n * 可选:内容承接元素。hook 订阅到结构性更新时直接写入\n * `style.height = totalSize + 'px'`(水平模式为 `width`),\n * **不经过 Vue 响应式**——因此 `totalSize` 的变化不会触发模板重渲染。\n * 当引用切换为 `null` 时,会清空此前写入的内联尺寸,恢复 CSS 默认。\n */\n contentEl?: MaybeRefOrGetter<MaybeEl>\n /** 可选:首项前占位元素。语义同 `contentEl`,写 `beforeSize`。 */\n beforeEl?: MaybeRefOrGetter<MaybeEl>\n /** 可选:末项后占位元素。语义同 `contentEl`,写 `afterSize`。 */\n afterEl?: MaybeRefOrGetter<MaybeEl>\n}\n\nexport interface UseVirtualizerReturned {\n /**\n * 底层 `Virtualizer` 实例。\n *\n * 未做包装;消费者可直接调用 `scrollToIndex` / `scrollToOffset` /\n * `reset` / `measureMany` / `setOptions` / `measureElement` 等方法。\n */\n virtualizer: Virtualizer\n /**\n * 完整快照 shallowRef。任一结构性变化(items/range/isScrolling/各种 size)都会更新。\n * 大多数消费者只需 `items` / `isScrolling`,此 ref 仅在需要 `range` / `totalSize` 等\n * 其它字段时使用。\n */\n snapshot: ShallowRef<VirtualSnapshot>\n /**\n * 仅在底层 `items` 引用变化时才更新的 shallowRef。\n *\n * 独立于 `snapshot`,用于模板 `v-for`;这样 `isScrolling` 等字段的切换不会\n * 连带触发 `items.map(...)` 的重新求值。\n */\n items: ShallowRef<VirtualItem[]>\n /** 仅在布尔值变化时更新的 `isScrolling` 状态。 */\n isScrolling: ShallowRef<boolean>\n}\n\nfunction writeSize(el: MaybeEl, size: number, horizontal: boolean): void {\n if (!el) return\n if (horizontal) el.style.width = `${size}px`\n else el.style.height = `${size}px`\n}\n\nfunction clearSize(el: MaybeEl, horizontal: boolean): void {\n if (!el) return\n if (horizontal) el.style.width = ''\n else el.style.height = ''\n}\n\n/**\n * Vue 适配层:把 `@cat-kit/fe` 的 `Virtualizer` 生命周期与响应式系统对接。\n *\n * 设计原则:\n * 1. **尺寸写入走 DOM**:`contentEl` / `beforeEl` / `afterEl` 一旦传入,\n * hook 在 `subscribe` 回调中命令式写 `style.height`;消费者模板无需绑定\n * `totalSize` / `beforeSize` / `afterSize`,滚动时 Vue 不会因尺寸变化重渲染。\n * 2. **`items` / `isScrolling` 独立拆分**:底层 subscribe 一次回调可能同时包含\n * items 变化与 isScrolling 切换;拆成两个 shallowRef 后,消费者 `v-for`\n * 的 `computed(() => items.value.map(...))` 不会因 `isScrolling` 变化而重新执行。\n * 3. **业务语义外置**:阈值判定 / key 组装 / `scrollToIndex` 对齐方式由消费者组装。\n *\n * 约束:\n * - `initialOffset` / `initialViewport` 仅构造时生效,后续 `setOptions` 忽略(底层契约)。\n * - 消费者若需运行时切换 `estimateSize` / `getItemKey` 等字段,调用 `virtualizer.setOptions(...)`。\n */\nexport function useVirtualizer(options: UseVirtualizerOptions): UseVirtualizerReturned {\n const { count, scrollEl, contentEl, beforeEl, afterEl, ...initOptions } = options\n const horizontal = initOptions.horizontal === true\n\n const virtualizer = new Virtualizer({ ...initOptions, count: count.value })\n\n const initialSnapshot = virtualizer.getSnapshot()\n const snapshot = shallowRef<VirtualSnapshot>(initialSnapshot)\n const items = shallowRef<VirtualItem[]>(initialSnapshot.items)\n const isScrolling = shallowRef<boolean>(initialSnapshot.isScrolling)\n\n function flushSizes(s: VirtualSnapshot): void {\n writeSize(toValue(contentEl) ?? null, s.totalSize, horizontal)\n writeSize(toValue(beforeEl) ?? null, s.beforeSize, horizontal)\n writeSize(toValue(afterEl) ?? null, s.afterSize, horizontal)\n }\n\n // 初次同步,覆盖构造阶段 count > 0 场景(容器未 connect 时 totalSize 已有值)。\n flushSizes(initialSnapshot)\n\n const unsubscribe = virtualizer.subscribe((next) => {\n snapshot.value = next\n if (next.items !== items.value) items.value = next.items\n if (next.isScrolling !== isScrolling.value) isScrolling.value = next.isScrolling\n flushSizes(next)\n })\n\n watch(count, (c) => {\n virtualizer.setCount(c)\n })\n\n watch(\n () => toValue(scrollEl) ?? null,\n (el) => {\n if (el) virtualizer.connect(el)\n else virtualizer.disconnect()\n },\n { immediate: true }\n )\n\n // 元素引用切换时:清旧元素的内联尺寸 + 立即给新元素写入当前快照值。\n // 不向 hook 返回值暴露这些元素(消费者用 :ref 绑定自己的 ref 即可)。\n function bindSizeSlot(\n source: MaybeRefOrGetter<MaybeEl> | undefined,\n pick: (s: VirtualSnapshot) => number\n ): void {\n if (source === undefined) return\n watch(\n () => toValue(source) ?? null,\n (el, oldEl) => {\n if (oldEl && oldEl !== el) clearSize(oldEl, horizontal)\n if (el) writeSize(el, pick(snapshot.value), horizontal)\n }\n )\n }\n\n bindSizeSlot(contentEl, (s) => s.totalSize)\n bindSizeSlot(beforeEl, (s) => s.beforeSize)\n bindSizeSlot(afterEl, (s) => s.afterSize)\n\n onScopeDispose(() => {\n unsubscribe()\n virtualizer.destroy()\n clearSize(toValue(contentEl) ?? null, horizontal)\n clearSize(toValue(beforeEl) ?? null, horizontal)\n clearSize(toValue(afterEl) ?? null, horizontal)\n })\n\n return { virtualizer, snapshot, items, isScrolling }\n}\n"],"mappings":";;;AAmFA,SAAS,UAAU,IAAa,MAAc,YAA2B;AACvE,KAAI,CAAC,GAAI;AACT,KAAI,WAAY,IAAG,MAAM,QAAQ,GAAG,KAAK;KACpC,IAAG,MAAM,SAAS,GAAG,KAAK;;AAGjC,SAAS,UAAU,IAAa,YAA2B;AACzD,KAAI,CAAC,GAAI;AACT,KAAI,WAAY,IAAG,MAAM,QAAQ;KAC5B,IAAG,MAAM,SAAS;;;;;;;;;;;;;;;;;;AAmBzB,SAAgB,eAAe,SAAwD;CACrF,MAAM,EAAE,OAAO,UAAU,WAAW,UAAU,SAAS,GAAG,gBAAgB;CAC1E,MAAM,aAAa,YAAY,eAAe;CAE9C,MAAM,cAAc,IAAI,YAAY;EAAE,GAAG;EAAa,OAAO,MAAM;EAAO,CAAC;CAE3E,MAAM,kBAAkB,YAAY,aAAa;CACjD,MAAM,WAAW,WAA4B,gBAAgB;CAC7D,MAAM,QAAQ,WAA0B,gBAAgB,MAAM;CAC9D,MAAM,cAAc,WAAoB,gBAAgB,YAAY;CAEpE,SAAS,WAAW,GAA0B;AAC5C,YAAU,QAAQ,UAAU,IAAI,MAAM,EAAE,WAAW,WAAW;AAC9D,YAAU,QAAQ,SAAS,IAAI,MAAM,EAAE,YAAY,WAAW;AAC9D,YAAU,QAAQ,QAAQ,IAAI,MAAM,EAAE,WAAW,WAAW;;AAI9D,YAAW,gBAAgB;CAE3B,MAAM,cAAc,YAAY,WAAW,SAAS;AAClD,WAAS,QAAQ;AACjB,MAAI,KAAK,UAAU,MAAM,MAAO,OAAM,QAAQ,KAAK;AACnD,MAAI,KAAK,gBAAgB,YAAY,MAAO,aAAY,QAAQ,KAAK;AACrE,aAAW,KAAK;GAChB;AAEF,OAAM,QAAQ,MAAM;AAClB,cAAY,SAAS,EAAE;GACvB;AAEF,aACQ,QAAQ,SAAS,IAAI,OAC1B,OAAO;AACN,MAAI,GAAI,aAAY,QAAQ,GAAG;MAC1B,aAAY,YAAY;IAE/B,EAAE,WAAW,MAAM,CACpB;CAID,SAAS,aACP,QACA,MACM;AACN,MAAI,WAAW,KAAA,EAAW;AAC1B,cACQ,QAAQ,OAAO,IAAI,OACxB,IAAI,UAAU;AACb,OAAI,SAAS,UAAU,GAAI,WAAU,OAAO,WAAW;AACvD,OAAI,GAAI,WAAU,IAAI,KAAK,SAAS,MAAM,EAAE,WAAW;IAE1D;;AAGH,cAAa,YAAY,MAAM,EAAE,UAAU;AAC3C,cAAa,WAAW,MAAM,EAAE,WAAW;AAC3C,cAAa,UAAU,MAAM,EAAE,UAAU;AAEzC,sBAAqB;AACnB,eAAa;AACb,cAAY,SAAS;AACrB,YAAU,QAAQ,UAAU,IAAI,MAAM,WAAW;AACjD,YAAU,QAAQ,SAAS,IAAI,MAAM,WAAW;AAChD,YAAU,QAAQ,QAAQ,IAAI,MAAM,WAAW;GAC/C;AAEF,QAAO;EAAE;EAAa;EAAU;EAAO;EAAa"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/compositions",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src"
|
|
@@ -17,15 +17,12 @@
|
|
|
17
17
|
"check-types": "tsc -p tsconfig.json --noEmit"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@cat-kit/core": "^1.
|
|
20
|
+
"@cat-kit/core": "^1.1.1",
|
|
21
|
+
"@cat-kit/fe": "^1.1.1",
|
|
21
22
|
"@floating-ui/dom": "^1.7.6",
|
|
22
|
-
"@
|
|
23
|
-
"@veltra/utils": "1.0.8"
|
|
24
|
-
},
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"tsdown": "^0.21.7"
|
|
23
|
+
"@veltra/utils": "1.0.10"
|
|
27
24
|
},
|
|
28
25
|
"peerDependencies": {
|
|
29
|
-
"vue": "^3.5.
|
|
26
|
+
"vue": "^3.5.33"
|
|
30
27
|
}
|
|
31
28
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './use-focus'
|
|
|
10
10
|
|
|
11
11
|
export * from './use-form-component'
|
|
12
12
|
|
|
13
|
-
export * from './use-
|
|
13
|
+
export * from './use-user-action'
|
|
14
14
|
|
|
15
15
|
export * from './use-model'
|
|
16
16
|
|
|
@@ -22,4 +22,4 @@ export * from './use-resize-observer'
|
|
|
22
22
|
|
|
23
23
|
export * from './use-transition'
|
|
24
24
|
|
|
25
|
-
export * from './use-
|
|
25
|
+
export * from './use-virtualizer'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { nextTick } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type UserAction = <T extends (...args: any[]) => void | Promise<void>>(
|
|
4
|
+
fn: T
|
|
5
|
+
) => (...args: Parameters<T>) => Promise<void>
|
|
6
|
+
|
|
7
|
+
export interface UserActionResult {
|
|
8
|
+
isUserActive: () => boolean
|
|
9
|
+
userAction: UserAction
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 创建用户活动
|
|
14
|
+
* @description
|
|
15
|
+
* 解决"组件内部 emit 更新 → 外部 props 回传 → 监听 props 的副作用再次触发
|
|
16
|
+
* 内部状态变更"的循环更新问题。典型搭配:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const { userAction, isUserActive } = useUserAction()
|
|
20
|
+
*
|
|
21
|
+
* // 将函数标记为一个用户动作
|
|
22
|
+
* const handleSelect = userAction((date: Dater) => {
|
|
23
|
+
* currentDate.value = date
|
|
24
|
+
* emit('update:modelValue', date.format(fmt))
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* // modelValue 回显:用户活动期间跳过
|
|
28
|
+
* watch(() => props.modelValue, (v) => {
|
|
29
|
+
* if (isUserActive()) return
|
|
30
|
+
* currentDate.value = v ? date(v) : undefined
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function useUserAction(): UserActionResult {
|
|
35
|
+
let actionCount = 0
|
|
36
|
+
const isUserActive = () => {
|
|
37
|
+
return actionCount > 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const userAction: UserAction = (fn) => {
|
|
41
|
+
return async (...args) => {
|
|
42
|
+
actionCount++
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await fn(...args)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(error)
|
|
48
|
+
}
|
|
49
|
+
await nextTick()
|
|
50
|
+
|
|
51
|
+
actionCount--
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { isUserActive, userAction }
|
|
56
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Virtualizer,
|
|
3
|
+
type EstimateSize,
|
|
4
|
+
type GetItemKey,
|
|
5
|
+
type VirtualAlign,
|
|
6
|
+
type VirtualItem,
|
|
7
|
+
type VirtualMeasurement,
|
|
8
|
+
type VirtualRange,
|
|
9
|
+
type VirtualScrollOptions,
|
|
10
|
+
type VirtualSnapshot,
|
|
11
|
+
type VirtualizerOptions
|
|
12
|
+
} from '@cat-kit/fe'
|
|
13
|
+
import {
|
|
14
|
+
onScopeDispose,
|
|
15
|
+
shallowRef,
|
|
16
|
+
toValue,
|
|
17
|
+
watch,
|
|
18
|
+
type MaybeRefOrGetter,
|
|
19
|
+
type Ref,
|
|
20
|
+
type ShallowRef
|
|
21
|
+
} from 'vue'
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
EstimateSize,
|
|
25
|
+
GetItemKey,
|
|
26
|
+
VirtualAlign,
|
|
27
|
+
VirtualItem,
|
|
28
|
+
VirtualMeasurement,
|
|
29
|
+
VirtualRange,
|
|
30
|
+
VirtualScrollOptions,
|
|
31
|
+
VirtualSnapshot,
|
|
32
|
+
Virtualizer,
|
|
33
|
+
VirtualizerOptions
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type MaybeEl = HTMLElement | null | undefined
|
|
37
|
+
|
|
38
|
+
export interface UseVirtualizerOptions extends Omit<VirtualizerOptions, 'count'> {
|
|
39
|
+
/** 响应式虚拟项总数;内部 `watch` 驱动 `virtualizer.setCount`。 */
|
|
40
|
+
count: Ref<number>
|
|
41
|
+
/**
|
|
42
|
+
* 响应式滚动容器;变更时自动 `connect` / `disconnect`。
|
|
43
|
+
* `immediate: true`,初始为 `null` 时等价于 `disconnect()`。
|
|
44
|
+
*/
|
|
45
|
+
scrollEl: MaybeRefOrGetter<MaybeEl>
|
|
46
|
+
/**
|
|
47
|
+
* 可选:内容承接元素。hook 订阅到结构性更新时直接写入
|
|
48
|
+
* `style.height = totalSize + 'px'`(水平模式为 `width`),
|
|
49
|
+
* **不经过 Vue 响应式**——因此 `totalSize` 的变化不会触发模板重渲染。
|
|
50
|
+
* 当引用切换为 `null` 时,会清空此前写入的内联尺寸,恢复 CSS 默认。
|
|
51
|
+
*/
|
|
52
|
+
contentEl?: MaybeRefOrGetter<MaybeEl>
|
|
53
|
+
/** 可选:首项前占位元素。语义同 `contentEl`,写 `beforeSize`。 */
|
|
54
|
+
beforeEl?: MaybeRefOrGetter<MaybeEl>
|
|
55
|
+
/** 可选:末项后占位元素。语义同 `contentEl`,写 `afterSize`。 */
|
|
56
|
+
afterEl?: MaybeRefOrGetter<MaybeEl>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UseVirtualizerReturned {
|
|
60
|
+
/**
|
|
61
|
+
* 底层 `Virtualizer` 实例。
|
|
62
|
+
*
|
|
63
|
+
* 未做包装;消费者可直接调用 `scrollToIndex` / `scrollToOffset` /
|
|
64
|
+
* `reset` / `measureMany` / `setOptions` / `measureElement` 等方法。
|
|
65
|
+
*/
|
|
66
|
+
virtualizer: Virtualizer
|
|
67
|
+
/**
|
|
68
|
+
* 完整快照 shallowRef。任一结构性变化(items/range/isScrolling/各种 size)都会更新。
|
|
69
|
+
* 大多数消费者只需 `items` / `isScrolling`,此 ref 仅在需要 `range` / `totalSize` 等
|
|
70
|
+
* 其它字段时使用。
|
|
71
|
+
*/
|
|
72
|
+
snapshot: ShallowRef<VirtualSnapshot>
|
|
73
|
+
/**
|
|
74
|
+
* 仅在底层 `items` 引用变化时才更新的 shallowRef。
|
|
75
|
+
*
|
|
76
|
+
* 独立于 `snapshot`,用于模板 `v-for`;这样 `isScrolling` 等字段的切换不会
|
|
77
|
+
* 连带触发 `items.map(...)` 的重新求值。
|
|
78
|
+
*/
|
|
79
|
+
items: ShallowRef<VirtualItem[]>
|
|
80
|
+
/** 仅在布尔值变化时更新的 `isScrolling` 状态。 */
|
|
81
|
+
isScrolling: ShallowRef<boolean>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeSize(el: MaybeEl, size: number, horizontal: boolean): void {
|
|
85
|
+
if (!el) return
|
|
86
|
+
if (horizontal) el.style.width = `${size}px`
|
|
87
|
+
else el.style.height = `${size}px`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function clearSize(el: MaybeEl, horizontal: boolean): void {
|
|
91
|
+
if (!el) return
|
|
92
|
+
if (horizontal) el.style.width = ''
|
|
93
|
+
else el.style.height = ''
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Vue 适配层:把 `@cat-kit/fe` 的 `Virtualizer` 生命周期与响应式系统对接。
|
|
98
|
+
*
|
|
99
|
+
* 设计原则:
|
|
100
|
+
* 1. **尺寸写入走 DOM**:`contentEl` / `beforeEl` / `afterEl` 一旦传入,
|
|
101
|
+
* hook 在 `subscribe` 回调中命令式写 `style.height`;消费者模板无需绑定
|
|
102
|
+
* `totalSize` / `beforeSize` / `afterSize`,滚动时 Vue 不会因尺寸变化重渲染。
|
|
103
|
+
* 2. **`items` / `isScrolling` 独立拆分**:底层 subscribe 一次回调可能同时包含
|
|
104
|
+
* items 变化与 isScrolling 切换;拆成两个 shallowRef 后,消费者 `v-for`
|
|
105
|
+
* 的 `computed(() => items.value.map(...))` 不会因 `isScrolling` 变化而重新执行。
|
|
106
|
+
* 3. **业务语义外置**:阈值判定 / key 组装 / `scrollToIndex` 对齐方式由消费者组装。
|
|
107
|
+
*
|
|
108
|
+
* 约束:
|
|
109
|
+
* - `initialOffset` / `initialViewport` 仅构造时生效,后续 `setOptions` 忽略(底层契约)。
|
|
110
|
+
* - 消费者若需运行时切换 `estimateSize` / `getItemKey` 等字段,调用 `virtualizer.setOptions(...)`。
|
|
111
|
+
*/
|
|
112
|
+
export function useVirtualizer(options: UseVirtualizerOptions): UseVirtualizerReturned {
|
|
113
|
+
const { count, scrollEl, contentEl, beforeEl, afterEl, ...initOptions } = options
|
|
114
|
+
const horizontal = initOptions.horizontal === true
|
|
115
|
+
|
|
116
|
+
const virtualizer = new Virtualizer({ ...initOptions, count: count.value })
|
|
117
|
+
|
|
118
|
+
const initialSnapshot = virtualizer.getSnapshot()
|
|
119
|
+
const snapshot = shallowRef<VirtualSnapshot>(initialSnapshot)
|
|
120
|
+
const items = shallowRef<VirtualItem[]>(initialSnapshot.items)
|
|
121
|
+
const isScrolling = shallowRef<boolean>(initialSnapshot.isScrolling)
|
|
122
|
+
|
|
123
|
+
function flushSizes(s: VirtualSnapshot): void {
|
|
124
|
+
writeSize(toValue(contentEl) ?? null, s.totalSize, horizontal)
|
|
125
|
+
writeSize(toValue(beforeEl) ?? null, s.beforeSize, horizontal)
|
|
126
|
+
writeSize(toValue(afterEl) ?? null, s.afterSize, horizontal)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 初次同步,覆盖构造阶段 count > 0 场景(容器未 connect 时 totalSize 已有值)。
|
|
130
|
+
flushSizes(initialSnapshot)
|
|
131
|
+
|
|
132
|
+
const unsubscribe = virtualizer.subscribe((next) => {
|
|
133
|
+
snapshot.value = next
|
|
134
|
+
if (next.items !== items.value) items.value = next.items
|
|
135
|
+
if (next.isScrolling !== isScrolling.value) isScrolling.value = next.isScrolling
|
|
136
|
+
flushSizes(next)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
watch(count, (c) => {
|
|
140
|
+
virtualizer.setCount(c)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
watch(
|
|
144
|
+
() => toValue(scrollEl) ?? null,
|
|
145
|
+
(el) => {
|
|
146
|
+
if (el) virtualizer.connect(el)
|
|
147
|
+
else virtualizer.disconnect()
|
|
148
|
+
},
|
|
149
|
+
{ immediate: true }
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// 元素引用切换时:清旧元素的内联尺寸 + 立即给新元素写入当前快照值。
|
|
153
|
+
// 不向 hook 返回值暴露这些元素(消费者用 :ref 绑定自己的 ref 即可)。
|
|
154
|
+
function bindSizeSlot(
|
|
155
|
+
source: MaybeRefOrGetter<MaybeEl> | undefined,
|
|
156
|
+
pick: (s: VirtualSnapshot) => number
|
|
157
|
+
): void {
|
|
158
|
+
if (source === undefined) return
|
|
159
|
+
watch(
|
|
160
|
+
() => toValue(source) ?? null,
|
|
161
|
+
(el, oldEl) => {
|
|
162
|
+
if (oldEl && oldEl !== el) clearSize(oldEl, horizontal)
|
|
163
|
+
if (el) writeSize(el, pick(snapshot.value), horizontal)
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
bindSizeSlot(contentEl, (s) => s.totalSize)
|
|
169
|
+
bindSizeSlot(beforeEl, (s) => s.beforeSize)
|
|
170
|
+
bindSizeSlot(afterEl, (s) => s.afterSize)
|
|
171
|
+
|
|
172
|
+
onScopeDispose(() => {
|
|
173
|
+
unsubscribe()
|
|
174
|
+
virtualizer.destroy()
|
|
175
|
+
clearSize(toValue(contentEl) ?? null, horizontal)
|
|
176
|
+
clearSize(toValue(beforeEl) ?? null, horizontal)
|
|
177
|
+
clearSize(toValue(afterEl) ?? null, horizontal)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
return { virtualizer, snapshot, items, isScrolling }
|
|
181
|
+
}
|
package/dist/use-lock/index.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
//#region src/use-lock/index.d.ts
|
|
2
|
-
type Update = (fn: Function) => any;
|
|
3
|
-
type Lock = (fn: Function) => Promise<void>;
|
|
4
|
-
interface Updater {
|
|
5
|
-
/**
|
|
6
|
-
* 更新
|
|
7
|
-
* @description 在非锁定时执行传入的函数
|
|
8
|
-
*/
|
|
9
|
-
update: Update;
|
|
10
|
-
/**
|
|
11
|
-
* 更新并锁定
|
|
12
|
-
* @description 执行传入的函数,并锁定更新操作,直到函数执行完成
|
|
13
|
-
*/
|
|
14
|
-
updateAndLock: Lock;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* 数据更新锁
|
|
18
|
-
* @description
|
|
19
|
-
* 更新锁主要用于防止组件数据更新时,循环触发更新。
|
|
20
|
-
*
|
|
21
|
-
* @returns 该函数返回两个函数:
|
|
22
|
-
* 1. update: 更新函数,在非锁定时执行传入的函数
|
|
23
|
-
* 2. lock: 锁定函数,执行时会锁定更新操作,直到锁定操作结束
|
|
24
|
-
*/
|
|
25
|
-
declare function useUpdateLock(): Updater;
|
|
26
|
-
//#endregion
|
|
27
|
-
export { Updater, useUpdateLock };
|
|
28
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/use-lock/index.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { nextTick } from "vue";
|
|
2
|
-
//#region src/use-lock/index.ts
|
|
3
|
-
/**
|
|
4
|
-
* 数据更新锁
|
|
5
|
-
* @description
|
|
6
|
-
* 更新锁主要用于防止组件数据更新时,循环触发更新。
|
|
7
|
-
*
|
|
8
|
-
* @returns 该函数返回两个函数:
|
|
9
|
-
* 1. update: 更新函数,在非锁定时执行传入的函数
|
|
10
|
-
* 2. lock: 锁定函数,执行时会锁定更新操作,直到锁定操作结束
|
|
11
|
-
*/
|
|
12
|
-
function useUpdateLock() {
|
|
13
|
-
let lockedCount = 0;
|
|
14
|
-
function update(fn) {
|
|
15
|
-
if (lockedCount > 0) return;
|
|
16
|
-
return fn();
|
|
17
|
-
}
|
|
18
|
-
async function updateAndLock(fn) {
|
|
19
|
-
lockedCount++;
|
|
20
|
-
try {
|
|
21
|
-
await fn();
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error(error);
|
|
24
|
-
}
|
|
25
|
-
await nextTick();
|
|
26
|
-
lockedCount--;
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
updateAndLock,
|
|
30
|
-
update
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
34
|
-
export { useUpdateLock };
|
|
35
|
-
|
|
36
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-lock/index.ts"],"sourcesContent":["import { nextTick } from 'vue'\n\ntype Update = (fn: Function) => any\ntype Lock = (fn: Function) => Promise<void>\n\nexport interface Updater {\n /**\n * 更新\n * @description 在非锁定时执行传入的函数\n */\n update: Update\n /**\n * 更新并锁定\n * @description 执行传入的函数,并锁定更新操作,直到函数执行完成\n */\n updateAndLock: Lock\n}\n\n/**\n * 数据更新锁\n * @description\n * 更新锁主要用于防止组件数据更新时,循环触发更新。\n *\n * @returns 该函数返回两个函数:\n * 1. update: 更新函数,在非锁定时执行传入的函数\n * 2. lock: 锁定函数,执行时会锁定更新操作,直到锁定操作结束\n */\nexport function useUpdateLock(): Updater {\n let lockedCount = 0\n\n function update(fn: Function) {\n if (lockedCount > 0) return\n return fn()\n }\n\n async function updateAndLock(fn: Function) {\n lockedCount++\n\n try {\n await fn()\n } catch (error) {\n console.error(error)\n }\n await nextTick()\n\n lockedCount--\n }\n\n return { updateAndLock, update }\n}\n"],"mappings":";;;;;;;;;;;AA2BA,SAAgB,gBAAyB;CACvC,IAAI,cAAc;CAElB,SAAS,OAAO,IAAc;AAC5B,MAAI,cAAc,EAAG;AACrB,SAAO,IAAI;;CAGb,eAAe,cAAc,IAAc;AACzC;AAEA,MAAI;AACF,SAAM,IAAI;WACH,OAAO;AACd,WAAQ,MAAM,MAAM;;AAEtB,QAAM,UAAU;AAEhB;;AAGF,QAAO;EAAE;EAAe;EAAQ"}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { ComputedRef, Ref, ShallowRef } from "vue";
|
|
2
|
-
import { VirtualItem } from "@tanstack/vue-virtual";
|
|
3
|
-
|
|
4
|
-
//#region src/use-virtual/index.d.ts
|
|
5
|
-
interface Options {
|
|
6
|
-
/** 指定启用虚拟列表的阈值 */
|
|
7
|
-
virtualThreshold?: number | Ref<number | undefined>;
|
|
8
|
-
/** 数量 */
|
|
9
|
-
count: Ref<number>;
|
|
10
|
-
/** 滚动容器 */
|
|
11
|
-
scrollEl: ShallowRef<HTMLElement | null>;
|
|
12
|
-
/** 估算高度(宽度) */
|
|
13
|
-
estimateSize?: (index: number) => number;
|
|
14
|
-
/** 列表项之间的间距 */
|
|
15
|
-
gap?: number;
|
|
16
|
-
}
|
|
17
|
-
type CustomVirtualItem = Omit<VirtualItem, 'key'> & {
|
|
18
|
-
key: number | string;
|
|
19
|
-
};
|
|
20
|
-
type VirtualReturned = {
|
|
21
|
-
/** 虚拟列表 */virtualList: ShallowRef<CustomVirtualItem[]>; /** 总高度 */
|
|
22
|
-
totalHeight: ShallowRef<number>; /** 测量元素高度 */
|
|
23
|
-
measureElement: (el: any) => void; /** 滚动到指定索引 */
|
|
24
|
-
scrollTo: (index: number) => void; /** 是否启用虚拟列表 */
|
|
25
|
-
virtualEnabled: ComputedRef<boolean>;
|
|
26
|
-
};
|
|
27
|
-
declare function useVirtual(options: Options): VirtualReturned;
|
|
28
|
-
//#endregion
|
|
29
|
-
export { VirtualReturned, useVirtual };
|
|
30
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { computed, isRef, onScopeDispose, shallowRef, watch } from "vue";
|
|
2
|
-
import { Virtualizer, elementScroll, observeElementOffset, observeElementRect } from "@tanstack/vue-virtual";
|
|
3
|
-
//#region src/use-virtual/index.ts
|
|
4
|
-
const defaultEstimateSize = () => 34;
|
|
5
|
-
function useVirtual(options) {
|
|
6
|
-
const { count, scrollEl, estimateSize, virtualThreshold, gap } = options;
|
|
7
|
-
const enabled = computed(() => {
|
|
8
|
-
if (isRef(virtualThreshold)) return virtualThreshold.value ? count.value > virtualThreshold.value : true;
|
|
9
|
-
return virtualThreshold ? count.value > virtualThreshold : true;
|
|
10
|
-
});
|
|
11
|
-
const virtualList = shallowRef([]);
|
|
12
|
-
/** 总高度 */
|
|
13
|
-
const totalHeight = shallowRef(0);
|
|
14
|
-
function updateVirtualList() {
|
|
15
|
-
if (enabled.value) {
|
|
16
|
-
totalHeight.value = v.getTotalSize();
|
|
17
|
-
virtualList.value = v.getVirtualItems();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const virtualizerOptions = computed(() => {
|
|
21
|
-
return {
|
|
22
|
-
enabled: enabled.value,
|
|
23
|
-
count: count.value,
|
|
24
|
-
getScrollElement: () => scrollEl.value,
|
|
25
|
-
estimateSize: estimateSize ?? defaultEstimateSize,
|
|
26
|
-
overscan: 3,
|
|
27
|
-
gap,
|
|
28
|
-
observeElementRect,
|
|
29
|
-
observeElementOffset,
|
|
30
|
-
scrollToFn: elementScroll,
|
|
31
|
-
onChange: updateVirtualList
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
const v = new Virtualizer(virtualizerOptions.value);
|
|
35
|
-
updateVirtualList();
|
|
36
|
-
const cleanup = v._didMount();
|
|
37
|
-
watch(scrollEl, (el) => {
|
|
38
|
-
el && v._willUpdate();
|
|
39
|
-
}, { immediate: true });
|
|
40
|
-
watch(() => virtualizerOptions.value, (o) => {
|
|
41
|
-
v.setOptions(o);
|
|
42
|
-
v._willUpdate();
|
|
43
|
-
updateVirtualList();
|
|
44
|
-
});
|
|
45
|
-
onScopeDispose(() => {
|
|
46
|
-
cleanup();
|
|
47
|
-
});
|
|
48
|
-
function scrollTo(index) {
|
|
49
|
-
v.scrollToIndex(index, { align: "center" });
|
|
50
|
-
}
|
|
51
|
-
/** 测量元素高度 */
|
|
52
|
-
function measureElement(el) {
|
|
53
|
-
if (!el) return;
|
|
54
|
-
v.measureElement(el);
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
virtualEnabled: enabled,
|
|
58
|
-
virtualList,
|
|
59
|
-
totalHeight,
|
|
60
|
-
measureElement,
|
|
61
|
-
scrollTo
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
//#endregion
|
|
65
|
-
export { useVirtual };
|
|
66
|
-
|
|
67
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/use-virtual/index.ts"],"sourcesContent":["import {\n elementScroll,\n observeElementOffset,\n observeElementRect,\n type VirtualItem,\n Virtualizer\n} from '@tanstack/vue-virtual'\nimport {\n computed,\n isRef,\n onScopeDispose,\n shallowRef,\n watch,\n type ComputedRef,\n type Ref,\n type ShallowRef\n} from 'vue'\n\ninterface Options {\n /** 指定启用虚拟列表的阈值 */\n virtualThreshold?: number | Ref<number | undefined>\n /** 数量 */\n count: Ref<number>\n /** 滚动容器 */\n scrollEl: ShallowRef<HTMLElement | null>\n /** 估算高度(宽度) */\n estimateSize?: (index: number) => number\n /** 列表项之间的间距 */\n gap?: number\n}\n\ntype CustomVirtualItem = Omit<VirtualItem, 'key'> & { key: number | string }\n\nexport type VirtualReturned = {\n /** 虚拟列表 */\n virtualList: ShallowRef<CustomVirtualItem[]>\n /** 总高度 */\n totalHeight: ShallowRef<number>\n /** 测量元素高度 */\n measureElement: (el: any) => void\n /** 滚动到指定索引 */\n scrollTo: (index: number) => void\n /** 是否启用虚拟列表 */\n virtualEnabled: ComputedRef<boolean>\n}\n\nconst defaultEstimateSize = () => 34\n\nexport function useVirtual(options: Options): VirtualReturned {\n const { count, scrollEl, estimateSize, virtualThreshold, gap } = options\n\n const enabled = computed(() => {\n if (isRef(virtualThreshold)) {\n return virtualThreshold.value ? count.value > virtualThreshold.value : true\n }\n\n return virtualThreshold ? count.value > virtualThreshold : true\n })\n\n const virtualList = shallowRef<CustomVirtualItem[]>([])\n\n /** 总高度 */\n const totalHeight = shallowRef(0)\n\n function updateVirtualList() {\n if (enabled.value) {\n totalHeight.value = v.getTotalSize()\n virtualList.value = v.getVirtualItems() as CustomVirtualItem[]\n }\n }\n\n const virtualizerOptions = computed(() => {\n return {\n enabled: enabled.value,\n count: count.value,\n getScrollElement: () => scrollEl.value,\n estimateSize: estimateSize ?? defaultEstimateSize,\n overscan: 3,\n gap,\n observeElementRect: observeElementRect,\n observeElementOffset: observeElementOffset,\n scrollToFn: elementScroll,\n onChange: updateVirtualList\n }\n })\n\n const v = new Virtualizer(virtualizerOptions.value)\n\n updateVirtualList()\n\n const cleanup = v._didMount()\n\n watch(\n scrollEl,\n (el) => {\n el && v._willUpdate()\n },\n { immediate: true }\n )\n\n watch(\n () => virtualizerOptions.value,\n (o) => {\n v.setOptions(o)\n\n v._willUpdate()\n\n updateVirtualList()\n }\n )\n\n onScopeDispose(() => {\n cleanup()\n })\n\n function scrollTo(index: number) {\n v.scrollToIndex(index, { align: 'center' })\n }\n\n /** 测量元素高度 */\n function measureElement(el: Element) {\n if (!el) return\n\n v.measureElement(el)\n\n return undefined\n }\n\n return { virtualEnabled: enabled, virtualList, totalHeight, measureElement, scrollTo }\n}\n"],"mappings":";;;AA8CA,MAAM,4BAA4B;AAElC,SAAgB,WAAW,SAAmC;CAC5D,MAAM,EAAE,OAAO,UAAU,cAAc,kBAAkB,QAAQ;CAEjE,MAAM,UAAU,eAAe;AAC7B,MAAI,MAAM,iBAAiB,CACzB,QAAO,iBAAiB,QAAQ,MAAM,QAAQ,iBAAiB,QAAQ;AAGzE,SAAO,mBAAmB,MAAM,QAAQ,mBAAmB;GAC3D;CAEF,MAAM,cAAc,WAAgC,EAAE,CAAC;;CAGvD,MAAM,cAAc,WAAW,EAAE;CAEjC,SAAS,oBAAoB;AAC3B,MAAI,QAAQ,OAAO;AACjB,eAAY,QAAQ,EAAE,cAAc;AACpC,eAAY,QAAQ,EAAE,iBAAiB;;;CAI3C,MAAM,qBAAqB,eAAe;AACxC,SAAO;GACL,SAAS,QAAQ;GACjB,OAAO,MAAM;GACb,wBAAwB,SAAS;GACjC,cAAc,gBAAgB;GAC9B,UAAU;GACV;GACoB;GACE;GACtB,YAAY;GACZ,UAAU;GACX;GACD;CAEF,MAAM,IAAI,IAAI,YAAY,mBAAmB,MAAM;AAEnD,oBAAmB;CAEnB,MAAM,UAAU,EAAE,WAAW;AAE7B,OACE,WACC,OAAO;AACN,QAAM,EAAE,aAAa;IAEvB,EAAE,WAAW,MAAM,CACpB;AAED,aACQ,mBAAmB,QACxB,MAAM;AACL,IAAE,WAAW,EAAE;AAEf,IAAE,aAAa;AAEf,qBAAmB;GAEtB;AAED,sBAAqB;AACnB,WAAS;GACT;CAEF,SAAS,SAAS,OAAe;AAC/B,IAAE,cAAc,OAAO,EAAE,OAAO,UAAU,CAAC;;;CAI7C,SAAS,eAAe,IAAa;AACnC,MAAI,CAAC,GAAI;AAET,IAAE,eAAe,GAAG;;AAKtB,QAAO;EAAE,gBAAgB;EAAS;EAAa;EAAa;EAAgB;EAAU"}
|
package/src/use-lock/index.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { nextTick } from 'vue'
|
|
2
|
-
|
|
3
|
-
type Update = (fn: Function) => any
|
|
4
|
-
type Lock = (fn: Function) => Promise<void>
|
|
5
|
-
|
|
6
|
-
export interface Updater {
|
|
7
|
-
/**
|
|
8
|
-
* 更新
|
|
9
|
-
* @description 在非锁定时执行传入的函数
|
|
10
|
-
*/
|
|
11
|
-
update: Update
|
|
12
|
-
/**
|
|
13
|
-
* 更新并锁定
|
|
14
|
-
* @description 执行传入的函数,并锁定更新操作,直到函数执行完成
|
|
15
|
-
*/
|
|
16
|
-
updateAndLock: Lock
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 数据更新锁
|
|
21
|
-
* @description
|
|
22
|
-
* 更新锁主要用于防止组件数据更新时,循环触发更新。
|
|
23
|
-
*
|
|
24
|
-
* @returns 该函数返回两个函数:
|
|
25
|
-
* 1. update: 更新函数,在非锁定时执行传入的函数
|
|
26
|
-
* 2. lock: 锁定函数,执行时会锁定更新操作,直到锁定操作结束
|
|
27
|
-
*/
|
|
28
|
-
export function useUpdateLock(): Updater {
|
|
29
|
-
let lockedCount = 0
|
|
30
|
-
|
|
31
|
-
function update(fn: Function) {
|
|
32
|
-
if (lockedCount > 0) return
|
|
33
|
-
return fn()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function updateAndLock(fn: Function) {
|
|
37
|
-
lockedCount++
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
await fn()
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(error)
|
|
43
|
-
}
|
|
44
|
-
await nextTick()
|
|
45
|
-
|
|
46
|
-
lockedCount--
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { updateAndLock, update }
|
|
50
|
-
}
|
package/src/use-virtual/index.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
elementScroll,
|
|
3
|
-
observeElementOffset,
|
|
4
|
-
observeElementRect,
|
|
5
|
-
type VirtualItem,
|
|
6
|
-
Virtualizer
|
|
7
|
-
} from '@tanstack/vue-virtual'
|
|
8
|
-
import {
|
|
9
|
-
computed,
|
|
10
|
-
isRef,
|
|
11
|
-
onScopeDispose,
|
|
12
|
-
shallowRef,
|
|
13
|
-
watch,
|
|
14
|
-
type ComputedRef,
|
|
15
|
-
type Ref,
|
|
16
|
-
type ShallowRef
|
|
17
|
-
} from 'vue'
|
|
18
|
-
|
|
19
|
-
interface Options {
|
|
20
|
-
/** 指定启用虚拟列表的阈值 */
|
|
21
|
-
virtualThreshold?: number | Ref<number | undefined>
|
|
22
|
-
/** 数量 */
|
|
23
|
-
count: Ref<number>
|
|
24
|
-
/** 滚动容器 */
|
|
25
|
-
scrollEl: ShallowRef<HTMLElement | null>
|
|
26
|
-
/** 估算高度(宽度) */
|
|
27
|
-
estimateSize?: (index: number) => number
|
|
28
|
-
/** 列表项之间的间距 */
|
|
29
|
-
gap?: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type CustomVirtualItem = Omit<VirtualItem, 'key'> & { key: number | string }
|
|
33
|
-
|
|
34
|
-
export type VirtualReturned = {
|
|
35
|
-
/** 虚拟列表 */
|
|
36
|
-
virtualList: ShallowRef<CustomVirtualItem[]>
|
|
37
|
-
/** 总高度 */
|
|
38
|
-
totalHeight: ShallowRef<number>
|
|
39
|
-
/** 测量元素高度 */
|
|
40
|
-
measureElement: (el: any) => void
|
|
41
|
-
/** 滚动到指定索引 */
|
|
42
|
-
scrollTo: (index: number) => void
|
|
43
|
-
/** 是否启用虚拟列表 */
|
|
44
|
-
virtualEnabled: ComputedRef<boolean>
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const defaultEstimateSize = () => 34
|
|
48
|
-
|
|
49
|
-
export function useVirtual(options: Options): VirtualReturned {
|
|
50
|
-
const { count, scrollEl, estimateSize, virtualThreshold, gap } = options
|
|
51
|
-
|
|
52
|
-
const enabled = computed(() => {
|
|
53
|
-
if (isRef(virtualThreshold)) {
|
|
54
|
-
return virtualThreshold.value ? count.value > virtualThreshold.value : true
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return virtualThreshold ? count.value > virtualThreshold : true
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const virtualList = shallowRef<CustomVirtualItem[]>([])
|
|
61
|
-
|
|
62
|
-
/** 总高度 */
|
|
63
|
-
const totalHeight = shallowRef(0)
|
|
64
|
-
|
|
65
|
-
function updateVirtualList() {
|
|
66
|
-
if (enabled.value) {
|
|
67
|
-
totalHeight.value = v.getTotalSize()
|
|
68
|
-
virtualList.value = v.getVirtualItems() as CustomVirtualItem[]
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const virtualizerOptions = computed(() => {
|
|
73
|
-
return {
|
|
74
|
-
enabled: enabled.value,
|
|
75
|
-
count: count.value,
|
|
76
|
-
getScrollElement: () => scrollEl.value,
|
|
77
|
-
estimateSize: estimateSize ?? defaultEstimateSize,
|
|
78
|
-
overscan: 3,
|
|
79
|
-
gap,
|
|
80
|
-
observeElementRect: observeElementRect,
|
|
81
|
-
observeElementOffset: observeElementOffset,
|
|
82
|
-
scrollToFn: elementScroll,
|
|
83
|
-
onChange: updateVirtualList
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const v = new Virtualizer(virtualizerOptions.value)
|
|
88
|
-
|
|
89
|
-
updateVirtualList()
|
|
90
|
-
|
|
91
|
-
const cleanup = v._didMount()
|
|
92
|
-
|
|
93
|
-
watch(
|
|
94
|
-
scrollEl,
|
|
95
|
-
(el) => {
|
|
96
|
-
el && v._willUpdate()
|
|
97
|
-
},
|
|
98
|
-
{ immediate: true }
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
watch(
|
|
102
|
-
() => virtualizerOptions.value,
|
|
103
|
-
(o) => {
|
|
104
|
-
v.setOptions(o)
|
|
105
|
-
|
|
106
|
-
v._willUpdate()
|
|
107
|
-
|
|
108
|
-
updateVirtualList()
|
|
109
|
-
}
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
onScopeDispose(() => {
|
|
113
|
-
cleanup()
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
function scrollTo(index: number) {
|
|
117
|
-
v.scrollToIndex(index, { align: 'center' })
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** 测量元素高度 */
|
|
121
|
-
function measureElement(el: Element) {
|
|
122
|
-
if (!el) return
|
|
123
|
-
|
|
124
|
-
v.measureElement(el)
|
|
125
|
-
|
|
126
|
-
return undefined
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { virtualEnabled: enabled, virtualList, totalHeight, measureElement, scrollTo }
|
|
130
|
-
}
|