@veltra/compositions 1.0.0

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.
Files changed (67) hide show
  1. package/dist/index.d.ts +14 -0
  2. package/dist/index.js +14 -0
  3. package/dist/use-component-props/index.d.ts +12 -0
  4. package/dist/use-component-props/index.js +52 -0
  5. package/dist/use-component-props/index.js.map +1 -0
  6. package/dist/use-config/index.d.ts +25 -0
  7. package/dist/use-config/index.js +51 -0
  8. package/dist/use-config/index.js.map +1 -0
  9. package/dist/use-drag/index.d.ts +47 -0
  10. package/dist/use-drag/index.js +87 -0
  11. package/dist/use-drag/index.js.map +1 -0
  12. package/dist/use-fallback-props/index.d.ts +31 -0
  13. package/dist/use-fallback-props/index.js +35 -0
  14. package/dist/use-fallback-props/index.js.map +1 -0
  15. package/dist/use-focus/index.d.ts +16 -0
  16. package/dist/use-focus/index.js +27 -0
  17. package/dist/use-focus/index.js.map +1 -0
  18. package/dist/use-form-component/index.d.ts +22 -0
  19. package/dist/use-form-component/index.js +15 -0
  20. package/dist/use-form-component/index.js.map +1 -0
  21. package/dist/use-lock/index.d.ts +28 -0
  22. package/dist/use-lock/index.js +36 -0
  23. package/dist/use-lock/index.js.map +1 -0
  24. package/dist/use-model/index.d.ts +35 -0
  25. package/dist/use-model/index.js +46 -0
  26. package/dist/use-model/index.js.map +1 -0
  27. package/dist/use-pop/index.d.ts +53 -0
  28. package/dist/use-pop/index.js +121 -0
  29. package/dist/use-pop/index.js.map +1 -0
  30. package/dist/use-reactive-size/index.d.ts +17 -0
  31. package/dist/use-reactive-size/index.js +40 -0
  32. package/dist/use-reactive-size/index.js.map +1 -0
  33. package/dist/use-resize-observer/index.d.ts +33 -0
  34. package/dist/use-resize-observer/index.js +92 -0
  35. package/dist/use-resize-observer/index.js.map +1 -0
  36. package/dist/use-transition/index.d.ts +18 -0
  37. package/dist/use-transition/index.js +11 -0
  38. package/dist/use-transition/index.js.map +1 -0
  39. package/dist/use-transition/type.d.ts +47 -0
  40. package/dist/use-transition/use-css-transition.js +129 -0
  41. package/dist/use-transition/use-css-transition.js.map +1 -0
  42. package/dist/use-transition/use-style-transition.js +127 -0
  43. package/dist/use-transition/use-style-transition.js.map +1 -0
  44. package/dist/use-transition/utils.js +41 -0
  45. package/dist/use-transition/utils.js.map +1 -0
  46. package/dist/use-virtual/index.d.ts +30 -0
  47. package/dist/use-virtual/index.js +67 -0
  48. package/dist/use-virtual/index.js.map +1 -0
  49. package/package.json +32 -0
  50. package/src/index.ts +25 -0
  51. package/src/use-component-props/index.ts +63 -0
  52. package/src/use-config/index.ts +77 -0
  53. package/src/use-drag/index.ts +151 -0
  54. package/src/use-fallback-props/index.ts +76 -0
  55. package/src/use-focus/index.ts +26 -0
  56. package/src/use-form-component/index.ts +31 -0
  57. package/src/use-lock/index.ts +50 -0
  58. package/src/use-model/index.ts +96 -0
  59. package/src/use-pop/index.ts +206 -0
  60. package/src/use-reactive-size/index.ts +53 -0
  61. package/src/use-resize-observer/index.ts +148 -0
  62. package/src/use-transition/index.ts +22 -0
  63. package/src/use-transition/type.ts +50 -0
  64. package/src/use-transition/use-css-transition.ts +186 -0
  65. package/src/use-transition/use-style-transition.ts +182 -0
  66. package/src/use-transition/utils.ts +56 -0
  67. package/src/use-virtual/index.ts +130 -0
@@ -0,0 +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;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,QAAQ,CACJ;GAChB;AAEF,QAAO;EACL;EACA,QAAQ;AACN,UAAO,KAAK;;EAEd,QAAQ;AACN,UAAO,MAAM;;EAEhB"}
@@ -0,0 +1,127 @@
1
+ import { watchTransition } from "./utils.js";
2
+ import { createToggle, nextFrame, setStyles } from "@veltra/utils";
3
+ import { isRef, watch } from "vue";
4
+ //#region src/use-transition/use-style-transition.ts
5
+ function useStyleTransition(options) {
6
+ const { enterTo, enterActive, leaveActive, target, afterEnter, afterLeave, enterCanceled, leaveCanceled } = options;
7
+ const getDom = () => isRef(target) ? target.value : target;
8
+ /** 进入动画前的初始状态 */
9
+ const transitionOriginStyle = {};
10
+ /** 获取过渡样式的初始样式 */
11
+ const getOriginStyles = (styles) => {
12
+ return Object.fromEntries(Object.keys(styles).map((key) => {
13
+ return [key, transitionOriginStyle[key]];
14
+ }));
15
+ };
16
+ watch(() => getDom(), (dom) => {
17
+ if (dom) {
18
+ const map = dom.attributeStyleMap;
19
+ ~[...Object.keys(enterTo), ...Object.keys(enterActive)].forEach((key) => {
20
+ transitionOriginStyle[key] = map.get(key);
21
+ });
22
+ } else Object.keys(transitionOriginStyle).forEach((key) => {
23
+ delete transitionOriginStyle[key];
24
+ });
25
+ }, { immediate: true });
26
+ /**
27
+ * 添加过渡进入时并持续时的样式
28
+ * @param dom 元素
29
+ */
30
+ const addEnterActive = (dom) => {
31
+ setStyles(dom, enterActive);
32
+ };
33
+ /**
34
+ * 移除过渡进入时并持续时的样式
35
+ * @param dom 元素
36
+ */
37
+ const removeEnterActive = (dom) => {
38
+ setStyles(dom, getOriginStyles(enterActive));
39
+ };
40
+ /**
41
+ * 添加过渡离开并持续时的样式
42
+ * @param dom 元素
43
+ */
44
+ const addLeaveActive = (dom) => {
45
+ setStyles(dom, leaveActive);
46
+ };
47
+ /**
48
+ * 移除过渡离开并持续时的样式
49
+ * @param dom 元素
50
+ */
51
+ const removeLeaveActive = (dom) => {
52
+ setStyles(dom, getOriginStyles(leaveActive));
53
+ };
54
+ /**
55
+ * 添加过渡目标样式
56
+ * @param dom 元素
57
+ */
58
+ /**
59
+ * 移除过渡目标样式
60
+ * @param dom 元素
61
+ */
62
+ /**
63
+ * 添加过渡目标样式
64
+ * @param dom 元素
65
+ */
66
+ const addEnterToStyle = (dom) => {
67
+ setStyles(dom, enterTo);
68
+ };
69
+ /**
70
+ * 移除过渡目标样式
71
+ * @param dom 元素
72
+ */
73
+ const removeEnterToStyle = (dom) => {
74
+ setStyles(dom, getOriginStyles(enterTo));
75
+ };
76
+ /** 开始进入动画 */
77
+ const startEnter = () => {
78
+ const dom = getDom();
79
+ if (!dom) return;
80
+ addEnterActive(dom);
81
+ nextFrame(() => {
82
+ addEnterToStyle(dom);
83
+ });
84
+ };
85
+ /** 开始离开动画 */
86
+ const startLeave = () => {
87
+ const dom = getDom();
88
+ if (!dom) return;
89
+ addLeaveActive(dom);
90
+ nextFrame(() => {
91
+ removeEnterToStyle(dom);
92
+ });
93
+ };
94
+ const [active, toggle] = createToggle(false, (active) => {
95
+ active ? startEnter() : startLeave();
96
+ });
97
+ watchTransition(getDom, {
98
+ styleKeys: Object.keys(enterTo),
99
+ onCancel(el) {
100
+ if (active.value) {
101
+ removeEnterActive(el);
102
+ enterCanceled?.();
103
+ } else {
104
+ removeLeaveActive(el);
105
+ leaveCanceled?.();
106
+ }
107
+ },
108
+ onEnd(el) {
109
+ if (active.value) {
110
+ removeEnterActive(el);
111
+ afterEnter?.();
112
+ } else {
113
+ removeLeaveActive(el);
114
+ afterLeave?.();
115
+ }
116
+ }
117
+ });
118
+ return {
119
+ toggle,
120
+ enter: () => toggle(true),
121
+ leave: () => toggle(false)
122
+ };
123
+ }
124
+ //#endregion
125
+ export { useStyleTransition };
126
+
127
+ //# sourceMappingURL=use-style-transition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-style-transition.js","names":[],"sources":["../../src/use-transition/use-style-transition.ts"],"sourcesContent":["import { createToggle, nextFrame, setStyles } from '@veltra/utils'\nimport { isRef, watch, type CSSProperties } from 'vue'\n\nimport type { Returned, StyleTransitionOptions } from './type'\nimport { watchTransition } from './utils'\n\nexport function useStyleTransition(options: StyleTransitionOptions): Returned {\n const {\n // enterFrom,\n // leaveTo,\n enterTo,\n enterActive,\n leaveActive,\n target,\n afterEnter,\n afterLeave,\n enterCanceled,\n leaveCanceled\n } = options\n\n const getDom = (): HTMLElement | undefined => (isRef(target) ? target.value : target)\n\n /** 进入动画前的初始状态 */\n const transitionOriginStyle: CSSProperties = {}\n\n /** 获取过渡样式的初始样式 */\n const getOriginStyles = (styles: CSSProperties) => {\n return Object.fromEntries(\n Object.keys(styles).map((key) => {\n return [key, transitionOriginStyle[key as keyof CSSProperties]]\n })\n )\n }\n // 监听dom并获取dom在进入动画之前的样式\n // ...Object.keys(enterFrom ?? {}),\n // ...Object.keys(leaveTo ?? {}),\n watch(\n () => getDom(),\n (dom) => {\n if (dom) {\n const map = dom.attributeStyleMap\n ~[...Object.keys(enterTo), ...Object.keys(enterActive)].forEach((key) => {\n transitionOriginStyle[key] = map.get(key)\n })\n } else {\n Object.keys(transitionOriginStyle).forEach((key) => {\n delete transitionOriginStyle[key as keyof CSSProperties]\n })\n }\n },\n { immediate: true }\n )\n\n /**\n * 添加过渡进入时并持续时的样式\n * @param dom 元素\n */\n const addEnterActive = (dom: HTMLElement) => {\n setStyles(dom, enterActive)\n }\n\n /**\n * 移除过渡进入时并持续时的样式\n * @param dom 元素\n */\n const removeEnterActive = (dom: HTMLElement) => {\n setStyles(dom, getOriginStyles(enterActive))\n }\n\n /**\n * 添加过渡离开并持续时的样式\n * @param dom 元素\n */\n const addLeaveActive = (dom: HTMLElement) => {\n setStyles(dom, leaveActive)\n }\n\n /**\n * 移除过渡离开并持续时的样式\n * @param dom 元素\n */\n const removeLeaveActive = (dom: HTMLElement) => {\n setStyles(dom, getOriginStyles(leaveActive))\n }\n\n /**\n * 添加过渡目标样式\n * @param dom 元素\n */\n // const addEnterFromStyle = (dom: HTMLElement) => {\n // enterFrom && setStyles(dom, enterFrom)\n // }\n\n /**\n * 移除过渡目标样式\n * @param dom 元素\n */\n // const removeEnterFromStyle = (dom: HTMLElement) => {\n // if (!enterFrom) return\n\n // const canRemovedStyles = {}\n\n // for (const key in enterFrom) {\n // if (key in enterTo) continue\n // canRemovedStyles[key] = enterFrom[key]\n // }\n\n // setStyles(dom, getOriginStyles(canRemovedStyles))\n // }\n /**\n * 添加过渡目标样式\n * @param dom 元素\n */\n const addEnterToStyle = (dom: HTMLElement) => {\n setStyles(dom, enterTo)\n }\n\n /**\n * 移除过渡目标样式\n * @param dom 元素\n */\n const removeEnterToStyle = (dom: HTMLElement) => {\n setStyles(dom, getOriginStyles(enterTo))\n }\n\n /** 开始进入动画 */\n const startEnter = () => {\n const dom = getDom()\n if (!dom) return\n addEnterActive(dom)\n // 在下一帧插入动画运动目标状态\n nextFrame(() => {\n addEnterToStyle(dom)\n })\n }\n\n /** 开始离开动画 */\n const startLeave = () => {\n const dom = getDom()\n if (!dom) return\n\n // 标记动画进入离开状态\n addLeaveActive(dom)\n\n // 在下一帧移除动画运动目标状态恢复原状或者应用新的状态\n nextFrame(() => {\n removeEnterToStyle(dom)\n })\n }\n\n const [active, toggle] = createToggle(false, (active) => {\n active ? startEnter() : startLeave()\n })\n\n watchTransition(getDom, {\n styleKeys: Object.keys(enterTo),\n onCancel(el) {\n // 激活状态,移除enter-active类\n if (active.value) {\n removeEnterActive(el)\n enterCanceled?.()\n } else {\n removeLeaveActive(el)\n leaveCanceled?.()\n }\n },\n\n onEnd(el) {\n if (active.value) {\n removeEnterActive(el)\n // removeEnterFromStyle(el)\n afterEnter?.()\n } else {\n removeLeaveActive(el)\n\n afterLeave?.()\n }\n }\n })\n\n return { toggle, enter: () => toggle(true), leave: () => toggle(false) }\n}\n"],"mappings":";;;;AAMA,SAAgB,mBAAmB,SAA2C;CAC5E,MAAM,EAGJ,SACA,aACA,aACA,QACA,YACA,YACA,eACA,kBACE;CAEJ,MAAM,eAAyC,MAAM,OAAO,GAAG,OAAO,QAAQ;;CAG9E,MAAM,wBAAuC,EAAE;;CAG/C,MAAM,mBAAmB,WAA0B;AACjD,SAAO,OAAO,YACZ,OAAO,KAAK,OAAO,CAAC,KAAK,QAAQ;AAC/B,UAAO,CAAC,KAAK,sBAAsB,KAA4B;IAC/D,CACH;;AAKH,aACQ,QAAQ,GACb,QAAQ;AACP,MAAI,KAAK;GACP,MAAM,MAAM,IAAI;AAChB,IAAC,CAAC,GAAG,OAAO,KAAK,QAAQ,EAAE,GAAG,OAAO,KAAK,YAAY,CAAC,CAAC,SAAS,QAAQ;AACvE,0BAAsB,OAAO,IAAI,IAAI,IAAI;KACzC;QAEF,QAAO,KAAK,sBAAsB,CAAC,SAAS,QAAQ;AAClD,UAAO,sBAAsB;IAC7B;IAGN,EAAE,WAAW,MAAM,CACpB;;;;;CAMD,MAAM,kBAAkB,QAAqB;AAC3C,YAAU,KAAK,YAAY;;;;;;CAO7B,MAAM,qBAAqB,QAAqB;AAC9C,YAAU,KAAK,gBAAgB,YAAY,CAAC;;;;;;CAO9C,MAAM,kBAAkB,QAAqB;AAC3C,YAAU,KAAK,YAAY;;;;;;CAO7B,MAAM,qBAAqB,QAAqB;AAC9C,YAAU,KAAK,gBAAgB,YAAY,CAAC;;;;;;;;;;;;;;CA+B9C,MAAM,mBAAmB,QAAqB;AAC5C,YAAU,KAAK,QAAQ;;;;;;CAOzB,MAAM,sBAAsB,QAAqB;AAC/C,YAAU,KAAK,gBAAgB,QAAQ,CAAC;;;CAI1C,MAAM,mBAAmB;EACvB,MAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,IAAK;AACV,iBAAe,IAAI;AAEnB,kBAAgB;AACd,mBAAgB,IAAI;IACpB;;;CAIJ,MAAM,mBAAmB;EACvB,MAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,IAAK;AAGV,iBAAe,IAAI;AAGnB,kBAAgB;AACd,sBAAmB,IAAI;IACvB;;CAGJ,MAAM,CAAC,QAAQ,UAAU,aAAa,QAAQ,WAAW;AACvD,WAAS,YAAY,GAAG,YAAY;GACpC;AAEF,iBAAgB,QAAQ;EACtB,WAAW,OAAO,KAAK,QAAQ;EAC/B,SAAS,IAAI;AAEX,OAAI,OAAO,OAAO;AAChB,sBAAkB,GAAG;AACrB,qBAAiB;UACZ;AACL,sBAAkB,GAAG;AACrB,qBAAiB;;;EAIrB,MAAM,IAAI;AACR,OAAI,OAAO,OAAO;AAChB,sBAAkB,GAAG;AAErB,kBAAc;UACT;AACL,sBAAkB,GAAG;AAErB,kBAAc;;;EAGnB,CAAC;AAEF,QAAO;EAAE;EAAQ,aAAa,OAAO,KAAK;EAAE,aAAa,OAAO,MAAM;EAAE"}
@@ -0,0 +1,41 @@
1
+ import { onBeforeUnmount, watch } from "vue";
2
+ //#region src/use-transition/utils.ts
3
+ /**
4
+ * 监听过渡
5
+ * @param domGetter 元素获取函数
6
+ * @param config 配置
7
+ * @returns
8
+ */
9
+ function watchTransition(domGetter, config) {
10
+ const runCallback = (e, cb) => {
11
+ e.stopPropagation();
12
+ if (e.target !== domGetter() || !config.styleKeys.includes(e.propertyName)) return;
13
+ cb(e.target);
14
+ };
15
+ const transitionEndHandler = (e) => {
16
+ if (!domGetter()) return;
17
+ runCallback(e, config.onEnd);
18
+ };
19
+ const transitionCancelHandler = (e) => {
20
+ if (!domGetter()) return;
21
+ runCallback(e, config.onCancel);
22
+ };
23
+ const addEvent = (el) => {
24
+ el.addEventListener("transitionend", transitionEndHandler, false);
25
+ };
26
+ const removeEvent = (el) => {
27
+ el?.removeEventListener("transitionend", transitionEndHandler);
28
+ el?.removeEventListener("transitioncancel", transitionCancelHandler);
29
+ };
30
+ watch(domGetter, (target, oldTarget) => {
31
+ removeEvent(oldTarget);
32
+ target && addEvent(target);
33
+ }, { immediate: true });
34
+ onBeforeUnmount(() => {
35
+ removeEvent(domGetter());
36
+ });
37
+ }
38
+ //#endregion
39
+ export { watchTransition };
40
+
41
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../src/use-transition/utils.ts"],"sourcesContent":["import { watch, onBeforeUnmount } from 'vue'\n\n/**\n * 监听过渡\n * @param domGetter 元素获取函数\n * @param config 配置\n * @returns\n */\nexport function watchTransition(\n domGetter: () => HTMLElement | undefined,\n config: {\n styleKeys: string[]\n onEnd: (dom: HTMLElement) => void\n onCancel: (dom: HTMLElement) => void\n }\n): void {\n const runCallback = (e: TransitionEvent, cb: (el: HTMLElement) => void) => {\n e.stopPropagation()\n if (e.target !== domGetter() || !config.styleKeys.includes(e.propertyName)) {\n return\n }\n\n cb(e.target as HTMLElement)\n }\n\n const transitionEndHandler = (e: TransitionEvent) => {\n if (!domGetter()) return\n runCallback(e, config.onEnd)\n }\n const transitionCancelHandler = (e: TransitionEvent) => {\n if (!domGetter()) return\n runCallback(e, config.onCancel)\n }\n const addEvent = (el: HTMLElement) => {\n el.addEventListener('transitionend', transitionEndHandler, false)\n // el.addEventListener('transitioncancel', transitionCancelHandler, false)\n }\n\n const removeEvent = (el?: HTMLElement) => {\n el?.removeEventListener('transitionend', transitionEndHandler)\n el?.removeEventListener('transitioncancel', transitionCancelHandler)\n }\n\n watch(\n domGetter,\n (target, oldTarget) => {\n removeEvent(oldTarget)\n target && addEvent(target)\n },\n { immediate: true }\n )\n\n onBeforeUnmount(() => {\n removeEvent(domGetter())\n })\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,gBACd,WACA,QAKM;CACN,MAAM,eAAe,GAAoB,OAAkC;AACzE,IAAE,iBAAiB;AACnB,MAAI,EAAE,WAAW,WAAW,IAAI,CAAC,OAAO,UAAU,SAAS,EAAE,aAAa,CACxE;AAGF,KAAG,EAAE,OAAsB;;CAG7B,MAAM,wBAAwB,MAAuB;AACnD,MAAI,CAAC,WAAW,CAAE;AAClB,cAAY,GAAG,OAAO,MAAM;;CAE9B,MAAM,2BAA2B,MAAuB;AACtD,MAAI,CAAC,WAAW,CAAE;AAClB,cAAY,GAAG,OAAO,SAAS;;CAEjC,MAAM,YAAY,OAAoB;AACpC,KAAG,iBAAiB,iBAAiB,sBAAsB,MAAM;;CAInE,MAAM,eAAe,OAAqB;AACxC,MAAI,oBAAoB,iBAAiB,qBAAqB;AAC9D,MAAI,oBAAoB,oBAAoB,wBAAwB;;AAGtE,OACE,YACC,QAAQ,cAAc;AACrB,cAAY,UAAU;AACtB,YAAU,SAAS,OAAO;IAE5B,EAAE,WAAW,MAAM,CACpB;AAED,uBAAsB;AACpB,cAAY,WAAW,CAAC;GACxB"}
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,67 @@
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
@@ -0,0 +1 @@
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/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@veltra/compositions",
3
+ "version": "1.0.0",
4
+ "files": [
5
+ "dist",
6
+ "src"
7
+ ],
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "development": "./src/index.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsdown",
18
+ "check-types": "tsc -p tsconfig.json --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "@cat-kit/core": "^1.0.1",
22
+ "@floating-ui/dom": "^1.7.6",
23
+ "@tanstack/vue-virtual": "^3.13.23",
24
+ "@veltra/utils": "workspace:*"
25
+ },
26
+ "devDependencies": {
27
+ "tsdown": "^0.21.7"
28
+ },
29
+ "peerDependencies": {
30
+ "vue": "^3.5.0"
31
+ }
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ export * from './use-component-props'
2
+
3
+ export * from './use-config'
4
+
5
+ export * from './use-drag'
6
+
7
+ export * from './use-fallback-props'
8
+
9
+ export * from './use-focus'
10
+
11
+ export * from './use-form-component'
12
+
13
+ export * from './use-lock'
14
+
15
+ export * from './use-model'
16
+
17
+ export * from './use-pop'
18
+
19
+ export * from './use-reactive-size'
20
+
21
+ export * from './use-resize-observer'
22
+
23
+ export * from './use-transition'
24
+
25
+ export * from './use-virtual'
@@ -0,0 +1,63 @@
1
+ import { extractNormalVNodes } from '@veltra/utils'
2
+ import { defineComponent, isRef, type MaybeRef, createVNode, cloneVNode, type Component } from 'vue'
3
+
4
+ /**
5
+ * 生成一个用于设置组件通用属性的组件
6
+ * @param props 组件通用的属性
7
+ * @returns
8
+ */
9
+ export function useComponentProps<T extends Record<string, any>>(
10
+ props: MaybeRef<T & Record<string, any>>
11
+ ): Component {
12
+ return defineComponent({
13
+ name: 'ComponentCommonProps',
14
+ inheritAttrs: false,
15
+
16
+ props: {
17
+ /** 渲染一个标准html5标签 */
18
+ tag: { type: String }
19
+ },
20
+
21
+ setup(componentProps, { slots, attrs }) {
22
+ const isPropsRef = isRef(props)
23
+ // 非 ref 时 keys 固定,缓存避免每次 render 重复计算
24
+ const staticKeys = isPropsRef ? null : Object.keys(props)
25
+
26
+ const mergeNodesProps = (commonProps: Record<string, any>) => {
27
+ const nodes = extractNormalVNodes(slots.default?.() ?? [])
28
+ if (!nodes?.length) return undefined
29
+
30
+ const keys = staticKeys ?? Object.keys(commonProps)
31
+ return nodes.map((node) => {
32
+ const mergedProps: Record<string, any> = {}
33
+ let count = 0
34
+ for (let i = 0; i < keys.length; i++) {
35
+ const key = keys[i]!
36
+ // node中已定义的属性优先
37
+ if (node.props?.[key] !== undefined) continue
38
+ mergedProps[key] = attrs[key] !== undefined ? attrs[key] : commonProps[key]
39
+ count++
40
+ }
41
+ return count > 0 ? cloneVNode(node, mergedProps) : node
42
+ })
43
+ }
44
+
45
+ return () => {
46
+ const _props = isPropsRef ? props.value : props
47
+ const nodes = mergeNodesProps(_props)
48
+
49
+ if (componentProps.tag) {
50
+ if (!nodes) return undefined
51
+ const tagProps = Object.keys(attrs).reduce<Record<string, any>>((acc, cur) => {
52
+ if (!(cur in _props)) {
53
+ acc[cur] = attrs[cur]
54
+ }
55
+ return acc
56
+ }, {})
57
+ return createVNode(componentProps.tag, tagProps, nodes)
58
+ }
59
+ return nodes
60
+ }
61
+ }
62
+ })
63
+ }
@@ -0,0 +1,77 @@
1
+ import { isObj } from '@cat-kit/core'
2
+ import type { ComponentSize } from '@veltra/utils/types'
3
+ import { reactive, readonly, watch } from 'vue'
4
+
5
+ interface State {
6
+ /** 是否开启动画,机器老可以关闭动画来获得性能 */
7
+ animation: boolean
8
+ /** 组件尺寸大小 */
9
+ size: ComponentSize
10
+ /** 表单 */
11
+ form: {
12
+ /** 标签宽度 */
13
+ labelWidth?: number | number
14
+ }
15
+ paginator: { pageSize: number; pageSizeOptions: number[] }
16
+ }
17
+
18
+ const state = reactive<State>({
19
+ animation: true,
20
+ size: 'default',
21
+ form: { labelWidth: 80 },
22
+ paginator: { pageSize: 40, pageSizeOptions: [40, 100, 200, 500, 1000] }
23
+ })
24
+
25
+ export function setDocumentSize(size: ComponentSize, oldSize?: ComponentSize): void {
26
+ if (typeof document === 'undefined') return
27
+ if (oldSize) {
28
+ document.documentElement.classList.remove(oldSize)
29
+ }
30
+ document.documentElement.classList.add(size)
31
+ }
32
+
33
+ let stopDocumentSizeSync: (() => void) | null = null
34
+
35
+ function ensureDocumentSizeSync(): void {
36
+ if (stopDocumentSizeSync) return
37
+ if (typeof document === 'undefined') return
38
+
39
+ stopDocumentSizeSync = watch(
40
+ () => state.size,
41
+ (size, oldSize) => setDocumentSize(size, oldSize)
42
+ )
43
+ }
44
+
45
+ function deepSet(original: Record<string, any>, extend: Record<string, any>) {
46
+ Object.keys(extend).forEach((key) => {
47
+ const val = original[key]
48
+ const targetVal = extend[key]
49
+ if (isObj(val)) {
50
+ if (isObj(targetVal)) {
51
+ deepSet(val, targetVal)
52
+ } else {
53
+ console.warn(`extend['${key}']应该是一个对象`)
54
+ }
55
+ } else {
56
+ original[key] = targetVal
57
+ }
58
+ })
59
+ }
60
+
61
+ export function useConfig(): {
62
+ config: Readonly<State>
63
+ setConfig: (conf: Partial<State>) => void
64
+ } {
65
+ ensureDocumentSizeSync()
66
+ return {
67
+ /** 全局配置 */
68
+ config: readonly(state) as Readonly<State>,
69
+ /**
70
+ * 设置全局配置项
71
+ * @param conf
72
+ */
73
+ setConfig(conf: Partial<State>) {
74
+ deepSet(state, conf)
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,151 @@
1
+ import { type Ref, type ShallowRef, watch, onBeforeUnmount } from 'vue'
2
+
3
+ interface DragParams {
4
+ /** 本次拖动水平距离 */
5
+ x: number
6
+ /** 本次拖动垂直距离 */
7
+ y: number
8
+ /** 拖拽目标水平偏移量 */
9
+ offsetX: number
10
+ /** 拖拽目标垂直偏移量 */
11
+ offsetY: number
12
+ /** 鼠标事件 */
13
+ e: MouseEvent
14
+ }
15
+
16
+ interface DragOptions {
17
+ /** 拖动目标 */
18
+ target: ShallowRef<HTMLElement | undefined | null> | Ref<HTMLElement | undefined | null>
19
+ /** 拖动开始 */
20
+ onDragStart?(e: MouseEvent): void
21
+ /** 拖动结束 */
22
+ onDragEnd?(params: DragParams): void
23
+ /** 拖动时 */
24
+ onDrag?(params: DragParams): void
25
+ /** 水平拖动范围 */
26
+ rangeX?: [number, number]
27
+ /** 垂直拖动范围 */
28
+ rangeY?: [number, number]
29
+ /** 初始偏移量 */
30
+ initial?: { offsetX?: number; offsetY?: number }
31
+ }
32
+
33
+ /**
34
+ * 拖动组合式方法
35
+ * @param options 拖动选项
36
+ */
37
+ export function useDrag(options: DragOptions): {
38
+ update: (options: { offsetX?: number; offsetY?: number }) => void
39
+ } {
40
+ const { target, onDragStart, onDrag, onDragEnd, rangeX, rangeY, initial } = options
41
+
42
+ // 鼠标拖拽前的坐标
43
+ let originX = 0
44
+ let originY = 0
45
+
46
+ let offsetX = initial?.offsetX ?? 0
47
+ let offsetY = initial?.offsetY ?? 0
48
+
49
+ // 拖拽参数
50
+ const dragParams: DragParams = { x: 0, y: 0, offsetX: 0, offsetY: 0, e: null as any }
51
+
52
+ // 先取
53
+ const onselectstart = document.onselectstart
54
+
55
+ const handleMousedown = (e: MouseEvent) => {
56
+ // 阻止事件冒泡
57
+ e.stopPropagation()
58
+ // 鼠标左键按下有效
59
+ if (e.button !== 0) return
60
+ // 放置拖拽时选择内容
61
+ window.getSelection()?.removeAllRanges()
62
+ // 阻止后续的事件监听器被执行
63
+ e.stopImmediatePropagation()
64
+
65
+ originX = e.x
66
+ originY = e.y
67
+
68
+ onDragStart?.(e)
69
+
70
+ // 禁止浏览器的选中事件, 直到mouseup事件触发时还原
71
+ document.onselectstart = () => false
72
+ document.addEventListener('mousemove', handleMousemove, { passive: true })
73
+ document.addEventListener('mouseup', handleMouseup)
74
+ }
75
+
76
+ const getOffsetXWithRange = (deltaX: number) => {
77
+ return Math.max(rangeX![0], Math.min(rangeX![1], offsetX + deltaX))
78
+ }
79
+
80
+ const getOffsetXWithoutRange = (deltaX: number) => {
81
+ return offsetX + deltaX
82
+ }
83
+
84
+ const getOffsetYWithRange = (deltaY: number) => {
85
+ return Math.max(rangeY![0], Math.min(rangeY![1], offsetY + deltaY))
86
+ }
87
+
88
+ const getOffsetYWithoutRange = (deltaY: number) => {
89
+ return offsetY + deltaY
90
+ }
91
+
92
+ const getOffsetX = rangeX ? getOffsetXWithRange : getOffsetXWithoutRange
93
+ const getOffsetY = rangeY ? getOffsetYWithRange : getOffsetYWithoutRange
94
+
95
+ // 避免重复创建对象影响内存占用
96
+ const setDragParam = (e: MouseEvent) => {
97
+ dragParams.x = e.x - originX
98
+ dragParams.y = e.y - originY
99
+ dragParams.offsetX = getOffsetX(dragParams.x)
100
+ dragParams.offsetY = getOffsetY(dragParams.y)
101
+ dragParams.e = e
102
+ }
103
+
104
+ const handleMousemove = (e: MouseEvent) => {
105
+ setDragParam(e)
106
+ onDrag?.(dragParams)
107
+ }
108
+
109
+ const handleMouseup = (e: MouseEvent) => {
110
+ setDragParam(e)
111
+ offsetX = dragParams.offsetX
112
+ offsetY = dragParams.offsetY
113
+
114
+ onDragEnd?.(dragParams)
115
+ document.onselectstart = onselectstart
116
+
117
+ cleanup()
118
+ }
119
+
120
+ // 统一的清理函数
121
+ const cleanup = () => {
122
+ document.removeEventListener('mousemove', handleMousemove)
123
+ document.removeEventListener('mouseup', handleMouseup)
124
+ }
125
+
126
+ watch(
127
+ target,
128
+ (dom, oldDom) => {
129
+ oldDom?.removeEventListener('mousedown', handleMousedown)
130
+ if (!dom) return
131
+ dom.addEventListener('mousedown', handleMousedown)
132
+ },
133
+ { immediate: true }
134
+ )
135
+
136
+ onBeforeUnmount(() => {
137
+ target.value?.removeEventListener('mousedown', handleMousedown)
138
+ cleanup()
139
+ })
140
+
141
+ return {
142
+ update(options: { offsetX?: number; offsetY?: number }) {
143
+ if (options.offsetX !== undefined) {
144
+ offsetX = options.offsetX
145
+ }
146
+ if (options.offsetY !== undefined) {
147
+ offsetY = options.offsetY
148
+ }
149
+ }
150
+ }
151
+ }