Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
liuyunhe committed Sep 23, 2024
2 parents d60c23b + 8425f92 commit f5265de
Show file tree
Hide file tree
Showing 27 changed files with 822 additions and 46 deletions.
9 changes: 6 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@
</template>

<script setup lang="ts">
import { h, nextTick, onMounted, reactive, ref } from 'vue'
import { getCurrentInstance, h, nextTick, onMounted, reactive, ref, type ComponentPublicInstance } from 'vue'
import Button from '@/components/Button/Button.vue'
import Icon from './components/Icon/Icon.vue'
import SRadio, { SRadioGroup, SRadioButton } from './components/Radio'
Expand Down Expand Up @@ -281,7 +281,7 @@ const openedValue = ref(['a'])
const dialogFormVisible = ref(false)
const radio = ref(null)
const radio = ref()
const num = ref(1)
Expand Down Expand Up @@ -380,6 +380,9 @@ const closeAlert = () => {
}
onMounted(() => {
const vm = getCurrentInstance()
console.log('🚀 ~ onMounted ~ :', vm)
console.log('🚀 ~ onMounted ~ alertRef:', alertRef.value)
const instance = createMessage({
type: 'warning',
message: 'hello word',
Expand All @@ -392,7 +395,7 @@ onMounted(() => {
createMessage({ type: 'info', message: 'hello word', showClose: true })
createMessage({ type: 'danger', message: 'hello word', showClose: true })
if (buttonRef.value) {
console.log('🚀 ~ onMounted ~ buttonRef.value:', buttonRef.value)
console.log('🚀 ~ onMounted ~ buttonRef.value.$el:', buttonRef.value.$el)
console.log('🚀 ~ onMounted ~ buttonRef.value.ref:', buttonRef.value.ref)
}
setTimeout(() => {
Expand Down
37 changes: 30 additions & 7 deletions src/directives/repeat-click/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof setInterval> | undefined
let delayId: ReturnType<typeof setTimeout> | undefined

/**
* 处理函数,根据绑定值的类型动态调用相应的函数。
*/
const handler = () => (isFunction(value) ? value() : value.handler())

/**
* 清除定时器的方法。
*/
const clear = () => {
if (delayId) {
clearTimeout(delayId)
Expand All @@ -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)
})
},
}
}
58 changes: 54 additions & 4 deletions src/directives/trap-focus/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
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]) {
focusableElement[0].focus()
}
return
}
// 根据Shift键的状态和当前焦点的位置,定向焦点到前一个或后一个可聚焦元素
const goingBackward = e.shiftKey
const isFirst = e.target === focusableElement[0]
const isLast = e.target === focusableElement[focusableElement.length - 1]
Expand All @@ -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') {
Expand All @@ -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
11 changes: 11 additions & 0 deletions src/hooks/useClickOutside.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { onMounted, onUnmounted, type Ref } from 'vue'

/**
* 实现点击外部区域触发回调函数的功能。
*
* @param elementRef 组件内部元素的引用,用于判断点击事件是否发生在该元素内部。
* @param callback 当点击发生在元素外部时调用的回调函数。
*/
const useClickOutside = (
elementRef: Ref<undefined | HTMLElement>,
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)
Expand Down
30 changes: 22 additions & 8 deletions src/hooks/useDeprecated.ts
Original file line number Diff line number Diff line change
@@ -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<boolean>
) => {
// 使用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.
Expand All @@ -29,7 +43,7 @@ For more detail, please visit: ${ref}
}
},
{
immediate: true
immediate: true // 立即执行一次检查,确保在组件初始化时就能发出弃用警告
}
)
}
Loading

0 comments on commit f5265de

Please sign in to comment.