mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
* refactor(components): popper composables - Refactor popper composables * updates * updates for tooltip * Updates for popper. TODO: fix controlled tooltip animation * Fix controlled mode popper animation issue * Add new feature for customizing tooltip theme * Fix popover and popconfirm error * - Add Collection component for wrapping a collection of component - Add FocusTrap component for trap focus for popups - Add RovingFocus component for roving focus component type - Adjust dropdown component based on these newly added components - Add popper-trigger component for placing the trigger - TODO: Finish current dropdown component, and all component's tests plus documents * Refactor popper * Complete organizing popper * Almost finish dropdown * Update popper tests * update only-child test * Finish focus trap component test * Finish tooltip content test * Finish tooltip trigger tests * Finish tooltip tests * finish tests for Collection and RovingFocusGroup * Fix test cases for timeselect & select & popover * Fix popover, popconfirm, menu bug and test cases * Fix select-v2 test error caused by updating popper * Fix date-picker test issue for updating popper * fix test cases * Fix eslint * Rebase dev & fix tests * Remove unused code
135 lines
3.2 KiB
TypeScript
135 lines
3.2 KiB
TypeScript
export type FocusLayer = {
|
|
paused: boolean
|
|
pause: () => void
|
|
resume: () => void
|
|
}
|
|
|
|
export type FocusStack = FocusLayer[]
|
|
|
|
export const obtainAllFocusableElements = (
|
|
element: HTMLElement
|
|
): HTMLElement[] => {
|
|
const nodes: HTMLElement[] = []
|
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, {
|
|
acceptNode: (
|
|
node: Element & {
|
|
disabled: boolean
|
|
hidden: boolean
|
|
type: string
|
|
tabIndex: number
|
|
}
|
|
) => {
|
|
const isHiddenInput = node.tagName === 'INPUT' && node.type === 'hidden'
|
|
if (node.disabled || node.hidden || isHiddenInput)
|
|
return NodeFilter.FILTER_SKIP
|
|
return node.tabIndex >= 0
|
|
? NodeFilter.FILTER_ACCEPT
|
|
: NodeFilter.FILTER_SKIP
|
|
},
|
|
})
|
|
while (walker.nextNode()) nodes.push(walker.currentNode as HTMLElement)
|
|
|
|
return nodes
|
|
}
|
|
|
|
export const getVisibleElement = (
|
|
elements: HTMLElement[],
|
|
container: HTMLElement
|
|
) => {
|
|
for (const element of elements) {
|
|
if (!isHidden(element, container)) return element
|
|
}
|
|
}
|
|
|
|
export const isHidden = (element: HTMLElement, container: HTMLElement) => {
|
|
if (process.env.NODE_ENV === 'test') return false
|
|
if (getComputedStyle(element).visibility === 'hidden') return true
|
|
|
|
while (element) {
|
|
if (container && element === container) return false
|
|
if (getComputedStyle(element).display === 'none') return true
|
|
element = element.parentElement as HTMLElement
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
export const getEdges = (container: HTMLElement) => {
|
|
const focusable = obtainAllFocusableElements(container)
|
|
const first = getVisibleElement(focusable, container)
|
|
const last = getVisibleElement(focusable.reverse(), container)
|
|
return [first, last]
|
|
}
|
|
|
|
const isSelectable = (
|
|
element: any
|
|
): element is HTMLInputElement & { select: () => void } => {
|
|
return element instanceof HTMLInputElement && 'select' in element
|
|
}
|
|
|
|
export const tryFocus = (
|
|
element?: HTMLElement | { focus: () => void } | null,
|
|
shouldSelect?: boolean
|
|
) => {
|
|
if (element && element.focus) {
|
|
const prevFocusedElement = document.activeElement
|
|
element.focus({ preventScroll: true })
|
|
if (
|
|
element !== prevFocusedElement &&
|
|
isSelectable(element) &&
|
|
shouldSelect
|
|
) {
|
|
element.select()
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeFromStack<T>(list: T[], item: T) {
|
|
const copy = [...list]
|
|
|
|
const idx = list.indexOf(item)
|
|
|
|
if (idx !== -1) {
|
|
copy.splice(idx, 1)
|
|
}
|
|
return copy
|
|
}
|
|
|
|
const createFocusableStack = () => {
|
|
let stack = [] as FocusStack
|
|
|
|
const push = (layer: FocusLayer) => {
|
|
const currentLayer = stack[0]
|
|
|
|
if (currentLayer && layer !== currentLayer) {
|
|
currentLayer.pause()
|
|
}
|
|
|
|
stack = removeFromStack(stack, layer)
|
|
stack.unshift(layer)
|
|
}
|
|
|
|
const remove = (layer: FocusLayer) => {
|
|
stack = removeFromStack(stack, layer)
|
|
stack[0]?.resume?.()
|
|
}
|
|
|
|
return {
|
|
push,
|
|
remove,
|
|
}
|
|
}
|
|
|
|
export const focusFirstDescendant = (
|
|
elements: HTMLElement[],
|
|
shouldSelect = false
|
|
) => {
|
|
const prevFocusedElement = document.activeElement
|
|
for (const element of elements) {
|
|
tryFocus(element, shouldSelect)
|
|
if (document.activeElement !== prevFocusedElement) return
|
|
}
|
|
}
|
|
|
|
export const focusableStack = createFocusableStack()
|