@popperjs/core#VirtualElement TypeScript Examples
The following examples show how to use
@popperjs/core#VirtualElement.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useMenu.ts From kodiak-ui with MIT License | 4 votes |
export function useMenu({
placement = 'bottom-start',
offset = [0, 8],
}: UseMenuProps = {}): UseMenuReturnValue {
const buttonRef = React.useRef<HTMLButtonElement | null>(null)
const menuRef = React.useRef<HTMLUListElement>()
const itemsRef = React.useRef<{ [key: string]: HTMLLIElement | Element }>({})
const itemHandlersRef = React.useRef<{
[key: string]: ((event: any) => void) | undefined
}>({})
const elementIds = React.useRef<ElementIds>(generateElementIds())
const popperInstanceRef = React.useRef<any | null>(null)
const [activeItem, setActiveItem] = React.useState('')
const {
isOpen: isExpanded,
handleOpenPortal,
handleClosePortal,
Portal,
} = usePortal()
useIsomorphicLayoutEffect(
function initializePopper() {
if (!isExpanded && (!buttonRef.current || !menuRef.current)) {
return
}
const popperInstance = createPopper(
buttonRef.current as Element | VirtualElement,
menuRef.current as HTMLElement,
{
placement,
modifiers: [{ name: 'offset', options: { offset } }],
},
)
popperInstanceRef.current = popperInstance
return () => {
popperInstance.destroy()
popperInstanceRef.current = null
}
},
[isExpanded, placement, offset],
)
function getItemNodeFromIndex(index: number): HTMLLIElement | Element | null {
return itemsRef && itemsRef.current && itemsRef.current[index]
}
function focusMenuRef() {
menuRef && menuRef.current && menuRef.current.focus()
}
function focusButtonRef() {
buttonRef && buttonRef.current && buttonRef.current.focus()
}
useKey({
key: 'Escape',
target: menuRef.current,
handler: () => {
if (isExpanded) {
handleClosePortal({})
}
focusButtonRef()
},
})
useKey({
key: 'ArrowDown',
target: menuRef.current,
handler: () => {
const nextItem = getNextItem({
moveAmount: 1,
baseIndex: Object.keys(itemsRef.current).indexOf(activeItem),
items: itemsRef.current,
getItemNodeFromIndex,
})
setActiveItem(nextItem)
setAttributes(itemsRef.current[nextItem], {
'aria-selected': 'true',
})
},
})
useKey({
key: 'ArrowUp',
target: menuRef.current,
handler: () => {
const nextItem = getNextItem({
moveAmount: -1,
baseIndex: Object.keys(itemsRef.current).indexOf(activeItem),
items: itemsRef.current,
getItemNodeFromIndex,
})
setActiveItem(nextItem)
setAttributes(itemsRef.current[nextItem], {
'aria-selected': 'true',
})
},
})
useKey({
key: 'Enter',
target: menuRef.current,
handler: () => {
const handler = itemHandlersRef.current[activeItem] as () => void
handler?.()
},
})
useOnClickOutside({
ref: menuRef as React.MutableRefObject<Element>,
refException: buttonRef as React.MutableRefObject<Element>,
handler: () => {
handleClosePortal({})
},
})
React.useEffect(() => {
if (isExpanded) {
focusMenuRef()
}
}, [isExpanded])
function registerButtonElement({
ref,
options,
}: RefAndOptions<Element>): RefAndOptions<Element> {
if (ref && ref.tagName === MenuElementTagNames.Button) {
buttonRef.current = ref as HTMLButtonElement
setAttributes(buttonRef.current, {
id: elementIds.current.buttonId,
'aria-haspopup': 'true',
'aria-controls': 'IDREF',
})
}
return { ref, options }
}
function registerMenuElement({
ref,
options,
}: RefAndOptions<Element>): RefAndOptions<Element> {
if (ref && ref.tagName === MenuElementTagNames.Ul) {
menuRef.current = ref as HTMLUListElement
setAttributes(menuRef.current, {
id: elementIds.current.menuId,
role: 'menu',
tabIndex: '-1',
'aria-labelledby':
buttonRef && buttonRef.current ? `${buttonRef.current.id}` : '',
})
}
return { ref, options }
}
function registerMenuItemElement({
ref,
options,
}: RefAndOptions<Element>): RefAndOptions<Element> {
if (ref && ref.tagName === MenuElementTagNames.Li) {
const name = options && options.name
const handler = options && options.handler
const items = itemsRef.current
const itemHandlers = itemHandlersRef.current
items[name as string] = ref
itemHandlers[name as string] = handler
const item = items[name as string]
setAttributes(item, {
id: elementIds.current.getItemId(name as string),
role: 'option',
'aria-selected': 'false',
})
}
return { ref, options }
}
const registerElementRefs = React.useCallback(function registerElementRefs(
ref: Element | null,
options?: RegisterOptions,
): RefAndOptions<Element> {
return registerMenuItemElement(
registerMenuElement(registerButtonElement({ ref, options })),
)
},
[])
/**
* Register the menu elements
*
* Allows the ability to add the appropriate HTML attributes
* to an HTML element.
*/
const register = React.useCallback(
function register(
ref: (HTMLButtonElement | HTMLUListElement | HTMLLIElement) | null,
options?: RegisterOptions,
): RefAndOptions<Element> | null {
return ref && registerElementRefs(ref, options)
},
[registerElementRefs],
)
const handleToggleMenu = React.useCallback(
function handleToggleMenu(event) {
setAttributes(buttonRef && (buttonRef.current as Element | null), {
'aria-expanded': `${!isExpanded}`,
})
isExpanded ? handleClosePortal(event) : handleOpenPortal(event)
},
[isExpanded, handleOpenPortal, handleClosePortal],
)
const handleCloseMenu = React.useCallback(
function handleCloseMenu() {
setAttributes(buttonRef && (buttonRef.current as Element | null), {
'aria-expanded': 'false',
})
handleClosePortal({})
},
[handleClosePortal],
)
function getItemProps(name: string) {
return {
onClick: (event: React.MouseEvent<any, MouseEvent>) => {
// TS doesn't like this itemHandlersRef.current[name] && itemHandlersRef.current[name](event)
// this will work when the bundler supports conditional chaining itemHandlersRef.current[name]?.(event)
const itemHandler = itemHandlersRef.current[name]
itemHandler && itemHandler(event)
},
onMouseEnter: () => setActiveItem(name),
}
}
return {
register,
isExpanded,
activeItem,
handleToggleMenu,
handleCloseMenu,
getItemProps,
Menu: Portal,
}
}
Example #2
Source File: useTooltip.ts From kodiak-ui with MIT License | 4 votes |
export function useTooltip({
placement = 'top',
offset = [0, 10],
closeTimeout = 0,
}: UseTooltipProps = {}): UseTooltipReturn {
const triggerRef = React.useRef<HTMLElement | null>(null)
const tooltipRef = React.useRef<HTMLElement | null>(null)
const arrowRef = React.useRef<HTMLElement | null>(null)
const popperInstanceRef = React.useRef<any>(null)
const timeoutIdRef = React.useRef<any>(null)
const id = useId()
const {
isOpen: isVisible,
handleOpenPortal,
handleClosePortal: instantlyClosePortal,
Portal,
portalRef,
} = usePortal()
const delayedClosePortal = React.useCallback(
function delayedClosePortal(event) {
clearTimeout(timeoutIdRef.current)
if (closeTimeout === 0) {
instantlyClosePortal(event)
} else {
timeoutIdRef.current = setTimeout(() => {
return instantlyClosePortal(event)
}, closeTimeout)
}
},
[instantlyClosePortal, closeTimeout],
)
React.useLayoutEffect(
function initializePopper() {
if (!isVisible && (!triggerRef.current || !tooltipRef.current)) {
return
}
const popperInstance = createPopper(
triggerRef.current as Element | VirtualElement,
tooltipRef.current as HTMLElement,
{
placement,
modifiers: [
offset ? { name: 'offset', options: { offset } } : {},
arrowRef && arrowRef.current
? {
name: 'arrow',
options: { element: arrowRef && arrowRef.current },
}
: {},
],
},
)
popperInstanceRef.current = popperInstance
return () => {
popperInstance.destroy()
popperInstanceRef.current = null
}
},
[isVisible, offset, placement],
)
useOnClickOutside({
ref: portalRef as React.MutableRefObject<Element>,
refException: triggerRef as React.MutableRefObject<Element>,
handler: () => {
instantlyClosePortal({})
},
})
useKey({
key: 'Escape',
target: triggerRef.current,
handler: () => {
if (isVisible) {
instantlyClosePortal({})
}
},
})
function registerTriggerElement({
ref,
options,
}: RefAndOptions): RefAndOptions {
if (ref && options && options.trigger) {
triggerRef.current = ref
setAttributes(triggerRef.current, {
'aria-describedby': `kodiak-ui-tooltip-${id}`,
})
}
return { ref, options }
}
function registerTooltipElement({
ref,
options,
}: RefAndOptions): RefAndOptions {
if ((ref && !options) || (options && !options.trigger && !options.arrow)) {
tooltipRef.current = ref
setAttributes(tooltipRef.current, {
id: `kodiak-ui-tooltip-${id}`,
role: 'tooltip',
})
}
return { ref, options }
}
function registerArrowElement({
ref,
options,
}: RefAndOptions): RefAndOptions {
if (ref && options && options.arrow) {
arrowRef.current = ref
}
return { ref, options }
}
function register(
ref: TooltipRef,
options?: RegisterOptions,
): {
ref: TooltipRef
options?: RegisterOptions
} {
return registerArrowElement(
registerTriggerElement(registerTooltipElement({ ref, options })),
)
}
const getTriggerProps = React.useCallback(
function getTriggerProps() {
return {
onFocus: handleOpenPortal,
onBlur: delayedClosePortal,
onMouseEnter: handleOpenPortal,
onMouseLeave: delayedClosePortal,
}
},
[handleOpenPortal, delayedClosePortal],
)
return {
isVisible,
register,
getTriggerProps,
Portal,
}
}