From b8a8c361ee834f684b92ca79c464c3a4cf3c6bf1 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 8 Oct 2024 10:35:22 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat(utils/rand.ts):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E4=B8=A4=E4=B8=AA=E7=94=A8=E4=BA=8E=E7=94=9F=E6=88=90?= =?UTF-8?q?=E9=9A=8F=E6=9C=BA=E6=95=B0=E7=9A=84=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/rand.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/utils/rand.ts diff --git a/src/utils/rand.ts b/src/utils/rand.ts new file mode 100644 index 0000000..2557b7b --- /dev/null +++ b/src/utils/rand.ts @@ -0,0 +1,14 @@ +/** + * @deprecated Use `useId` `useIdInjection` instead + * Generate random number in range [0, 1000] + * Maybe replace with [uuid](https://www.npmjs.com/package/uuid) + */ +export const generateId = (): number => Math.floor(Math.random() * 10000) + +/** + * @deprecated + * Generating a random int in range (0, max - 1) + * @param max {number} + */ +export const getRandomInt = (max: number) => + Math.floor(Math.random() * Math.floor(max)) From fa95ad67d5ab26b39e8c08b4614ae4ac09d1e27a Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Wed, 9 Oct 2024 14:14:51 +0800 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0throttleByRaf?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E4=BB=A5=E4=BC=98=E5=8C=96=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/throttleByRaf.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/utils/throttleByRaf.ts diff --git a/src/utils/throttleByRaf.ts b/src/utils/throttleByRaf.ts new file mode 100644 index 0000000..801d5f1 --- /dev/null +++ b/src/utils/throttleByRaf.ts @@ -0,0 +1,22 @@ +import { cAF, rAF } from './raf' + +export function throttleByRaf(cb: (...args: any[]) => void) { + let timer = 0 + + const throttle = (...args: any[]): void => { + if (timer) { + cAF(timer) + } + timer = rAF(() => { + cb(...args) + timer = 0 + }) + } + + throttle.cancel = () => { + cAF(timer) + timer = 0 + } + + return throttle +} From e64672a65ed7c2e39d25d19bda3980742453159e Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Wed, 9 Oct 2024 14:16:42 +0800 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E6=96=B0=E7=9A=84=20`throttleByRaf`=20=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index afed721..bfb163a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,4 +11,5 @@ export * from './types' export * from './typescript' export * from './validator' export * from './easings' -export * from './raf' \ No newline at end of file +export * from './raf' +export * from './throttleByRaf' \ No newline at end of file From 2a378a27ddc1f9dbabef1fcc4c7d49b5c0dafaa9 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Thu, 10 Oct 2024 09:55:23 +0800 Subject: [PATCH 04/10] =?UTF-8?q?feat(utils/vue):=20=E6=B7=BB=E5=8A=A0=20E?= =?UTF-8?q?mitFn=20=E7=B1=BB=E5=9E=8B=E5=92=8C=20EmitsOptions=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vue/typescript.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/vue/typescript.ts b/src/utils/vue/typescript.ts index 1ea4353..6c63b15 100644 --- a/src/utils/vue/typescript.ts +++ b/src/utils/vue/typescript.ts @@ -1,7 +1,9 @@ -import type { AppContext, Plugin } from 'vue' +import type { AppContext, EmitsOptions, Plugin, SetupContext } from 'vue' export type SFCWithInstall = T & Plugin export type SFCInstallWithContext = SFCWithInstall & { _context: AppContext | null } + +export type EmitFn = SetupContext['emit'] From 0de120456ee1862de82d9f1382c134581955386e Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Sat, 12 Oct 2024 15:33:41 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E5=A4=96=E9=83=A8=E6=8C=87=E4=BB=A4=20ClickOutside?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/directives/click-outside/index.ts | 120 ++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/directives/click-outside/index.ts diff --git a/src/directives/click-outside/index.ts b/src/directives/click-outside/index.ts new file mode 100644 index 0000000..472fa96 --- /dev/null +++ b/src/directives/click-outside/index.ts @@ -0,0 +1,120 @@ +import { isClient, isElement } from '@/utils' + +import type { + ComponentPublicInstance, + DirectiveBinding, + ObjectDirective, +} from 'vue' + +type DocumentHandler = (mouseup: T, mousedown: T) => void +type FlushList = Map< + HTMLElement, + { + documentHandler: DocumentHandler + bindingFn: (...args: unknown[]) => unknown + }[] +> + +const nodeList: FlushList = new Map() + +if (isClient) { + let startClick: MouseEvent | undefined + document.addEventListener('mousedown', (e: MouseEvent) => (startClick = e)) + document.addEventListener('mouseup', (e: MouseEvent) => { + if (startClick) { + for (const handlers of nodeList.values()) { + for (const { documentHandler } of handlers) { + documentHandler(e as MouseEvent, startClick) + } + } + startClick = undefined + } + }) +} + +function createDocumentHandler( + el: HTMLElement, + binding: DirectiveBinding +): DocumentHandler { + let excludes: HTMLElement[] = [] + if (Array.isArray(binding.arg)) { + excludes = binding.arg + } else if (isElement(binding.arg)) { + // due to current implementation on binding type is wrong the type casting is necessary here + excludes.push(binding.arg as unknown as HTMLElement) + } + return function (mouseup, mousedown) { + const popperRef = ( + binding.instance as ComponentPublicInstance<{ + popperRef: HTMLElement + }> + ).popperRef + const mouseUpTarget = mouseup.target as Node + const mouseDownTarget = mousedown?.target as Node + const isBound = !binding || !binding.instance + const isTargetExists = !mouseUpTarget || !mouseDownTarget + const isContainedByEl = + el.contains(mouseUpTarget) || el.contains(mouseDownTarget) + const isSelf = el === mouseUpTarget + + const isTargetExcluded = + (excludes.length && + excludes.some((item) => item?.contains(mouseUpTarget))) || + (excludes.length && excludes.includes(mouseDownTarget as HTMLElement)) + const isContainedByPopper = + popperRef && + (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget)) + if ( + isBound || + isTargetExists || + isContainedByEl || + isSelf || + isTargetExcluded || + isContainedByPopper + ) { + return + } + binding.value(mouseup, mousedown) + } +} + +const ClickOutside: ObjectDirective = { + beforeMount(el: HTMLElement, binding: DirectiveBinding) { + // there could be multiple handlers on the element + if (!nodeList.has(el)) { + nodeList.set(el, []) + } + + nodeList.get(el)!.push({ + documentHandler: createDocumentHandler(el, binding), + bindingFn: binding.value, + }) + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + if (!nodeList.has(el)) { + nodeList.set(el, []) + } + + const handlers = nodeList.get(el)! + const oldHandlerIndex = handlers.findIndex( + (item) => item.bindingFn === binding.oldValue + ) + const newHandler = { + documentHandler: createDocumentHandler(el, binding), + bindingFn: binding.value, + } + + if (oldHandlerIndex >= 0) { + // replace the old handler to the new handler + handlers.splice(oldHandlerIndex, 1, newHandler) + } else { + handlers.push(newHandler) + } + }, + unmounted(el: HTMLElement) { + // remove all listeners when a component unmounted + nodeList.delete(el) + }, +} + +export default ClickOutside From 16ebf96face0573b8191b35d359b89be109a2c24 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 14 Oct 2024 09:17:01 +0800 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E8=8A=82=E7=82=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vue/global-node.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/utils/vue/global-node.ts diff --git a/src/utils/vue/global-node.ts b/src/utils/vue/global-node.ts new file mode 100644 index 0000000..8244f48 --- /dev/null +++ b/src/utils/vue/global-node.ts @@ -0,0 +1,34 @@ +import { isClient } from '../browser' + +const globalNodes: HTMLElement[] = [] +let target: HTMLElement | undefined = !isClient ? undefined : document.body + +export function createGlobalNode(id?: string) { + const el = document.createElement('div') + if (id !== undefined) { + el.setAttribute('id', id) + } + + if (target) { + target.appendChild(el) + globalNodes.push(el) + } + + return el +} + +export function removeGlobalNode(el: HTMLElement) { + globalNodes.splice(globalNodes.indexOf(el), 1) + el.remove() +} + +export function changeGlobalNodesTarget(el: HTMLElement) { + if (el === target) return + + target = el + globalNodes.forEach((el) => { + if (target && !el.contains(target)) { + target.appendChild(el) + } + }) +} From 0e48cca66772cacd47a79a1c4c931755e1ad6725 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 14 Oct 2024 09:17:11 +0800 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=BB=84=E4=BB=B6=E5=A4=A7=E5=B0=8F=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vue/size.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/utils/vue/size.ts diff --git a/src/utils/vue/size.ts b/src/utils/vue/size.ts new file mode 100644 index 0000000..dbdf05d --- /dev/null +++ b/src/utils/vue/size.ts @@ -0,0 +1,7 @@ +import { componentSizeMap } from '@/constants' + +import type { ComponentSize } from '@/constants' + +export const getComponentSize = (size?: ComponentSize) => { + return componentSizeMap[size || 'default'] +} From cb9480ba53d3c20f5c9bba05f031a9a17ad11706 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 14 Oct 2024 09:17:16 +0800 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Size=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vue/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/vue/index.ts b/src/utils/vue/index.ts index 3d8366a..17c6c69 100644 --- a/src/utils/vue/index.ts +++ b/src/utils/vue/index.ts @@ -4,3 +4,4 @@ export * from './typescript' export * from './vnode' export * from './props' export * from './refs' +export * from './size' From ca477e031067ca368f49d83ab068d56763f75730 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 15 Oct 2024 10:41:04 +0800 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20ClickOutside?= =?UTF-8?q?=20=E6=8C=87=E4=BB=A4=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/directives/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/directives/index.ts b/src/directives/index.ts index 56092c9..5671369 100644 --- a/src/directives/index.ts +++ b/src/directives/index.ts @@ -1,2 +1,3 @@ export { default as TrapFocus } from './trap-focus' export { vRepeatClick } from './repeat-click' +export { default as ClickOutside } from './click-outside' \ No newline at end of file From cdcf5471780adcb733c04d35592186eddfa096df Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Thu, 17 Oct 2024 09:05:05 +0800 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0useAttrs?= =?UTF-8?q?=E9=92=A9=E5=AD=90=E4=BB=A5=E5=A4=84=E7=90=86Vue=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 1 + src/hooks/useAttrs.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/hooks/useAttrs.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index c7fcd79..10be352 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,5 @@ export * from './useAria' +export * from './useAttrs' export * from './useClickOutside' export * from './useDeprecated' export * from './useEventListener' diff --git a/src/hooks/useAttrs.ts b/src/hooks/useAttrs.ts new file mode 100644 index 0000000..b37d0c1 --- /dev/null +++ b/src/hooks/useAttrs.ts @@ -0,0 +1,38 @@ +import { computed, getCurrentInstance } from 'vue' +import { fromPairs } from 'lodash-unified' +import { debugWarn } from '@/utils' + +import type { ComputedRef } from 'vue' + +interface Params { + excludeListeners?: boolean + excludeKeys?: ComputedRef +} + +const DEFAULT_EXCLUDE_KEYS = ['class', 'style'] +const LISTENER_PREFIX = /^on[A-Z]/ + +export const useAttrs = (params: Params = {}): ComputedRef> => { + const { excludeListeners = false, excludeKeys } = params + const allExcludeKeys = computed(() => { + return (excludeKeys?.value || []).concat(DEFAULT_EXCLUDE_KEYS) + }) + + const instance = getCurrentInstance() + if (!instance) { + debugWarn( + 'use-attrs', + 'getCurrentInstance() returned null. useAttrs() must be called at the top of a setup function' + ) + return computed(() => ({})) + } + + return computed(() => + fromPairs( + Object.entries(instance.proxy?.$attrs!).filter( + ([key]) => + !allExcludeKeys.value.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key)) + ) + ) + ) +}