From 8a39460e3ff01e9bc208721a19b257881d2e5741 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Fri, 12 Jul 2024 09:07:18 +0800 Subject: [PATCH 01/29] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9CSS=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useNameSpace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useNameSpace.ts b/src/hooks/useNameSpace.ts index 4cd5387..863a194 100644 --- a/src/hooks/useNameSpace.ts +++ b/src/hooks/useNameSpace.ts @@ -64,7 +64,7 @@ export const useNamespace = (block: string, namespaceOverrides?: Ref) => { const styles: Record = {} for (const key in object) { From c06cdf57481aa1f92dc3ea7eb3472d5f58942a1c Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Fri, 12 Jul 2024 09:07:45 +0800 Subject: [PATCH 02/29] =?UTF-8?q?feat(hooks):=20=E6=B7=BB=E5=8A=A0useProp?= =?UTF-8?q?=E9=92=A9=E5=AD=90=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useProp.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/hooks/useProp.ts b/src/hooks/useProp.ts index 4b56fff..7d76e18 100644 --- a/src/hooks/useProp.ts +++ b/src/hooks/useProp.ts @@ -1,7 +1,19 @@ import { computed, getCurrentInstance } from 'vue' import type { ComputedRef } from 'vue' +/** + * 使用给定的属性名称,创建一个计算属性,用于获取当前Vue实例的属性值。 + * + * 这个函数主要用于在Vue组件中,通过计算属性的方式动态获取组件的props值。 + * 通过传入属性名称,函数内部会尝试获取当前Vue实例的props对象中对应属性的值。 + * 如果实例或属性不存在,则返回undefined。 + * + * @param name 属性名称,用于获取对应属性的值。 + * @returns 返回一个计算属性,该计算属性在访问时会返回当前Vue实例中对应属性的值。 + */ export const useProp = (name: string): ComputedRef => { + // 获取当前Vue实例 const vm = getCurrentInstance() + // 创建一个计算属性,用于获取实例的props中指定名称的属性值 return computed(() => (vm?.proxy?.$props as any)?.[name]) } From 9e78f1b39d4a8997238968689ec00b5ae0c25165 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 15 Jul 2024 09:20:07 +0800 Subject: [PATCH 03/29] =?UTF-8?q?feat(utils/browser):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=BD=93=E5=89=8D=E6=B5=8F=E8=A7=88=E5=99=A8?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E4=B8=BAFirefox=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/browser.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utils/browser.ts b/src/utils/browser.ts index 29ca9b5..970b3b7 100644 --- a/src/utils/browser.ts +++ b/src/utils/browser.ts @@ -1,5 +1,13 @@ import { isClient, isIOS } from '@vueuse/core' - +/** + * 检查当前环境是否为Firefox浏览器。 + * + * 该函数通过检测navigator.userAgent属性来确定当前浏览器是否为Firefox。 + * 它首先确认当前环境是在客户端(而不是服务器),然后使用正则表达式匹配userAgent来寻找Firefox的迹象。 + * 这种检查方式对于识别特定浏览器很有用,特别是在需要针对不同浏览器实现不同逻辑的场景中。 + * + * @returns {boolean} 如果当前浏览器是Firefox,则返回true;否则返回false。 + */ export const isFirefox = (): boolean => isClient && /firefox/i.test(window.navigator.userAgent) From 1ce36e29aacc9254ea990180f555a4b9fceeabfd Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 16 Jul 2024 09:03:19 +0800 Subject: [PATCH 04/29] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=8F=AF?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=8F=AF=E8=81=9A=E7=84=A6=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dom/aria.ts | 66 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/src/utils/dom/aria.ts b/src/utils/dom/aria.ts index 1132f38..fb96185 100644 --- a/src/utils/dom/aria.ts +++ b/src/utils/dom/aria.ts @@ -1,5 +1,16 @@ +/** + * 定义可聚焦元素的选择器。 + * 这些选择器用于筛选页面上所有可能通过tab键聚焦的元素。 + */ const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])` +/** + * 判断给定元素是否在视觉上可见。 + * + * 注意:此函数主要用于非测试环境,以确定元素是否可见。 + * 在测试环境中,任何元素都被视为可见。 + */ + /** * Determine if the testing element is visible on screen no matter if its on the viewport or not */ @@ -11,14 +22,32 @@ export const isVisible = (element: HTMLElement) => { return computed.position === 'fixed' ? false : element.offsetParent !== null } +/** + * 获取给定元素内所有可聚焦的元素。 + * + * 该函数通过查询给定元素内的所有元素,并筛选出那些既可聚焦又可视的元素。 + * 可聚焦元素是指在用户界面中可以通过键盘导航或获得焦点的元素。 + * 可视元素是指在当前视口内对用户可见的元素。 + * + * @param element - 被查询的元素,通常是页面或组件内的一个容器。 + * @returns 一个包含所有可聚焦且可视元素的数组。 + */ export const obtainAllFocusableElements = ( element: HTMLElement ): HTMLElement[] => { + // 使用FOCUSABLE_ELEMENT_SELECTORS选择器查询所有可能可聚焦的元素 + // 然后通过filter过滤出确实可聚焦且可视的元素 return Array.from( element.querySelectorAll(FOCUSABLE_ELEMENT_SELECTORS) ).filter((item: HTMLElement) => isFocusable(item) && isVisible(item)) } +/** + * 判断给定元素是否可聚焦。 + * + * @param element - 要检查的元素。 + * @returns 如果元素可聚焦则返回true,否则返回false。 + */ /** * @desc Determine if target element is focusable * @param element {HTMLElement} @@ -61,12 +90,16 @@ export const isFocusable = (element: HTMLElement): boolean => { } } +/** + * 尝试将焦点设置在给定的元素上。 + * + * @param element - 要聚焦的元素。 + * @returns 如果焦点成功设置在元素上,则返回true,否则返回false。 + */ /** * @desc Set Attempt to set focus on the current node. - * @param element - * The node to attempt to focus on. - * @returns - * true if element is focused. + * @param element The node to attempt to focus on. + * @returns true if element is focused. */ export const attemptFocus = (element: HTMLElement): boolean => { if (!isFocusable(element)) { @@ -77,6 +110,14 @@ export const attemptFocus = (element: HTMLElement): boolean => { return document.activeElement === element } +/** + * 触发指定的事件。 + * + * @param elm - 要触发事件的元素。 + * @param name - 事件名称。 + * @param opts - 事件特定选项。 + * @returns 触发事件的元素。 + */ /** * Trigger an event * mouseenter, mouseleave, mouseover, keyup, change, click, etc. @@ -105,6 +146,15 @@ export const triggerEvent = function ( return elm } +/** + * 判断元素是否为叶子节点。 + * + * 叶子节点指的是没有aria-owns属性的元素。 + * 这在某些情况下用于确定元素是否应该接收焦点。 + * + * @param el - 要检查的元素。 + * @returns 如果元素是叶子节点,则返回true,否则返回false。 + */ export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns') export const getSibling = ( @@ -119,6 +169,14 @@ export const getSibling = ( return siblings[index + distance] || null } +/** + * 将焦点设置在给定的元素上,并根据需要触发点击事件。 + * + * 此函数用于确保元素不仅获得焦点,而且如果它是一个非叶子节点, + * 则还会触发点击事件,以模拟用户的行为。 + * + * @param el - 要聚焦和可能点击的元素。 + */ export const focusNode = (el: HTMLElement) => { if (!el) return el.focus() From f0824a3617a38ee89c70ee780f5bb802a6a02e22 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 16 Jul 2024 09:04:54 +0800 Subject: [PATCH 05/29] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BD=BF?= =?UTF-8?q?=E7=94=A8useClickOutside=E5=8A=9F=E8=83=BD=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useClickOutside.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts index 4135d62..5b5ba73 100644 --- a/src/hooks/useClickOutside.ts +++ b/src/hooks/useClickOutside.ts @@ -1,9 +1,20 @@ import { onMounted, onUnmounted, type Ref } from 'vue' +/** + * 使用自定义指令v-click-outside实现点击外部区域触发回调函数的功能。 + * + * @param elementRef 组件内部元素的引用,用于判断点击事件是否发生在该元素内部。 + * @param callback 当点击发生在元素外部时调用的回调函数。 + */ const useClickOutside = ( elementRef: Ref, callback: (e: MouseEvent) => void ) => { + /** + * 点击事件的处理函数。 + * + * @param e MouseEvent 点击事件对象。 + */ const handler = (e: MouseEvent) => { if (elementRef.value && e.target) { if (!elementRef.value.contains(e.target as HTMLElement)) callback(e) From afe9a512debce31c4579e951c1b859e2a75dd0f9 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 22 Jul 2024 09:12:20 +0800 Subject: [PATCH 06/29] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0vRepeatClick?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E7=9A=84=E6=B3=A8=E9=87=8A=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/directives/repeat-click/index.ts | 37 ++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/directives/repeat-click/index.ts b/src/directives/repeat-click/index.ts index e1635ee..1fdb8a2 100644 --- a/src/directives/repeat-click/index.ts +++ b/src/directives/repeat-click/index.ts @@ -11,23 +11,42 @@ export interface RepeatClickOptions { handler: (...args: unknown[]) => unknown } +/** + * vRepeatClick指令定义了重复点击的行为。 + * + * 该指令用于HTMLElement上,为元素添加重复点击的功能。当用户快速连续点击元素时,会按照设定的间隔和延迟调用指定的处理函数。 + * + * @param el 指令绑定的DOM元素。 + * @param binding 指令的绑定对象,包含指令的值。 + */ export const vRepeatClick: ObjectDirective< HTMLElement, RepeatClickOptions | RepeatClickOptions['handler'] > = { + /** + * 在元素挂载之前执行的逻辑。 + * + * 这里初始化了间隔ID和延迟ID,用于后续的定时器管理。并根据绑定值的类型,配置了间隔和延迟时间,并绑定了鼠标点击事件。 + * + * @param el 指令绑定的DOM元素。 + * @param binding 指令的绑定对象,包含指令的值。 + */ beforeMount(el, binding) { const value = binding.value - const { interval = REPEAT_INTERVAL, delay = REPEAT_DELAY } = isFunction( - value - ) - ? {} - : value + // 根据绑定值是否为函数,来确定间隔和延迟的默认值。 + const { interval = REPEAT_INTERVAL, delay = REPEAT_DELAY } = isFunction(value) ? {} : value let intervalId: ReturnType | undefined let delayId: ReturnType | undefined + /** + * 处理函数,根据绑定值的类型动态调用相应的函数。 + */ const handler = () => (isFunction(value) ? value() : value.handler()) + /** + * 清除定时器的方法。 + */ const clear = () => { if (delayId) { clearTimeout(delayId) @@ -39,20 +58,24 @@ export const vRepeatClick: ObjectDirective< } } + // 绑定鼠标按下事件。 el.addEventListener('mousedown', (evt: MouseEvent) => { + // 仅处理主键点击。 if (evt.button !== 0) return clear() handler() + // 在鼠标抬起时清除定时器。 document.addEventListener('mouseup', () => clear(), { - once: true, + once: true }) + // 延迟后设置定时器,按照设定的间隔调用处理函数。 delayId = setTimeout(() => { intervalId = setInterval(() => { handler() }, interval) }, delay) }) - }, + } } From a3a540cfd03c8f68881d696c08d95646080beaf8 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 22 Jul 2024 09:57:09 +0800 Subject: [PATCH 07/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0TrapFocus?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/directives/trap-focus/index.ts | 58 +++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/directives/trap-focus/index.ts b/src/directives/trap-focus/index.ts index 6629f87..17d266b 100644 --- a/src/directives/trap-focus/index.ts +++ b/src/directives/trap-focus/index.ts @@ -1,24 +1,54 @@ import { nextTick } from 'vue' +/** + * 导入obtainAllFocusableElements函数,用于获取元素内所有可聚焦的元素。 + * 来自'@/utils'模块。 + */ import { obtainAllFocusableElements } from '@/utils' +/** + * 导入EVENT_CODE常量,包含键盘事件的键码。 + * 来自'@/constants'模块。 + */ import { EVENT_CODE } from '@/constants' +/** + * 导入ObjectDirective类型,用于定义Vue的对象指令。 + * 来自Vue的'vue'模块。 + */ import type { ObjectDirective } from 'vue' +/** + * 定义一个常量FOCUSABLE_CHILDREN,用于在元素上存储可聚焦子元素的数组。 + */ export const FOCUSABLE_CHILDREN = '_trap-focus-children' +/** + * 定义一个常量TRAP_FOCUS_HANDLER,用于在元素上存储焦点处理函数。 + */ export const TRAP_FOCUS_HANDLER = '_trap-focus-handler' +/** + * 定义TrapFocusElement接口,扩展HTMLElement,添加FOCUSABLE_CHILDREN和TRAP_FOCUS_HANDLER属性。 + */ export interface TrapFocusElement extends HTMLElement { [FOCUSABLE_CHILDREN]: HTMLElement[] [TRAP_FOCUS_HANDLER]: (e: KeyboardEvent) => void } +/** + * 一个保存当前激活的焦点陷阱元素的数组。 + */ const FOCUS_STACK: TrapFocusElement[] = [] +/** + * 焦点处理函数,用于在按下键盘键时定向焦点。 + * @param e 键盘事件对象。 + */ const FOCUS_HANDLER = (e: KeyboardEvent) => { + // 当焦点堆栈为空时,不执行任何操作 // Getting the top layer. if (FOCUS_STACK.length === 0) return - const focusableElement = - FOCUS_STACK[FOCUS_STACK.length - 1][FOCUSABLE_CHILDREN] + const focusableElement = FOCUS_STACK[FOCUS_STACK.length - 1][FOCUSABLE_CHILDREN] + // 如果存在可聚焦元素且按下的是Tab键 if (focusableElement.length > 0 && e.code === EVENT_CODE.tab) { + // 如果只有一个可聚焦元素,阻止默认行为并聚焦该元素 if (focusableElement.length === 1) { e.preventDefault() if (document.activeElement !== focusableElement[0]) { @@ -26,6 +56,7 @@ const FOCUS_HANDLER = (e: KeyboardEvent) => { } return } + // 根据Shift键的状态和当前焦点的位置,定向焦点到前一个或后一个可聚焦元素 const goingBackward = e.shiftKey const isFirst = e.target === focusableElement[0] const isLast = e.target === focusableElement[focusableElement.length - 1] @@ -37,7 +68,7 @@ const FOCUS_HANDLER = (e: KeyboardEvent) => { e.preventDefault() focusableElement[0].focus() } - + // 在测试环境中模拟焦点行为 // the is critical since jsdom did not implement user actions, you can only mock it // DELETE ME: when testing env switches to puppeteer if (process.env.NODE_ENV === 'test') { @@ -49,25 +80,44 @@ const FOCUS_HANDLER = (e: KeyboardEvent) => { } } +/** + * 定义TrapFocus指令,用于创建一个焦点陷阱,防止焦点离开指定的元素区域。 + */ const TrapFocus: ObjectDirective = { + /** + * 指令的beforeMount钩子,用于初始化焦点陷阱。 + * @param el 指令绑定的元素。 + */ beforeMount(el: TrapFocusElement) { + // 获取并存储元素内的所有可聚焦元素 el[FOCUSABLE_CHILDREN] = obtainAllFocusableElements(el) + // 将元素添加到焦点堆栈 FOCUS_STACK.push(el) + // 如果是第一个添加的焦点陷阱,监听键盘事件 if (FOCUS_STACK.length <= 1) { document.addEventListener('keydown', FOCUS_HANDLER) } }, + /** + * 指令的updated钩子,用于更新焦点陷阱内的可聚焦元素列表。 + * @param el 指令绑定的元素。 + */ updated(el: TrapFocusElement) { nextTick(() => { el[FOCUSABLE_CHILDREN] = obtainAllFocusableElements(el) }) }, + /** + * 指令的unmounted钩子,用于清理焦点陷阱。 + */ unmounted() { + // 从焦点堆栈移除当前元素 FOCUS_STACK.shift() + // 如果焦点堆栈为空,移除键盘事件监听器 if (FOCUS_STACK.length === 0) { document.removeEventListener('keydown', FOCUS_HANDLER) } - }, + } } export default TrapFocus From f1f02ba7c7935747ed3aa917b901c38c0a806823 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 23 Jul 2024 15:45:49 +0800 Subject: [PATCH 08/29] =?UTF-8?q?feat(utils/dom):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96DOM=E5=85=83=E7=B4=A0=E5=87=BD=E6=95=B0getEle?= =?UTF-8?q?ment=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dom/element.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/utils/dom/element.ts b/src/utils/dom/element.ts index 39da80d..beed8fb 100644 --- a/src/utils/dom/element.ts +++ b/src/utils/dom/element.ts @@ -1,10 +1,29 @@ import { isString } from '../types' import { isClient } from '../browser' +/** + * 根据传入的目标获取相应的元素。 + * + * 此函数旨在提供一种统一的方式来获取DOM元素,它可以处理字符串选择器、HTMLElement实例、Window对象, + * 以及null和undefined。在客户端环境下,它会尝试使用字符串选择器来查询元素;如果不是字符串选择器, + * 则直接返回目标对象。在非客户端环境下(如Node.js环境),所有操作都将返回null。 + * + * @param target - 目标元素,可以是字符串选择器、HTMLElement实例、Window对象,或者null和undefined。 + * @returns 返回查询到的HTMLElement实例、原始目标对象,或者在无法获取元素时返回null。 + */ + type GetElement = ( target: T ) => T extends string ? HTMLElement | null : T +/** + * 获取元素的实现。 + * + * 此实现首先检查是否在客户端环境中,以及目标是否为空字符串,如果是,则直接返回null。 + * 对于字符串类型的目标,尝试使用querySelector来查询元素,并捕获可能的查询错误,错误情况下返回null。 + * 对于非字符串类型的目標,直接返回原对象。 + */ + export const getElement = (( target: string | HTMLElement | Window | null | undefined ) => { From e42c24076f7dc3a9c22063d180564c48b1cf8964 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 23 Jul 2024 15:46:04 +0800 Subject: [PATCH 09/29] =?UTF-8?q?feat(utils/dom):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BB=84=E5=90=88=E4=BA=8B=E4=BB=B6=E5=A4=84=E7=90=86=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=92=8C=E9=BC=A0=E6=A0=87=E4=BA=8B=E4=BB=B6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=87=BD=E6=95=B0=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dom/event.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/utils/dom/event.ts b/src/utils/dom/event.ts index 4e82a09..4c1decb 100644 --- a/src/utils/dom/event.ts +++ b/src/utils/dom/event.ts @@ -1,3 +1,14 @@ +/** + * 组合事件处理函数。 + * + * 该函数用于将两个事件处理函数合并为一个,先调用第一个处理函数(他们的处理函数),如果它没有阻止事件的默认行为, + * 则调用第二个处理函数(我们的处理函数)。这在处理复杂事件逻辑时非常有用,例如需要在执行默认行为之前添加额外的验证或操作。 + * + * @param theirsHandler 第一个事件处理函数,它有机会阻止事件的默认行为。 + * @param oursHandler 第二个事件处理函数,只有在第一个处理函数没有阻止事件时才会被调用。 + * @param options 配置对象,包含可选的检查默认行为是否被阻止的标志。 + * @returns 返回一个组合的事件处理函数,它根据条件调用适当的处理函数。 + */ export const composeEventHandlers = ( theirsHandler?: (event: E) => boolean | void, oursHandler?: (event: E) => void, @@ -13,6 +24,15 @@ export const composeEventHandlers = ( return handleEvent } +/** + * 仅在鼠标事件发生时执行处理函数。 + * + * 这个高阶函数用于封装事件处理函数,确保只有在事件是由鼠标触发时才执行处理函数。 + * 这对于需要区分不同类型的输入设备(如鼠标或触摸屏)的交互非常有用。 + * + * @param handler 原始的事件处理函数,它将只在鼠标事件上执行。 + * @returns 返回一个包装过的事件处理函数,它会检查事件类型并根据需要调用原始处理函数。 + */ type WhenMouseHandler = (e: PointerEvent) => any export const whenMouse = (handler: WhenMouseHandler): WhenMouseHandler => { return (e: PointerEvent) => From 9fa9e62875a0e3aba3784edab00de9da367d2450 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Wed, 24 Jul 2024 09:32:43 +0800 Subject: [PATCH 10/29] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E4=BA=86useCli?= =?UTF-8?q?ckOutside.ts=E6=96=87=E4=BB=B6=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=86=E4=BB=A3=E7=A0=81=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useClickOutside.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts index 5b5ba73..50d739b 100644 --- a/src/hooks/useClickOutside.ts +++ b/src/hooks/useClickOutside.ts @@ -1,7 +1,7 @@ import { onMounted, onUnmounted, type Ref } from 'vue' /** - * 使用自定义指令v-click-outside实现点击外部区域触发回调函数的功能。 + * 实现点击外部区域触发回调函数的功能。 * * @param elementRef 组件内部元素的引用,用于判断点击事件是否发生在该元素内部。 * @param callback 当点击发生在元素外部时调用的回调函数。 From 9633b7e59cc2fd8130001a1f681c5d2004023f87 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Wed, 24 Jul 2024 09:33:16 +0800 Subject: [PATCH 11/29] =?UTF-8?q?feat(hooks):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E5=BC=83=E7=94=A8=E7=89=B9=E6=80=A7=E7=9B=91=E8=A7=86?= =?UTF-8?q?=E7=9A=84Hooks=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useDeprecated.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/hooks/useDeprecated.ts b/src/hooks/useDeprecated.ts index aa6006d..03fa1c0 100644 --- a/src/hooks/useDeprecated.ts +++ b/src/hooks/useDeprecated.ts @@ -1,25 +1,39 @@ import { unref, watch } from 'vue' import { debugWarn } from '@/utils' +// 导入类型定义,用于增强类型检查 import type { MaybeRef } from '@vueuse/core' +/** + * 使用deprecated警告的参数类型定义。 + * 这些参数提供了关于弃用的信息,包括来源、替代方案、作用域、版本号等。 + */ type DeprecationParam = { - from: string - replacement: string - scope: string - version: string - ref: string - type?: 'API' | 'Attribute' | 'Event' | 'Slot' + from: string // 被弃用的属性、方法或事件的名称 + replacement: string // 替代的属性、方法或事件的名称 + scope: string // 弃用的上下文或模块名称 + version: string // 弃用的版本号 + ref: string // 更多信息的链接或引用 + type?: 'API' | 'Attribute' | 'Event' | 'Slot' // 被弃用的类型,如API、属性、事件、插槽 } +/** + * 监视并警告使用了被弃用的特性。 + * 当指定的条件满足时,会在控制台输出弃用警告,建议使用新的特性替代。 + * + * @param {DeprecationParam} param0 弃用警告的参数对象,包括弃用信息和类型。 + * @param condition 一个布尔值或引用类型,当其为真时,触发弃用警告。 + */ export const useDeprecated = ( { from, replacement, scope, version, ref, type = 'API' }: DeprecationParam, condition: MaybeRef ) => { + // 使用Vue的watch函数监听condition的变化 watch( - () => unref(condition), + () => unref(condition), // 确保condition是解引用的,以直接获取其值 (val) => { if (val) { + // 如果condition为真,触发弃用警告 debugWarn( scope, `[${type}] ${from} is about to be deprecated in version ${version}, please use ${replacement} instead. @@ -29,7 +43,7 @@ For more detail, please visit: ${ref} } }, { - immediate: true + immediate: true // 立即执行一次检查,确保在组件初始化时就能发出弃用警告 } ) } From e2642b82c2e7127c2f5cee611ad5d3f95594825a Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Thu, 25 Jul 2024 09:33:14 +0800 Subject: [PATCH 12/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=8F=AF=E6=8B=96=E5=8A=A8=E5=8A=9F=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useDraggable.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/hooks/useDraggable.ts b/src/hooks/useDraggable.ts index 1441ba4..bf82941 100644 --- a/src/hooks/useDraggable.ts +++ b/src/hooks/useDraggable.ts @@ -2,76 +2,113 @@ import { onBeforeUnmount, onMounted, watchEffect } from 'vue' import { addUnit } from '@/utils' import type { ComputedRef, Ref } from 'vue' +/** + * 使用Vue的ref和computedRef实现可拖动功能。 + * @param targetRef 目标元素的ref,被拖动的元素。 + * @param dragRef 拖动触发元素的ref。 + * @param draggable 是否可拖动的计算属性。 + * @param overflow 是否允许拖动超出边界。 + */ export const useDraggable = ( targetRef: Ref, dragRef: Ref, draggable: ComputedRef, overflow?: ComputedRef ) => { + // 记录拖动过程中的偏移量 let transform = { offsetX: 0, offsetY: 0 } + /** + * 鼠标按下时的处理函数,初始化拖动状态。 + * @param e 鼠标事件对象。 + */ const onMousedown = (e: MouseEvent) => { + // 记录鼠标按下的位置 const downX = e.clientX const downY = e.clientY + // 当前偏移量 const { offsetX, offsetY } = transform + // 目标元素的布局信息 const targetRect = targetRef.value!.getBoundingClientRect() const targetLeft = targetRect.left const targetTop = targetRect.top const targetWidth = targetRect.width const targetHeight = targetRect.height + // 页面可滚动区域的大小 const clientWidth = document.documentElement.clientWidth const clientHeight = document.documentElement.clientHeight + // 计算拖动的边界 const minLeft = -targetLeft + offsetX const minTop = -targetTop + offsetY const maxLeft = clientWidth - targetLeft - targetWidth + offsetX const maxTop = clientHeight - targetTop - targetHeight + offsetY + /** + * 鼠标移动时的处理函数,更新元素位置。 + * @param e 鼠标事件对象。 + */ const onMousemove = (e: MouseEvent) => { + // 计算元素的新位置 let moveX = offsetX + e.clientX - downX let moveY = offsetY + e.clientY - downY + // 如果不允许超出边界,则限制元素的位置 if (!overflow?.value) { moveX = Math.min(Math.max(moveX, minLeft), maxLeft) moveY = Math.min(Math.max(moveY, minTop), maxTop) } + // 更新偏移量 transform = { offsetX: moveX, offsetY: moveY } + // 设置元素的新位置 if (targetRef.value) { targetRef.value.style.transform = `translate(${addUnit(moveX)}, ${addUnit(moveY)})` } } + /** + * 鼠标松开时的处理函数,停止拖动。 + */ const onMouseup = () => { + // 移除拖动事件监听器 document.removeEventListener('mousemove', onMousemove) document.removeEventListener('mouseup', onMouseup) } + // 添加拖动事件监听器 document.addEventListener('mousemove', onMousemove) document.addEventListener('mouseup', onMouseup) } + /** + * 开启拖动功能,为拖动触发元素添加鼠标按下事件监听器。 + */ const onDraggable = () => { if (dragRef.value && targetRef.value) { dragRef.value.addEventListener('mousedown', onMousedown) } } + /** + * 关闭拖动功能,移除拖动触发元素的鼠标按下事件监听器。 + */ const offDraggable = () => { if (dragRef.value && targetRef.value) { dragRef.value.removeEventListener('mousedown', onMousedown) } } + // 在组件挂载时,根据draggable的值决定是否开启拖动功能 onMounted(() => { watchEffect(() => { if (draggable.value) { @@ -82,6 +119,7 @@ export const useDraggable = ( }) }) + // 在组件卸载前,关闭拖动功能 onBeforeUnmount(() => { offDraggable() }) From 739b2d63d0a38a66f22b746fbe314a4a0b15529e Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 29 Jul 2024 16:27:14 +0800 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=85=A8?= =?UTF-8?q?=E5=B1=80Esc=E9=94=AE=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useEscapeKeydown.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/hooks/useEscapeKeydown.ts b/src/hooks/useEscapeKeydown.ts index ef427d9..48e6538 100644 --- a/src/hooks/useEscapeKeydown.ts +++ b/src/hooks/useEscapeKeydown.ts @@ -2,8 +2,21 @@ import { onBeforeUnmount, onMounted } from 'vue' import { isClient } from '@/utils' import { EVENT_CODE } from '@/constants' +/** + * 注册用于处理Esc键按下的回调函数。 + * + * 此功能旨在使多个组件能够监听Esc键的按下事件,而无需重复添加事件监听器。 + * 通过将回调函数添加到全局处理程序列表中,当Esc键被按下时,所有注册的处理程序都将被调用。 + * 在组件卸载时,相应的处理程序将从列表中移除,以避免内存泄漏。 + */ let registeredEscapeHandlers: ((e: KeyboardEvent) => void)[] = [] +/** + * 缓存的事件处理函数,用于处理键盘事件。 + * + * 这个函数的目的是为了避免在每个组件中都直接添加键盘事件监听器,从而减少性能开销。 + * 当Esc键被按下时,它将调用所有注册的处理程序。 + */ const cachedHandler = (e: Event) => { const event = e as KeyboardEvent if (event.key === EVENT_CODE.esc) { @@ -11,18 +24,30 @@ const cachedHandler = (e: Event) => { } } +/** + * 使用Esc键的键盘下压事件。 + * + * 此函数提供了一个方便的方法来注册一个回调函数,该函数将在按下Esc键时被调用。 + * 它利用Vue的生命周期钩子来添加和移除事件监听器,确保组件在挂载和卸载时正确处理事件。 + * + * @param handler 处理Esc键按下的回调函数。它将接收键盘事件作为参数。 + */ export const useEscapeKeydown = (handler: (e: KeyboardEvent) => void) => { onMounted(() => { + // 当没有注册任何处理程序时,添加键盘事件监听器 if (registeredEscapeHandlers.length === 0) { document.addEventListener('keydown', cachedHandler) } + // 将当前处理程序添加到注册列表中 if (isClient) registeredEscapeHandlers.push(handler) }) onBeforeUnmount(() => { + // 从注册列表中移除当前处理程序 registeredEscapeHandlers = registeredEscapeHandlers.filter( (registeredHandler) => registeredHandler !== handler ) + // 当注册列表为空时,移除键盘事件监听器 if (registeredEscapeHandlers.length === 0) { if (isClient) document.removeEventListener('keydown', cachedHandler) } From 810493f70abd0b1b61eac8ef47ff8ed0fc778ed6 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Fri, 2 Aug 2024 09:09:42 +0800 Subject: [PATCH 14/29] =?UTF-8?q?feat(useId):=20=E6=B7=BB=E5=8A=A0ID?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useId.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/hooks/useId.ts b/src/hooks/useId.ts index 9df990d..729c4c1 100644 --- a/src/hooks/useId.ts +++ b/src/hooks/useId.ts @@ -5,24 +5,43 @@ import { useGetDerivedNamespace } from './useNameSpace' import type { InjectionKey, Ref } from 'vue' import type { MaybeRef } from '@vueuse/core' +/** + * 定义了ID注入上下文的类型,包括前缀和当前计数。 + */ export type SIdInjectionContext = { prefix: number current: number } +/** + * 默认的ID注入对象,包含一个随机前缀和当前计数为0。 + */ const defaultIdInjection = { prefix: Math.floor(Math.random() * 10000), current: 0 } +/** + * 声明了一个 InjectionKey,用于在组件之间共享ID生成的上下文。 + */ export const ID_INJECTION_KEY: InjectionKey = Symbol('elIdInjection') +/** + * 提供了一个函数,用于获取当前组件实例的ID生成上下文。 + * 如果当前不在Vue实例中,则返回默认的ID生成上下文。 + */ export const useIdInjection = (): SIdInjectionContext => { return getCurrentInstance() ? inject(ID_INJECTION_KEY, defaultIdInjection) : defaultIdInjection } +/** + * 提供了一个函数,用于生成元素的唯一ID。 + * 如果提供了确定性的ID,则使用该ID;否则,根据命名空间和ID生成上下文生成一个唯一ID。 + * 在服务器渲染环境下,如果没有提供ID生成上下文,会发出警告。 + */ export const useId = (deterministicId?: MaybeRef): Ref => { const idInjection = useIdInjection() + /* 在非客户端环境中,且ID注入上下文为默认值时,发出警告。这通常发生在服务器渲染环境下。 */ if (!isClient && idInjection === defaultIdInjection) { debugWarn( 'IdInjection', @@ -34,7 +53,9 @@ usage: app.provide(ID_INJECTION_KEY, { ) } + /* 使用衍生的命名空间来确保ID的唯一性。 */ const namespace = useGetDerivedNamespace() + /* 计算最终的ID值,优先使用确定性的ID,如果未提供,则生成一个唯一的ID。 */ const idRef = computed( () => unref(deterministicId) || From 0c2d25a6168f62fa69ddb20e8e29cd6afe00ae0e Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 5 Aug 2024 09:20:38 +0800 Subject: [PATCH 15/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96=E5=8A=9F=E8=83=BD=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLocal.ts | 50 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/hooks/useLocal.ts b/src/hooks/useLocal.ts index 94c1484..ee3053a 100644 --- a/src/hooks/useLocal.ts +++ b/src/hooks/useLocal.ts @@ -6,19 +6,44 @@ import type { MaybeRef } from '@vueuse/core' import type { InjectionKey, Ref } from 'vue' import type { Language } from '@/locale' +/** + * 翻译器选项类型,用于存储翻译键和对应的值或数字。 + */ export type TranslatorOption = Record + +/** + * 翻译函数类型,接受一个路径和选项,返回翻译后的字符串。 + */ export type Translator = (path: string, option?: TranslatorOption) => string + +/** + * 本地化上下文类型,包含本地化语言、语言代码和翻译函数。 + */ export type LocaleContext = { locale: Ref lang: Ref t: Translator } +/** + * 创建一个翻译函数,根据提供的本地化语言进行翻译。 + * + * @param locale 本地化语言,可能是Vue的ref或普通对象。 + * @returns 返回一个翻译函数,用于根据给定的路径和选项翻译文本。 + */ export const buildTranslator = (locale: MaybeRef): Translator => (path, option) => translate(path, option, unref(locale)) +/** + * 根据提供的路径、选项和本地化语言,执行实际的翻译操作。 + * + * @param path 翻译文本的路径,使用点符号表示对象的嵌套属性。 + * @param option 翻译选项,用于替换路径中定义的占位符。 + * @param locale 本地化语言对象。 + * @returns 返回翻译后的文本,如果路径不存在,则返回原始路径。 + */ export const translate = ( path: string, option: undefined | TranslatorOption, @@ -26,22 +51,39 @@ export const translate = ( ): string => (get(locale, path, path) as string).replace( /\{(\w+)\}/g, - (_, key) => `${option?.[key] ?? `{${key}}`}` + (_, key) => `${option?.[key] ?? `{${key}}`}` // 如果选项中存在占位符的值,则替换;否则保留占位符。 ) +/** + * 创建本地化上下文对象,包含语言、语言代码和翻译函数。 + * + * @param locale 本地化语言,可能是Vue的ref或普通对象。 + * @returns 返回一个本地化上下文对象,用于在组件中使用。 + */ export const buildLocaleContext = (locale: MaybeRef): LocaleContext => { - const lang = computed(() => unref(locale).name) - const localeRef = isRef(locale) ? locale : ref(locale) + const lang = computed(() => unref(locale).name) // 计算属性,获取语言代码。 + const localeRef = isRef(locale) ? locale : ref(locale) // 根据输入类型,创建相应的ref。 return { lang, locale: localeRef, - t: buildTranslator(locale) + t: buildTranslator(locale) // 创建翻译函数。 } } +/** + * 本地化上下文的注入键,用于在组件中注入本地化上下文。 + */ export const localeContextKey: InjectionKey> = Symbol('localeContextKey') +/** + * 初始化并返回本地化上下文,如果提供了自定义的本地化语言,则使用之;否则使用默认语言。 + * + * @param localeOverrides 可以覆盖默认本地化语言的ref。 + * @returns 返回一个本地化上下文对象,用于在组件中使用。 + */ export const useLocale = (localeOverrides?: Ref) => { + // 根据是否有自定义本地化语言,决定使用哪个locale。 const locale = localeOverrides || inject(localeContextKey, ref())! + // 创建并返回本地化上下文。 return buildLocaleContext(computed(() => locale.value || English)) } From 5ccd59a5a0ee74e5b9cb4ee3ff9aad633767993b Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 6 Aug 2024 09:14:11 +0800 Subject: [PATCH 16/29] =?UTF-8?q?feat(useLockscreen):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=94=81=E5=AE=9A=E5=B1=8F=E5=B9=95=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLockscreen.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/hooks/useLockscreen.ts b/src/hooks/useLockscreen.ts index b63ab49..d8b78a7 100644 --- a/src/hooks/useLockscreen.ts +++ b/src/hooks/useLockscreen.ts @@ -6,7 +6,7 @@ import { hasClass, isClient, removeClass, - throwError, + throwError } from '@/utils' import { useNamespace } from './useNameSpace' @@ -23,10 +23,18 @@ export type UseLockScreenOptions = { * When the trigger became true, it assumes modal is now opened and vice versa. * @param trigger {Ref} */ + +/** + * 使用锁定屏幕功能。 + * 当给定的触发器引用变为true时,锁定屏幕,反之则解锁屏幕。 + * @param trigger 触发屏幕锁定的引用。当其值为true时,锁定屏幕;为false时,解锁屏幕。 + * @param options 可选参数,用于自定义命名空间。 + */ export const useLockscreen = ( trigger: Ref, options: UseLockScreenOptions = {} ) => { + // 检查传入的trigger是否为引用类型,如果不是,则抛出错误。 if (!isRef(trigger)) { throwError( '[useLockscreen]', @@ -34,18 +42,23 @@ export const useLockscreen = ( ) } + // 使用提供的命名空间或默认命名空间。 const ns = options.ns || useNamespace('popup') + // 计算隐藏类名。 const hiddenCls = computed(() => ns.bm('parent', 'hidden')) + // 如果不在客户端或body已经具有隐藏类,则直接返回。 if (!isClient || hasClass(document.body, hiddenCls.value)) { return } + // 初始化滚动条宽度、是否曾移除过隐藏类、body的宽度。 let scrollBarWidth = 0 let withoutHiddenClass = false let bodyWidth = '0' + // 清理函数,用于解锁屏幕。 const cleanup = () => { setTimeout(() => { removeClass(document?.body, hiddenCls.value) @@ -54,17 +67,22 @@ export const useLockscreen = ( } }, 200) } + + // 监视trigger的变化,根据值来锁定或解锁屏幕。 watch(trigger, (val) => { if (!val) { cleanup() return } + // 检查是否曾经移除过隐藏类,并记录body的宽度。 withoutHiddenClass = !hasClass(document.body, hiddenCls.value) if (withoutHiddenClass) { bodyWidth = document.body.style.width } + // 获取滚动条宽度。 scrollBarWidth = getScrollBarWidth(ns.namespace.value) + // 检查是否需要添加滚动条隐藏逻辑。 const bodyHasOverflow = document.documentElement.clientHeight < document.body.scrollHeight const bodyOverflowY = getStyle(document.body, 'overflowY') @@ -75,7 +93,9 @@ export const useLockscreen = ( ) { document.body.style.width = `calc(100% - ${scrollBarWidth}px)` } + // 添加隐藏类到body。 addClass(document.body, hiddenCls.value) }) + // 在作用域销毁时清理,即解锁屏幕。 onScopeDispose(() => cleanup()) } From 8a79f7832e9c29600df3b477381b4b028dac2740 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Thu, 8 Aug 2024 09:36:28 +0800 Subject: [PATCH 17/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0BEM=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useNameSpace.ts | 96 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/hooks/useNameSpace.ts b/src/hooks/useNameSpace.ts index 863a194..403d921 100644 --- a/src/hooks/useNameSpace.ts +++ b/src/hooks/useNameSpace.ts @@ -2,9 +2,24 @@ import { computed, getCurrentInstance, inject, ref, unref } from 'vue' import type { InjectionKey, Ref } from 'vue' +/** + * 默认的命名空间前缀。 + */ export const defaultNamespace = 's' +/** + * 状态前缀,用于生成BEM样式中的状态类。 + */ const statePrefix = 'is-' +/** + * 根据提供的参数生成BEM样式类名。 + * @param namespace 命名空间字符串。 + * @param block 块名。 + * @param blockSuffix 块的后缀。 + * @param element 元素名。 + * @param modifier 修改器名。 + * @returns 生成的BEM类名。 + */ const _bem = ( namespace: string, block: string, @@ -25,9 +40,17 @@ const _bem = ( return cls } +/** + * 命名空间上下文的注入键。 + */ export const namespaceContextKey: InjectionKey> = Symbol('namespaceContextKey') +/** + * 使用提供的命名空间覆盖值获取派生的命名空间。 + * @param namespaceOverrides 命名空间覆盖的引用。 + * @returns 派生的命名空间的引用。 + */ export const useGetDerivedNamespace = (namespaceOverrides?: Ref) => { const derivedNamespace = namespaceOverrides || @@ -40,21 +63,73 @@ export const useGetDerivedNamespace = (namespaceOverrides?: Ref) => { const namespace = useGetDerivedNamespace(namespaceOverrides) + /** + * 生成块类名。 + * @param blockSuffix 块的后缀。 + * @returns 生成的块类名。 + */ const b = (blockSuffix = '') => _bem(namespace.value, block, blockSuffix, '', '') + /** + * 生成元素类名。 + * @param element 元素名。 + * @returns 生成的元素类名。 + */ const e = (element?: string) => (element ? _bem(namespace.value, block, '', element, '') : '') + /** + * 生成修改器类名。 + * @param modifier 修改器名。 + * @returns 生成的修改器类名。 + */ const m = (modifier?: string) => (modifier ? _bem(namespace.value, block, '', '', modifier) : '') + /** + * 生成带有块后缀和元素的类名。 + * @param blockSuffix 块的后缀。 + * @param element 元素名。 + * @returns 生成的类名。 + */ const be = (blockSuffix?: string, element?: string) => blockSuffix && element ? _bem(namespace.value, block, blockSuffix, element, '') : '' + /** + * 生成带有元素和修改器的类名。 + * @param element 元素名。 + * @param modifier 修改器名。 + * @returns 生成的类名。 + */ const em = (element?: string, modifier?: string) => element && modifier ? _bem(namespace.value, block, '', element, modifier) : '' + /** + * 生成带有块后缀和修改器的类名。 + * @param blockSuffix 块的后缀。 + * @param modifier 修改器名。 + * @returns 生成的类名。 + */ const bm = (blockSuffix?: string, modifier?: string) => blockSuffix && modifier ? _bem(namespace.value, block, blockSuffix, '', modifier) : '' + /** + * 生成带有块后缀、元素和修改器的类名。 + * @param blockSuffix 块的后缀。 + * @param element 元素名。 + * @param modifier 修改器名。 + * @returns 生成的类名。 + */ const bem = (blockSuffix?: string, element?: string, modifier?: string) => blockSuffix && element && modifier ? _bem(namespace.value, block, blockSuffix, element, modifier) : '' + /** + * 生成状态类名。 + * @param name 状态名。 + * @param state 状态值,如果为真,则添加状态类名。 + * @returns 生成的状态类名。 + */ const is: { (name: string, state: boolean | undefined): string (name: string): string @@ -63,6 +138,11 @@ export const useNamespace = (block: string, namespaceOverrides?: Ref) => { @@ -74,6 +154,12 @@ export const useNamespace = (block: string, namespaceOverrides?: Ref) => { const styles: Record = {} @@ -85,7 +171,17 @@ export const useNamespace = (block: string, namespaceOverrides?: Ref `--${namespace.value}-${name}` + /** + * 生成与块相关的CSS变量名。 + * @param name 变量名。 + * @returns 生成的CSS变量名。 + */ const cssVarBlockName = (name: string) => `--${namespace.value}-${block}-${name}` return { From 8a7fb0229ab8aef9b11338a79f2db982d241cbfe Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 12 Aug 2024 13:57:19 +0800 Subject: [PATCH 18/29] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0useSameTarget?= =?UTF-8?q?=E9=92=A9=E5=AD=90=E4=BB=A5=E7=A1=AE=E4=BF=9D=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E4=BA=8B=E4=BB=B6=E7=9A=84=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useSameTarget.ts | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/hooks/useSameTarget.ts b/src/hooks/useSameTarget.ts index 38a8f02..fb35b0b 100644 --- a/src/hooks/useSameTarget.ts +++ b/src/hooks/useSameTarget.ts @@ -1,32 +1,61 @@ // eslint-disable-next-line vue/prefer-import-from-vue import { NOOP } from '@vue/shared' +/** + * 此函数用于确保只有当mousedown和mouseup事件发生在同一个目标上时,才触发click事件。 + * 这对于防止移动浏览器中的幽灵点击很有用。 + * + * @param handleClick 用户提供的自定义click事件处理器,仅当mousedown和mouseup事件发生在同一目标上时调用。 + * @returns 返回一个对象,包含onClick、onMousedown和OnMouseup事件处理器函数。 + */ export const useSameTarget = (handleClick?: (e: MouseEvent) => void) => { + // 如果没有提供自定义click事件处理器,返回一组空操作(event handlers)。 if (!handleClick) { return { onClick: NOOP, onMousedown: NOOP, onMouseup: NOOP } } + // 用于记录mousedown事件是否发生在当前目标上。 let mousedownTarget = false + // 用于记录mouseup事件是否发生在当前目标上。 let mouseupTarget = false + // refer to this https://javascript.info/mouse-events-basics // events fired in the order: mousedown -> mouseup -> click // we need to set the mousedown handle to false after click fired. + + /** + * click事件处理器。 + * 检查mousedown和mouseup事件是否发生在同一目标上,如果是,则调用自定义click事件处理器。 + * 处理后,重置mousedownTarget和mouseupTarget的状态。 + */ const onClick = (e: MouseEvent) => { - // if and only if + // 当且仅当mousedownTarget和mouseupTarget都为真时执行。 if (mousedownTarget && mouseupTarget) { handleClick(e) } mousedownTarget = mouseupTarget = false } + /** + * mousedown事件处理器。 + * 如果mousedown事件发生在当前目标上,设置mousedownTarget状态为真。 + */ const onMousedown = (e: MouseEvent) => { // marking current mousedown target. + // 标记当前mousedown目标。 mousedownTarget = e.target === e.currentTarget } + + /** + * mouseup事件处理器。 + * 如果mouseup事件发生在当前目标上,设置mouseupTarget状态为真。 + */ const onMouseup = (e: MouseEvent) => { // marking current mouseup target. + // 标记当前mouseup目标。 mouseupTarget = e.target === e.currentTarget } + // 返回事件处理器函数集合。 return { onClick, onMousedown, onMouseup } } From 0e31027418d51dd7ebbd200dcaefd7365891e60f Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Fri, 16 Aug 2024 15:51:57 +0800 Subject: [PATCH 19/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E4=BD=BF=E7=94=A8SizeProp=E7=9A=84=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E5=B0=BA=E5=AF=B8=E8=AE=A1=E7=AE=97=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useSize.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/hooks/useSize.ts b/src/hooks/useSize.ts index e7e1ea2..2416729 100644 --- a/src/hooks/useSize.ts +++ b/src/hooks/useSize.ts @@ -2,28 +2,51 @@ import { computed, inject, unref } from 'vue' import { buildProp } from '@/utils' import { componentSizes } from '@/constants' +// 导入类型定义,增强代码的可读性和可维护性 import type { InjectionKey, Ref } from 'vue' import type { ComponentSize } from '@/constants' +/** + * 定义尺寸属性,用于组件的props选项。 + * 允许组件接收特定于尺寸的字符串值,这些值由组件尺寸常量定义。 + */ export const useSizeProp = buildProp({ type: String, values: componentSizes, required: false } as const) +/** + * 提供组件可用的尺寸属性配置。 + */ export const useSizeProps = { size: useSizeProp } +/** + * 定义一个上下文接口,用于封装尺寸的引用。 + * 这使得在不同的组件中可以共享和访问尺寸信息。 + */ export interface SizeContext { size: Ref } +/** + * 定义一个注入键,用于跨组件共享尺寸上下文。 + * 这是一个符号,保证了全局唯一性,避免了命名冲突。 + */ export const SIZE_INJECTION_KEY: InjectionKey = Symbol('size') +/** + * 提供一个全局尺寸的计算属性。 + * 通过Vue的inject函数注入尺寸上下文,然后计算出当前应使用的尺寸。 + * 如果没有注入尺寸上下文,则默认返回空字符串。 + */ export const useGlobalSize = () => { + // 从父组件或根组件中注入尺寸上下文 const injectedSize = inject(SIZE_INJECTION_KEY, {} as SizeContext) + // 计算并返回当前应使用的尺寸,如果没有注入则使用空字符串作为默认值 return computed(() => { return unref(injectedSize.size) || '' }) From 817b55b1fbe448341b61f9acb59ad16caa9e8f9b Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 19 Aug 2024 09:32:22 +0800 Subject: [PATCH 20/29] =?UTF-8?q?feat(utils/dom):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86position.ts=E6=96=87=E4=BB=B6=E7=9A=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dom/position.ts | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/utils/dom/position.ts b/src/utils/dom/position.ts index 353b5b9..d744d6f 100644 --- a/src/utils/dom/position.ts +++ b/src/utils/dom/position.ts @@ -1,5 +1,11 @@ import { isClient } from '../browser' +/** + * 判断一个元素是否在指定的容器内(包括容器本身)。 + * @param el 待检查的元素 + * @param container 指定的容器元素或窗口对象 + * @returns 如果元素在容器内(包括容器本身),返回true;否则返回false。 + */ export const isInContainer = ( el?: Element, container?: Element | Window @@ -16,7 +22,7 @@ export const isInContainer = ( top: 0, right: window.innerWidth, bottom: window.innerHeight, - left: 0, + left: 0 } } return ( @@ -27,6 +33,11 @@ export const isInContainer = ( ) } +/** + * 获取元素距离文档顶部的距离。 + * @param el 要计算距离的元素 + * @returns 元素距离文档顶部的距离。 + */ export const getOffsetTop = (el: HTMLElement) => { let offset = 0 let parent = el @@ -39,13 +50,21 @@ export const getOffsetTop = (el: HTMLElement) => { return offset } -export const getOffsetTopDistance = ( - el: HTMLElement, - containerEl: HTMLElement -) => { +/** + * 计算两个元素之间的垂直距离。 + * @param el 元素A + * @param containerEl 元素B,作为参考的容器元素 + * @returns 元素A距离元素B顶部的垂直距离的绝对值。 + */ +export const getOffsetTopDistance = (el: HTMLElement, containerEl: HTMLElement) => { return Math.abs(getOffsetTop(el) - getOffsetTop(containerEl)) } +/** + * 获取鼠标或触摸事件的客户端坐标。 + * @param event 鼠标或触摸事件对象 + * @returns 包含客户端X和Y坐标的对象。 + */ export const getClientXY = (event: MouseEvent | TouchEvent) => { let clientX: number let clientY: number @@ -61,6 +80,6 @@ export const getClientXY = (event: MouseEvent | TouchEvent) => { } return { clientX, - clientY, + clientY } } From 2efca9903a2524ad227942c909170b1187a5f7ad Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Tue, 20 Aug 2024 09:26:29 +0800 Subject: [PATCH 21/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0scroll=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dom/scroll.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/utils/dom/scroll.ts b/src/utils/dom/scroll.ts index cc57fc6..bf0ce92 100644 --- a/src/utils/dom/scroll.ts +++ b/src/utils/dom/scroll.ts @@ -4,6 +4,12 @@ import { isWindow } from '../types' import { cAF, rAF } from '../raf' import { getStyle } from './style' +/** + * 判断元素是否允许滚动。 + * @param el 要检查的元素 + * @param isVertical 是否为垂直滚动,默认为undefined,表示不限制滚动方向 + * @returns 如果元素允许滚动,则返回true;否则返回false + */ export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => { if (!isClient) return false @@ -18,6 +24,12 @@ export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => { return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s)) } +/** + * 获取元素的滚动容器。 + * @param el 要检查的元素 + * @param isVertical 是否为垂直滚动,默认为undefined,表示不限制滚动方向 + * @returns 滚动容器,可能是Window或HTMLElement;如果没有滚动容器,则返回undefined + */ export const getScrollContainer = ( el: HTMLElement, isVertical?: boolean @@ -37,6 +49,11 @@ export const getScrollContainer = ( return parent } +/** + * 计算滚动条的宽度。 + * @param namespace 类名前缀,用于创建测试元素 + * @returns 滚动条的宽度 + */ let scrollBarWidth: number export const getScrollBarWidth = (namespace: string): number => { if (!isClient) return 0 @@ -64,6 +81,11 @@ export const getScrollBarWidth = (namespace: string): number => { return scrollBarWidth } +/** + * 将元素滚动到其容器的顶部。 + * @param container 元素的容器 + * @param selected 要滚动到顶部的元素 + */ /** * Scroll with in the container element, positioning the **selected** element at the top * of the container @@ -103,6 +125,14 @@ export function scrollIntoView( } } +/** + * 平滑滚动到指定位置。 + * @param container 滚动容器,可以是HTMLElement或Window + * @param from 起始位置 + * @param to 目标位置 + * @param duration 滚动持续时间 + * @param callback 滚动完成后的回调函数 + */ export function animateScrollTo( container: HTMLElement | Window, from: number, @@ -142,6 +172,12 @@ export function animateScrollTo( } } +/** + * 获取滚动元素。 + * @param target 目标元素 + * @param container 滚动容器,可以是HTMLElement或Window + * @returns 滚动元素 + */ export const getScrollElement = ( target: HTMLElement, container: HTMLElement | Window @@ -152,6 +188,11 @@ export const getScrollElement = ( return container } +/** + * 获取滚动位置。 + * @param container 滚动容器,可以是HTMLElement或Window + * @returns 滚动位置 + */ export const getScrollTop = (container: HTMLElement | Window) => { if (isWindow(container)) { return window.scrollY From a007f9f52d44273b701d9895179050103dfa69ae Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Thu, 29 Aug 2024 09:19:55 +0800 Subject: [PATCH 22/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=B0?= =?UTF-8?q?=E7=9A=84DOM=E6=93=8D=E4=BD=9C=E5=B7=A5=E5=85=B7=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dom/style.ts | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/utils/dom/style.ts b/src/utils/dom/style.ts index 8fdd898..5ef1d9e 100644 --- a/src/utils/dom/style.ts +++ b/src/utils/dom/style.ts @@ -7,25 +7,52 @@ import type { CSSProperties } from 'vue' const SCOPE = 'utils/dom/style' +/** + * 将类名字符串转换为数组形式。 + * @param cls 类名字符串,可以包含多个类名,用空格分隔。 + * @returns 过滤后的类名数组,不包含空格或空字符串。 + */ export const classNameToArray = (cls = '') => cls.split(' ').filter((item) => !!item.trim()) +/** + * 检查元素是否包含指定的类名。 + * @param el DOM元素。 + * @param cls 要检查的类名。 + * @returns 如果元素包含类名,则返回true,否则返回false。 + */ export const hasClass = (el: Element, cls: string): boolean => { if (!el || !cls) return false if (cls.includes(' ')) throw new Error('className should not contain space.') return el.classList.contains(cls) } +/** + * 为元素添加一个或多个类名。 + * @param el DOM元素。 + * @param cls 要添加的类名字符串,可以包含多个类名,用空格分隔。 + */ export const addClass = (el: Element, cls: string) => { if (!el || !cls.trim()) return el.classList.add(...classNameToArray(cls)) } +/** + * 从元素中移除一个或多个类名。 + * @param el DOM元素。 + * @param cls 要移除的类名字符串,可以包含多个类名,用空格分隔。 + */ export const removeClass = (el: Element, cls: string) => { if (!el || !cls.trim()) return el.classList.remove(...classNameToArray(cls)) } +/** + * 获取元素的指定样式值。 + * @param element HTML元素。 + * @param styleName 样式名,可以是驼峰式或连字符式。 + * @returns 元素的指定样式值。 + */ export const getStyle = ( element: HTMLElement, styleName: keyof CSSProperties @@ -44,6 +71,12 @@ export const getStyle = ( } } +/** + * 设置元素的指定样式值。 + * @param element HTML元素。 + * @param styleName 样式名,可以是驼峰式或连字符式。 + * @param value 样式值。 + */ export const setStyle = ( element: HTMLElement, styleName: CSSProperties | keyof CSSProperties, @@ -61,6 +94,11 @@ export const setStyle = ( } } +/** + * 移除元素的指定样式值。 + * @param element HTML元素。 + * @param style 样式名,可以是对象或驼峰式/连字符式样式名。 + */ export const removeStyle = ( element: HTMLElement, style: CSSProperties | keyof CSSProperties @@ -74,6 +112,12 @@ export const removeStyle = ( } } +/** + * 为值添加单位。 + * @param value 需要添加单位的值,可以是数字或字符串。 + * @param defaultUnit 默认单位,默认为'px'。 + * @returns 添加单位后的字符串。 + */ export function addUnit(value?: string | number, defaultUnit = 'px') { if (!value) return '' if (isNumber(value) || isStringNumber(value)) { From b5bc2a30db990c65f739b95b858665e63ddc5bbc Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Fri, 6 Sep 2024 16:55:23 +0800 Subject: [PATCH 23/29] =?UTF-8?q?feat(utils):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=89=E6=AC=A1=E6=96=B9=E7=BC=93=E8=BF=9B=E7=BC=93=E5=87=BA?= =?UTF-8?q?=E7=BC=93=E5=8A=A8=E5=87=BD=E6=95=B0easeInOutCubic=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/easings.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/utils/easings.ts b/src/utils/easings.ts index 8accfa0..e78df4b 100644 --- a/src/utils/easings.ts +++ b/src/utils/easings.ts @@ -1,8 +1,25 @@ +/** + * 缓动函数:三次方缓进缓出 + * + * 该函数用于计算动画的缓动效果,通过对时间参数`t`进行处理,返回相应的变形值。 + * 主要应用于渐入渐出的动画效果,使动画在开始和结束时缓慢,中间快速。 + * + * @param t 当前时间,相对于开始时间的时间偏移量 + * @param b 开始值 + * @param c 变化量,即结束值减去开始值 + * @param d 动画的总时长 + * @returns 计算后的时间点对应的值 + */ export function easeInOutCubic(t: number, b: number, c: number, d: number) { + // 计算变化量的差值,以便后续计算 const cc = c - b + // 调整时间参数,使其相对于整个动画时长的一半进行计算 t /= d / 2 + // 如果当前时间在动画的前半段 if (t < 1) { + // 返回缓入效果的计算结果 return (cc / 2) * t * t * t + b } + // 如果当前时间在动画的后半段 return (cc / 2) * ((t -= 2) * t * t + 2) + b } From 261c90fcb3a590f5c5e6244e8d42966f928f7ff0 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 9 Sep 2024 10:51:56 +0800 Subject: [PATCH 24/29] =?UTF-8?q?feat(utils/error):=20=E5=9C=A8Element=20P?= =?UTF-8?q?lus=E6=A1=86=E6=9E=B6=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E9=94=99=E8=AF=AF=E7=B1=BB=E5=92=8C=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=E5=87=BD=E6=95=B0=E7=9A=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/error.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/utils/error.ts b/src/utils/error.ts index c28cd8f..6954058 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -1,5 +1,9 @@ import { isString } from './types' +/** + * 自定义错误类,用于在Element Plus框架中表示错误。 + * 继承自Error类,增加了错误名称的定制。 + */ class ElementPlusError extends Error { constructor(m: string) { super(m) @@ -7,17 +11,35 @@ class ElementPlusError extends Error { } } +/** + * 抛出错误。 + * + * 该函数用于在指定的上下文中抛出一个错误,错误信息包含上下文和具体消息。 + * 主要用于开发阶段捕获并提示错误。 + * + * @param scope 错误的上下文,通常是一个标识符或类名。 + * @param m 错误的具体消息。 + * @throws {ElementPlusError} 抛出一个继承自Error的ElementPlusError实例。 + */ export function throwError(scope: string, m: string): never { throw new ElementPlusError(`[${scope}] ${m}`) } +/** + * 发出调试警告。 + * + * 该函数用于在非生产环境中输出调试警告信息。 + * 支持两种调用方式:传递一个Error对象或一个字符串作为警告信息。 + * 如果传递的是一个字符串,则会将其作为上下文,与具体消息拼接后输出。 + * + * @param scope 警告的上下文,可以是一个字符串或一个Error对象。 + * @param message (可选)当scope是一个字符串时,指定具体的警告消息。 + */ export function debugWarn(err: Error): void export function debugWarn(scope: string, message: string): void export function debugWarn(scope: string | Error, message?: string): void { if (process.env.NODE_ENV !== 'production') { - const error: Error = isString(scope) - ? new ElementPlusError(`[${scope}] ${message}`) - : scope + const error: Error = isString(scope) ? new ElementPlusError(`[${scope}] ${message}`) : scope // eslint-disable-next-line no-console console.warn(error) } From 1bbebfe9dc1511598f7917f3a54b5c6cc435ea6e Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Sat, 14 Sep 2024 09:42:06 +0800 Subject: [PATCH 25/29] =?UTF-8?q?feat(utils/objects.ts):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=8E=B7=E5=8F=96=E5=AF=B9=E8=B1=A1=E9=94=AE=E5=90=8D?= =?UTF-8?q?=E5=92=8C=E9=94=AE=E5=80=BC=E5=AF=B9=E7=9A=84=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/objects.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/utils/objects.ts b/src/utils/objects.ts index a8ea214..8d97b1d 100644 --- a/src/utils/objects.ts +++ b/src/utils/objects.ts @@ -2,11 +2,35 @@ import { get, set } from 'lodash-unified' import type { Entries } from 'type-fest' import type { Arrayable } from '.' +/** + * 获取对象的键名数组。 + * @param arr - 输入的对象。 + * @returns 对象的键名数组。 + */ export const keysOf = (arr: T) => Object.keys(arr) as Array + +/** + * 获取对象的键值对数组。 + * @param arr - 输入的对象。 + * @returns 对象的键值对数组。 + */ export const entriesOf = (arr: T) => Object.entries(arr) as Entries + +// 从 '@vue/shared' 导入 hasOwn,用于检查对象是否拥有特定的自身属性 // eslint-disable-next-line vue/prefer-import-from-vue export { hasOwn } from '@vue/shared' +/** + * 获取或设置对象的属性值。 + * + * 提供了一个获取和设置对象属性值的方便方式,特别是在处理嵌套属性时。 + * 通过传入路径数组来访问和修改深层属性。 + * + * @param obj - 要访问的对象。 + * @param path - 属性的路径,可以是字符串数组。 + * @param defaultValue - 当属性不存在时的默认值。 + * @returns 包含一个getter和setter的对象,用于获取和设置属性值。 + */ export const getProp = ( obj: Record, path: Arrayable, @@ -14,9 +38,11 @@ export const getProp = ( ): { value: T } => { return { get value() { + // 使用 lodash-unified 的 get 方法安全地获取属性值 return get(obj, path, defaultValue) }, set value(val: any) { + // 使用 lodash-unified 的 set 方法设置属性值 set(obj, path, val) } } From 0295f44bed09719c5b616c80441d197c8d53e0e8 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Wed, 18 Sep 2024 09:30:32 +0800 Subject: [PATCH 26/29] =?UTF-8?q?feat(utils/strings.ts):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=A4=84=E7=90=86=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=96=87=E6=A1=A3=EF=BC=8C=E4=BC=98=E5=8C=96camelize?= =?UTF-8?q?=E3=80=81hyphenate=E5=92=8CkebabCase=E5=87=BD=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=89=B9=E6=AE=8A=E5=AD=97=E7=AC=A6=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=A6=96?= =?UTF-8?q?=E5=AD=97=E6=AF=8D=E5=A4=A7=E5=86=99=E5=87=BD=E6=95=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/strings.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/utils/strings.ts b/src/utils/strings.ts index 3617227..b737692 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -1,18 +1,45 @@ +/** + * 从 '@vue/shared' 模块导入 camelize、hyphenate 和 kebabCase 函数。 + * 这里使用了别名 kebabCase 来重写 hyphenate,以提供更直观的函数命名。 + * + * @remarks + * 这些函数用于字符串转换,是 Vue.js 核心功能的一部分。 + */ // eslint-disable-next-line vue/prefer-import-from-vue import { capitalize as toCapitalize } from '@vue/shared' export { camelize, hyphenate, - hyphenate as kebabCase, // alias -// eslint-disable-next-line vue/prefer-import-from-vue + hyphenate as kebabCase // alias + // eslint-disable-next-line vue/prefer-import-from-vue } from '@vue/shared' +/** + * 将字符串中的特殊字符转义为正则表达式安全的格式。 + * + * @param string - 待转义的字符串,默认为空字符串。 + * @returns 转义后的字符串。 + * + * @remarks + * 该函数用于处理正则表达式中可能引起问题的字符,确保它们可以在正则表达式中安全使用。 + * 它是基于 sindresorhus 的 escape-string-regexp 库实现的。 + */ /** * fork from {@link https://github.com/sindresorhus/escape-string-regexp} */ export const escapeStringRegexp = (string = '') => string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') +/** + * 将字符串的首字母大写。 + * + * @param str - 待处理的字符串。 + * @returns 大写首字母的字符串。 + * + * @remarks + * 该函数使用了 TypeScript 的泛型来保持类型信息的准确性。 + * 它通过将字符串的第一个字符转换为大写,然后与剩余的字符串拼接来实现。 + */ // NOTE: improve capitalize types. Restore previous code after the [PR](https://github.com/vuejs/core/pull/6212) merge export const capitalize = (str: T) => toCapitalize(str) as Capitalize From bf5100f0a396092434e46c45d0b8e1c37ab6a2c9 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Thu, 19 Sep 2024 09:25:00 +0800 Subject: [PATCH 27/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=88=A4=E6=96=AD=E5=87=BD=E6=95=B0=E7=9A=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/types.ts | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/utils/types.ts b/src/utils/types.ts index 15deaf3..5d82b00 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line vue/prefer-import-from-vue import { isArray, isObject, isString } from '@vue/shared' import { isNil } from 'lodash-unified' @@ -8,28 +9,68 @@ export { isString, isDate, isPromise, - isSymbol, + isSymbol + // eslint-disable-next-line vue/prefer-import-from-vue } from '@vue/shared' export { isVNode } from 'vue' +/** + * 判断一个值是否为未定义。 + * @param val 待检查的值 + * @returns 如果值为未定义,则返回true,否则返回false。 + */ export const isUndefined = (val: any): val is undefined => val === undefined + +/** + * 判断一个值是否为布尔值。 + * @param val 待检查的值 + * @returns 如果值为布尔值,则返回true,否则返回false。 + */ export const isBoolean = (val: any): val is boolean => typeof val === 'boolean' + +/** + * 判断一个值是否为数字。 + * @param val 待检查的值 + * @returns 如果值为数字,则返回true,否则返回false。 + */ export const isNumber = (val: any): val is number => typeof val === 'number' +/** + * 判断一个值是否为空。 + * 空值定义为:null、undefined、空数组、空对象或false。 + * @param val 待检查的值 + * @returns 如果值为空,则返回true,否则返回false。 + */ export const isEmpty = (val: unknown) => (!val && val !== 0) || (isArray(val) && val.length === 0) || (isObject(val) && !Object.keys(val).length) +/** + * 判断一个值是否为元素。 + * @param e 待检查的值 + * @returns 如果值为元素,则返回true,否则返回false。 + */ export const isElement = (e: unknown): e is Element => { if (typeof Element === 'undefined') return false return e instanceof Element } +/** + * 判断一个属性是否缺失。 + * 属性缺失定义为值为null或undefined。 + * @param prop 待检查的属性 + * @returns 如果属性缺失,则返回true,否则返回false。 + */ export const isPropAbsent = (prop: unknown): prop is null | undefined => { return isNil(prop) } +/** + * 判断一个字符串是否可以转换为数字。 + * @param val 待检查的字符串 + * @returns 如果字符串可以转换为数字,则返回true,否则返回false。 + */ export const isStringNumber = (val: string): boolean => { if (!isString(val)) { return false @@ -37,6 +78,11 @@ export const isStringNumber = (val: string): boolean => { return !Number.isNaN(Number(val)) } +/** + * 判断一个值是否为Window对象。 + * @param val 待检查的值 + * @returns 如果值为Window对象,则返回true,否则返回false。 + */ export const isWindow = (val: unknown): val is Window => { return val === window } From e74eef5ef1328e68248dba5795fdf30be449c438 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Fri, 20 Sep 2024 10:01:35 +0800 Subject: [PATCH 28/29] =?UTF-8?q?feat(utils/validator):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=94=A8=E4=BA=8E=E6=A3=80=E6=9F=A5=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=B0=BA=E5=AF=B8=E5=92=8C=E6=97=A5=E6=9C=9F=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=9C=89=E6=95=88=E6=80=A7=E7=9A=84?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/validator.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/utils/validator.ts b/src/utils/validator.ts index 5801317..cac86f2 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -1,8 +1,30 @@ +/** + * 从 '@/constants' 导入组件尺寸和日期选择类型常量。 + * 这里的导入语句为类型导入,用于在类型检查时引用定义在常量文件中的枚举类型。 + */ import { componentSizes, datePickTypes } from '@/constants' import type { ComponentSize, DatePickType } from '@/constants' +/** + * 验证给定的字符串是否为有效的组件尺寸。 + * + * @param val 待验证的字符串,预期是组件尺寸的名称。 + * @returns 如果 `val` 是有效的组件尺寸或空字符串,则返回 true;否则返回 false。 + * + * 此函数通过检查给定的字符串是否存在于组件尺寸的常量数组中来验证其有效性。 + * 包含空字符串是为了允许函数在验证失败时返回一个“空”值。 + */ export const isValidComponentSize = (val: string): val is ComponentSize | '' => ['', ...componentSizes].includes(val) +/** + * 验证给定的字符串是否为有效的日期选择类型。 + * + * @param val 待验证的字符串,预期是日期选择类型的名称。 + * @returns 如果 `val` 是有效的日期选择类型,则返回 true;否则返回 false。 + * + * 此函数通过检查给定的字符串是否存在于日期选择类型常量数组中来验证其有效性。 + * 注意这里使用类型断言将 `datePickTypes` 强制转换为字符串数组,以符合 `includes` 方法的期望参数类型。 + */ export const isValidDatePickType = (val: string): val is DatePickType => ([...datePickTypes] as string[]).includes(val) From 8425f92fbb0f83522d52258913e45ecfce28d451 Mon Sep 17 00:00:00 2001 From: liuyunhe Date: Mon, 23 Sep 2024 09:25:31 +0800 Subject: [PATCH 29/29] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BA=86App.vu?= =?UTF-8?q?e=E6=96=87=E4=BB=B6=EF=BC=8C=E5=BC=95=E5=85=A5=E4=BA=86getCurre?= =?UTF-8?q?ntInstance=E6=96=B9=E6=B3=95=EF=BC=8C=E5=B9=B6=E5=9C=A8onMounte?= =?UTF-8?q?d=E9=92=A9=E5=AD=90=E4=B8=AD=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97=E3=80=82=E5=90=8C=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=AF=B9radio=E5=92=8CbuttonRef=E7=9A=84=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E6=96=B9=E5=BC=8F=E8=BF=9B=E8=A1=8C=E4=BA=86=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/App.vue b/src/App.vue index b31f608..e44814e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -221,7 +221,7 @@