8 changed files with 261 additions and 82 deletions
@ -0,0 +1,19 @@ |
|||||
|
import { ref, unref } from 'vue'; |
||||
|
|
||||
|
export function useLockFn<P extends any[] = any[], V extends any = any>( |
||||
|
fn: (...args: P) => Promise<V> |
||||
|
) { |
||||
|
const lockRef = ref(false); |
||||
|
return async function (...args: P) { |
||||
|
if (unref(lockRef)) return; |
||||
|
lockRef.value = true; |
||||
|
try { |
||||
|
const ret = await fn(...args); |
||||
|
lockRef.value = false; |
||||
|
return ret; |
||||
|
} catch (e) { |
||||
|
lockRef.value = false; |
||||
|
throw e; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
@ -1,47 +0,0 @@ |
|||||
import { toRef, Ref, reactive, customRef, SetupContext, watch, UnwrapRef } from 'vue'; |
|
||||
|
|
||||
export type ModelProps<U> = Readonly< |
|
||||
{ [props: string]: any } & { |
|
||||
modelValue?: U; |
|
||||
} |
|
||||
>; |
|
||||
|
|
||||
export function useModel<T>( |
|
||||
props: ModelProps<T>, |
|
||||
context: SetupContext, |
|
||||
callback?: (val: T | undefined, internalState: { value: UnwrapRef<T | undefined> }) => any |
|
||||
) { |
|
||||
const outerModel: Ref<T | undefined> = toRef(props, 'modelValue'); |
|
||||
const internalState = reactive({ |
|
||||
value: props.modelValue, |
|
||||
}); |
|
||||
|
|
||||
const internalModel = customRef<UnwrapRef<T> | undefined>((track, trigger) => { |
|
||||
return { |
|
||||
get() { |
|
||||
track(); |
|
||||
return internalState.value; |
|
||||
}, |
|
||||
set(newVal) { |
|
||||
if (internalState.value === newVal) return; |
|
||||
internalState.value = newVal; |
|
||||
context.emit('update:modelValue', newVal); |
|
||||
trigger(); |
|
||||
}, |
|
||||
}; |
|
||||
}); |
|
||||
|
|
||||
watch(outerModel, (val, oldVal) => { |
|
||||
if (val === oldVal || val === internalState.value) return; |
|
||||
if (callback) { |
|
||||
callback(val, internalState); |
|
||||
return; |
|
||||
} |
|
||||
internalState.value = val as UnwrapRef<T> | undefined; |
|
||||
}); |
|
||||
|
|
||||
return { |
|
||||
internalState, |
|
||||
internalModel, |
|
||||
}; |
|
||||
} |
|
||||
@ -0,0 +1,58 @@ |
|||||
|
import { isObject } from '@vue/shared'; |
||||
|
import { reactive, Ref, ref, readonly } from 'vue'; |
||||
|
import { isFunction } from '/@/utils/is'; |
||||
|
|
||||
|
type State<T> = ((s: T) => T) | T; |
||||
|
type Dispatch<T> = (t: T) => void; |
||||
|
|
||||
|
type DispatchState<T> = Dispatch<State<T>>; |
||||
|
|
||||
|
type ResultState<T> = Readonly<Ref<T>>; |
||||
|
|
||||
|
export function useState<T extends undefined>( |
||||
|
initialState: (() => T) | T |
||||
|
): [ResultState<T>, DispatchState<T>]; |
||||
|
|
||||
|
export function useState<T extends null>( |
||||
|
initialState: (() => T) | T |
||||
|
): [ResultState<T>, DispatchState<T>]; |
||||
|
|
||||
|
export function useState<T extends boolean>( |
||||
|
initialState: (() => T) | T |
||||
|
): [ResultState<boolean>, DispatchState<boolean>]; |
||||
|
|
||||
|
export function useState<T extends string>( |
||||
|
initialState: (() => T) | T |
||||
|
): [ResultState<string>, DispatchState<string>]; |
||||
|
|
||||
|
export function useState<T extends number>( |
||||
|
initialState: (() => T) | T |
||||
|
): [ResultState<number>, DispatchState<number>]; |
||||
|
|
||||
|
export function useState<T extends object>( |
||||
|
initialState: (() => T) | T |
||||
|
): [Readonly<T>, DispatchState<T>]; |
||||
|
|
||||
|
export function useState<T extends any>( |
||||
|
initialState: (() => T) | T |
||||
|
): [Readonly<T>, DispatchState<T>]; |
||||
|
|
||||
|
export function useState<T>(initialState: (() => T) | T): [ResultState<T> | T, DispatchState<T>] { |
||||
|
if (isFunction(initialState)) { |
||||
|
initialState = (initialState as Fn)(); |
||||
|
} |
||||
|
|
||||
|
if (isObject(initialState)) { |
||||
|
const state = reactive({ data: initialState }) as any; |
||||
|
const setState = (newState: T) => { |
||||
|
state.data = newState; |
||||
|
}; |
||||
|
return [readonly(state), setState]; |
||||
|
} else { |
||||
|
const state = ref(initialState) as any; |
||||
|
const setState = (newState: T) => { |
||||
|
state.value = newState; |
||||
|
}; |
||||
|
return [readonly(state), setState]; |
||||
|
} |
||||
|
} |
||||
@ -1,20 +0,0 @@ |
|||||
import { ref, watch, Ref, SetupContext } from 'vue'; |
|
||||
|
|
||||
export function useToggle(internalModel: Ref<unknown>, { emit }: SetupContext) { |
|
||||
const isActive = ref(!!internalModel.value); |
|
||||
const isToggled = ref(false); |
|
||||
watch(internalModel, (val) => { |
|
||||
isActive.value = !!val; |
|
||||
}); |
|
||||
watch(isActive, (value) => { |
|
||||
!!value !== !!internalModel.value && emit('onUpdate:modelValue', value); |
|
||||
}); |
|
||||
function toggleIt() { |
|
||||
isToggled.value = !isToggled.value; |
|
||||
} |
|
||||
return { |
|
||||
isActive, |
|
||||
toggleIt, |
|
||||
isToggled, |
|
||||
}; |
|
||||
} |
|
||||
@ -0,0 +1,172 @@ |
|||||
|
// https://ahooks.js.org/zh-CN/hooks/dom/use-key-press
|
||||
|
|
||||
|
import type { Ref } from 'vue'; |
||||
|
import { onBeforeUnmount, onMounted, unref } from 'vue'; |
||||
|
import { noop } from '/@/utils'; |
||||
|
import { isFunction, isString, isNumber, isArray } from '/@/utils/is'; |
||||
|
|
||||
|
export type KeyPredicate = (event: KeyboardEvent) => boolean; |
||||
|
export type keyType = KeyboardEvent['keyCode'] | KeyboardEvent['key']; |
||||
|
export type KeyFilter = keyType | keyType[] | ((event: KeyboardEvent) => boolean); |
||||
|
export type EventHandler = (event: KeyboardEvent) => void; |
||||
|
|
||||
|
export type keyEvent = 'keydown' | 'keyup'; |
||||
|
|
||||
|
export type TargetElement = HTMLElement | Element | Document | Window; |
||||
|
export type Target = Ref<TargetElement>; |
||||
|
|
||||
|
export type EventOption = { |
||||
|
events?: keyEvent[]; |
||||
|
target?: Target; |
||||
|
}; |
||||
|
|
||||
|
const defaultEvents: keyEvent[] = ['keydown']; |
||||
|
|
||||
|
// 键盘事件 keyCode 别名
|
||||
|
const aliasKeyCodeMap: Record<string, number | number[]> = { |
||||
|
esc: 27, |
||||
|
tab: 9, |
||||
|
enter: 13, |
||||
|
space: 32, |
||||
|
up: 38, |
||||
|
left: 37, |
||||
|
right: 39, |
||||
|
down: 40, |
||||
|
delete: [8, 46], |
||||
|
}; |
||||
|
|
||||
|
// 键盘事件 key 别名
|
||||
|
const aliasKeyMap: Record<string, string | string[]> = { |
||||
|
esc: 'Escape', |
||||
|
tab: 'Tab', |
||||
|
enter: 'Enter', |
||||
|
space: ' ', |
||||
|
// IE11 uses key names without `Arrow` prefix for arrow keys.
|
||||
|
up: ['Up', 'ArrowUp'], |
||||
|
left: ['Left', 'ArrowLeft'], |
||||
|
right: ['Right', 'ArrowRight'], |
||||
|
down: ['Down', 'ArrowDown'], |
||||
|
delete: ['Backspace', 'Delete'], |
||||
|
}; |
||||
|
|
||||
|
// 修饰键
|
||||
|
const modifierKey: Record<string, (event: KeyboardEvent) => boolean> = { |
||||
|
ctrl: (event: KeyboardEvent) => event.ctrlKey, |
||||
|
shift: (event: KeyboardEvent) => event.shiftKey, |
||||
|
alt: (event: KeyboardEvent) => event.altKey, |
||||
|
meta: (event: KeyboardEvent) => event.metaKey, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* 判断按键是否激活 |
||||
|
* @param [event: KeyboardEvent]键盘事件 |
||||
|
* @param [keyFilter: any] 当前键 |
||||
|
* @returns Boolean |
||||
|
*/ |
||||
|
function genFilterKey(event: any, keyFilter: any) { |
||||
|
// 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空
|
||||
|
if (!event.key) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 数字类型直接匹配事件的 keyCode
|
||||
|
if (isNumber(keyFilter)) { |
||||
|
return event.keyCode === keyFilter; |
||||
|
} |
||||
|
// 字符串依次判断是否有组合键
|
||||
|
const genArr = keyFilter.split('.'); |
||||
|
let genLen = 0; |
||||
|
for (const key of genArr) { |
||||
|
// 组合键
|
||||
|
const genModifier = modifierKey[key]; |
||||
|
// key 别名
|
||||
|
const aliasKey = aliasKeyMap[key]; |
||||
|
// keyCode 别名
|
||||
|
const aliasKeyCode = aliasKeyCodeMap[key]; |
||||
|
/** |
||||
|
* 满足以上规则 |
||||
|
* 1. 自定义组合键别名 |
||||
|
* 2. 自定义 key 别名 |
||||
|
* 3. 自定义 keyCode 别名 |
||||
|
* 4. 匹配 key 或 keyCode |
||||
|
*/ |
||||
|
if ( |
||||
|
(genModifier && genModifier(event)) || |
||||
|
(aliasKey && isArray(aliasKey) ? aliasKey.includes(event.key) : aliasKey === event.key) || |
||||
|
(aliasKeyCode && isArray(aliasKeyCode) |
||||
|
? aliasKeyCode.includes(event.keyCode) |
||||
|
: aliasKeyCode === event.keyCode) || |
||||
|
event.key.toUpperCase() === key.toUpperCase() |
||||
|
) { |
||||
|
genLen++; |
||||
|
} |
||||
|
} |
||||
|
return genLen === genArr.length; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 键盘输入预处理方法 |
||||
|
*/ |
||||
|
function genKeyFormat(keyFilter: any): KeyPredicate { |
||||
|
if (isFunction(keyFilter)) { |
||||
|
return keyFilter; |
||||
|
} |
||||
|
if (isString(keyFilter) || isNumber(keyFilter)) { |
||||
|
return (event: KeyboardEvent) => genFilterKey(event, keyFilter); |
||||
|
} |
||||
|
if (isArray(keyFilter)) { |
||||
|
return (event: KeyboardEvent) => keyFilter.some((item: any) => genFilterKey(event, item)); |
||||
|
} |
||||
|
return keyFilter ? () => true : () => false; |
||||
|
} |
||||
|
|
||||
|
export function useKeyPress( |
||||
|
keyFilter: KeyFilter, |
||||
|
eventHandler: EventHandler = noop, |
||||
|
option: EventOption = {} |
||||
|
) { |
||||
|
const { events = defaultEvents, target } = option; |
||||
|
|
||||
|
let el: TargetElement | null | undefined; |
||||
|
|
||||
|
function handler(event: any) { |
||||
|
const genGuard: KeyPredicate = genKeyFormat(keyFilter); |
||||
|
if (genGuard(event)) { |
||||
|
return eventHandler(event); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMounted(() => { |
||||
|
el = getTargetElement(target, window); |
||||
|
if (!el) return; |
||||
|
|
||||
|
for (const eventName of events) { |
||||
|
el.addEventListener(eventName, handler); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
onBeforeUnmount(() => { |
||||
|
if (!el) return; |
||||
|
for (const eventName of events) { |
||||
|
el.removeEventListener(eventName, handler); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getTargetElement( |
||||
|
target?: Target, |
||||
|
defaultElement?: TargetElement |
||||
|
): TargetElement | undefined | null { |
||||
|
if (!target) { |
||||
|
return defaultElement; |
||||
|
} |
||||
|
|
||||
|
let targetElement: TargetElement | undefined | null; |
||||
|
|
||||
|
if (isFunction(target)) { |
||||
|
targetElement = target(); |
||||
|
} else { |
||||
|
targetElement = unref(target); |
||||
|
} |
||||
|
return targetElement; |
||||
|
} |
||||
Loading…
Reference in new issue