-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
243 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { isClient, isElement } from '@/utils' | ||
|
||
import type { | ||
ComponentPublicInstance, | ||
DirectiveBinding, | ||
ObjectDirective, | ||
} from 'vue' | ||
|
||
type DocumentHandler = <T extends MouseEvent>(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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { default as TrapFocus } from './trap-focus' | ||
export { vRepeatClick } from './repeat-click' | ||
export { default as ClickOutside } from './click-outside' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string[]> | ||
} | ||
|
||
const DEFAULT_EXCLUDE_KEYS = ['class', 'style'] | ||
const LISTENER_PREFIX = /^on[A-Z]/ | ||
|
||
export const useAttrs = (params: Params = {}): ComputedRef<Record<string, unknown>> => { | ||
const { excludeListeners = false, excludeKeys } = params | ||
const allExcludeKeys = computed<string[]>(() => { | ||
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)) | ||
) | ||
) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { componentSizeMap } from '@/constants' | ||
|
||
import type { ComponentSize } from '@/constants' | ||
|
||
export const getComponentSize = (size?: ComponentSize) => { | ||
return componentSizeMap[size || 'default'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
import type { AppContext, Plugin } from 'vue' | ||
import type { AppContext, EmitsOptions, Plugin, SetupContext } from 'vue' | ||
|
||
export type SFCWithInstall<T> = T & Plugin | ||
|
||
export type SFCInstallWithContext<T> = SFCWithInstall<T> & { | ||
_context: AppContext | null | ||
} | ||
|
||
export type EmitFn<E extends EmitsOptions> = SetupContext<E>['emit'] |