@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
1 lines • 20.4 kB
Source Map (JSON)
{"version":3,"file":"useMenuVirtualList.mjs","names":[],"sources":["../../../src/base-ui/virtual/useMenuVirtualList.tsx"],"sourcesContent":["'use client';\n\nimport type React from 'react';\nimport {\n Children,\n cloneElement,\n isValidElement,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { VirtualizerHandle } from 'virtua';\n\nimport type { VirtualListProps } from './type';\nimport { usePointerScrollGuard } from './VirtualScrollArea';\n\nconst LIST_ITEM_SELECTOR = '[role^=\"menuitem\"], [role=\"option\"]';\nconst INDEX_ATTRIBUTE = 'data-virtual-index';\nconst MAX_JUMP_FRAMES = 60;\n\ninterface VirtualChildProps {\n 'aria-disabled'?: boolean | 'true' | 'false';\n 'aria-label'?: string;\n 'children'?: React.ReactNode;\n 'disabled'?: boolean;\n 'label'?: string;\n 'onFocus'?: (event: React.FocusEvent<HTMLElement>) => void;\n}\n\nexport interface ListEntry {\n disabled: boolean;\n label: string;\n}\n\nexport interface UseMenuVirtualListParams extends Pick<\n VirtualListProps,\n 'getItemLabel' | 'keepMounted'\n> {\n children: React.ReactNode;\n enabled: boolean | undefined;\n /**\n * Idle time after which the typeahead query resets; matches Base UI's per-component value.\n * @default 500\n */\n typeaheadResetMs?: number;\n}\n\nexport const flattenText = (node: React.ReactNode): string => {\n if (node == null || typeof node === 'boolean') return '';\n if (typeof node === 'string' || typeof node === 'number') return String(node);\n if (Array.isArray(node)) return node.map(flattenText).join('');\n if (isValidElement<{ children?: React.ReactNode }>(node)) return flattenText(node.props.children);\n return '';\n};\n\nconst readEntry = (\n child: React.ReactNode,\n index: number,\n getItemLabel: UseMenuVirtualListParams['getItemLabel'],\n): ListEntry => {\n if (!isValidElement<VirtualChildProps>(child)) return { disabled: true, label: '' };\n const props = child.props;\n const label =\n getItemLabel?.(child, index) ??\n props.label ??\n props['aria-label'] ??\n flattenText(props.children);\n const disabled =\n Boolean(props.disabled) || props['aria-disabled'] === true || props['aria-disabled'] === 'true';\n return { disabled, label: label.trim() };\n};\n\nexport const findMatch = (entries: ListEntry[], query: string, startIndex: number) => {\n if (entries.length === 0) return -1;\n const lowerQuery = query.toLowerCase();\n const start = ((startIndex % entries.length) + entries.length) % entries.length;\n for (let offset = 0; offset < entries.length; offset += 1) {\n const index = (start + offset) % entries.length;\n const entry = entries[index];\n if (!entry.disabled && entry.label.toLowerCase().startsWith(lowerQuery)) return index;\n }\n return -1;\n};\n\nexport const findEdge = (entries: ListEntry[], fromEnd: boolean) => {\n if (fromEnd) {\n for (let index = entries.length - 1; index >= 0; index -= 1) {\n if (!entries[index].disabled) return index;\n }\n return -1;\n }\n return entries.findIndex((entry) => !entry.disabled);\n};\n\nexport function useMenuVirtualList({\n children,\n enabled,\n getItemLabel,\n keepMounted,\n typeaheadResetMs = 500,\n}: UseMenuVirtualListParams) {\n const viewportRef = useRef<HTMLDivElement | null>(null);\n const virtualizerRef = useRef<VirtualizerHandle | null>(null);\n const focusedItemRef = useRef<HTMLElement | null>(null);\n const activeIndexRef = useRef<number | null>(null);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n // A Home/End/typeahead target stays mounted until focus actually lands on it, so the\n // focusin resync (which re-sets the active index to the still-focused old row) can't unmount\n // it mid-scroll on a large jump.\n const jumpTargetRef = useRef<number | null>(null);\n const [jumpTarget, setJumpTarget] = useState<number | null>(null);\n const { pointerScrollRef, scrollGuardProps } = usePointerScrollGuard();\n\n const entries = useMemo(\n () =>\n enabled\n ? (Children.map(children, (child, index) => readEntry(child, index, getItemLabel)) ?? [])\n : [],\n [children, enabled, getItemLabel],\n );\n\n // Base UI scrolls the focused item into view right after focusing it; while the pointer is\n // scrolling that would yank the list back, so the item's scrollIntoView checks the guard.\n const guardScrollIntoView = useCallback(\n (item: HTMLElement) => {\n if (Object.hasOwn(item, 'scrollIntoView')) return;\n item.scrollIntoView = (...args) => {\n if (!pointerScrollRef.current) HTMLElement.prototype.scrollIntoView.call(item, ...args);\n };\n },\n [pointerScrollRef],\n );\n\n // Keeps focus inside the popup on the viewport, so a row unmounting under the old focus never\n // drops focus to the body mid-jump — Base UI Menu closes on focus-out.\n const parkFocus = useCallback(() => {\n const viewport = viewportRef.current;\n if (viewport && !viewport.contains(document.activeElement)) {\n viewport.focus({ preventScroll: true });\n }\n }, []);\n\n const settleScrollRef = useRef(-1);\n // Focus the jump target only once the scroll has settled on it (its row is mounted and the\n // offset stopped changing), so we never focus a row that's about to unmount and orphan focus.\n const focusJumpTarget = useCallback(() => {\n const index = jumpTargetRef.current;\n const viewport = viewportRef.current;\n if (index === null || !viewport) return false;\n parkFocus();\n const scrollTop = viewport.scrollTop;\n const settled = scrollTop === settleScrollRef.current;\n settleScrollRef.current = scrollTop;\n if (!settled) return false;\n const child = viewport.querySelector<HTMLElement>(`[${INDEX_ATTRIBUTE}=\"${index}\"]`);\n const target =\n child &&\n (child.matches(LIST_ITEM_SELECTOR)\n ? child\n : child.querySelector<HTMLElement>(LIST_ITEM_SELECTOR));\n if (!target) return false;\n guardScrollIntoView(target);\n focusedItemRef.current = target;\n target.focus({ preventScroll: true });\n return true;\n }, [guardScrollIntoView, parkFocus]);\n\n // The virtualizer shifts its window in response to keyboard nav and jumps, so react to every\n // DOM change: finish a pending jump the moment its target mounts, otherwise keep Base UI's\n // highlight index in step by re-firing focusin on the still-focused row (or, if it was\n // unmounted, park focus on the viewport so the popup keydown handlers stay reachable).\n useEffect(() => {\n const viewport = viewportRef.current;\n if (!enabled || !viewport) return;\n const observer = new MutationObserver(() => {\n if (jumpTargetRef.current !== null) {\n focusJumpTarget();\n return;\n }\n const focusedItem = focusedItemRef.current;\n const active = document.activeElement;\n if (focusedItem && !focusedItem.isConnected && active === document.body) {\n focusedItemRef.current = null;\n viewport.focus({ preventScroll: true });\n return;\n }\n if (focusedItem && active === focusedItem) {\n focusedItem.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));\n }\n });\n observer.observe(viewport, { childList: true, subtree: true });\n return () => observer.disconnect();\n }, [enabled, focusJumpTarget]);\n\n const jumpFrameRef = useRef<number | null>(null);\n useEffect(() => {\n return () => {\n if (jumpFrameRef.current !== null) cancelAnimationFrame(jumpFrameRef.current);\n };\n }, []);\n\n const jumpTo = useCallback(\n (index: number) => {\n activeIndexRef.current = index;\n jumpTargetRef.current = index;\n setActiveIndex(index);\n setJumpTarget(index);\n // Park focus on the viewport (inside the popup) before scrolling, so the row being\n // unmounted underneath the old focus never drops focus to the body — which would trip\n // Base UI Menu's focus-out close mid-jump.\n viewportRef.current?.focus({ preventScroll: true });\n settleScrollRef.current = -1;\n virtualizerRef.current?.scrollToIndex(index, { align: 'nearest' });\n // The MutationObserver finishes the jump on mount; kick it in case the target is already\n // mounted (a short jump) and produces no mutation.\n if (jumpFrameRef.current !== null) cancelAnimationFrame(jumpFrameRef.current);\n let frames = 0;\n const kick = () => {\n jumpFrameRef.current = null;\n focusJumpTarget();\n frames += 1;\n // Loop until the real focus event lands (which clears `jumpTargetRef` in onFocus), so a\n // mid-scroll remount that orphans focus is recovered on the next frame; cap so a target\n // that never mounts can't spin forever.\n if (jumpTargetRef.current !== null && frames < MAX_JUMP_FRAMES) {\n jumpFrameRef.current = requestAnimationFrame(kick);\n } else if (jumpTargetRef.current !== null) {\n jumpTargetRef.current = null;\n setJumpTarget(null);\n }\n };\n jumpFrameRef.current = requestAnimationFrame(kick);\n },\n [focusJumpTarget],\n );\n\n const queryRef = useRef('');\n const lastMatchRef = useRef<number | null>(null);\n const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const resetQuery = useCallback(() => {\n queryRef.current = '';\n if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);\n resetTimeoutRef.current = null;\n }, []);\n\n useEffect(() => {\n return () => {\n if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);\n };\n }, []);\n\n // Runs in the capture phase so Base UI's own Home/End and typeahead, which only see the\n // mounted rows, never get the event. Mirrors `useTypeahead`: printable keys accumulate for\n // `typeaheadResetMs`, a lone space activates while a space mid-query keeps typing, rapid\n // repeats of one letter cycle through matches, and the search starts after the focused row\n // and wraps.\n const handleKeyDownCapture = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (!enabled || entries.length === 0) return;\n if (event.target instanceof HTMLElement && event.target.matches('input, textarea')) return;\n\n if (event.key === 'Home' || event.key === 'End') {\n const fromEnd = event.key === 'End';\n const index = findEdge(entries, fromEnd);\n if (index === -1) return;\n event.preventDefault();\n event.stopPropagation();\n resetQuery();\n jumpTo(index);\n return;\n }\n\n if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey) return;\n if (event.key === ' ' && queryRef.current === '') return;\n event.preventDefault();\n event.stopPropagation();\n\n const isNewSession = queryRef.current === '';\n const allowRapidRepeat = entries.every(\n (entry) =>\n entry.disabled || entry.label[0]?.toLowerCase() !== entry.label[1]?.toLowerCase(),\n );\n if (allowRapidRepeat && queryRef.current === event.key) {\n queryRef.current = '';\n }\n queryRef.current += event.key;\n if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);\n resetTimeoutRef.current = setTimeout(resetQuery, typeaheadResetMs);\n\n const previous =\n isNewSession || queryRef.current.length === 1\n ? (activeIndexRef.current ?? lastMatchRef.current ?? -1)\n : (lastMatchRef.current ?? activeIndexRef.current ?? -1);\n const startIndex = queryRef.current.length === 1 ? previous + 1 : previous;\n const index = findMatch(entries, queryRef.current, startIndex);\n if (index !== -1) {\n lastMatchRef.current = index;\n jumpTo(index);\n } else if (event.key !== ' ') {\n resetQuery();\n }\n },\n [enabled, entries, jumpTo, resetQuery, typeaheadResetMs],\n );\n\n const virtualChildren = useMemo(() => {\n if (!enabled) return children;\n return Children.map(children, (child, index) => {\n if (!isValidElement<VirtualChildProps>(child)) return child;\n const { onFocus } = child.props;\n const extraProps: Record<string, unknown> = {\n [INDEX_ATTRIBUTE]: index,\n onFocus: (event: React.FocusEvent<HTMLElement>) => {\n onFocus?.(event);\n const item = (event.target as HTMLElement).closest<HTMLElement>(LIST_ITEM_SELECTOR);\n if (item && event.currentTarget.contains(item)) {\n focusedItemRef.current = item;\n guardScrollIntoView(item);\n }\n activeIndexRef.current = index;\n setActiveIndex(index);\n if (jumpTargetRef.current === index) {\n jumpTargetRef.current = null;\n setJumpTarget(null);\n }\n },\n };\n return cloneElement(child, extraProps);\n });\n }, [children, enabled, guardScrollIntoView]);\n\n const keepMountedIndices = useMemo(() => {\n if (!enabled) return undefined;\n const count = Children.count(children);\n const indices = new Set(keepMounted);\n if (activeIndex !== null) indices.add(activeIndex);\n if (jumpTarget !== null) indices.add(jumpTarget);\n const inRange = [...indices].filter((index) => index >= 0 && index < count);\n return inRange.length ? inRange : undefined;\n }, [activeIndex, children, enabled, jumpTarget, keepMounted]);\n\n // While a jump is in flight, a row unmounting under focus fires blur → focus to the body,\n // which trips Base UI Menu's focus-out close. Catch it and pull focus back to the viewport\n // (still inside the popup) synchronously before the close can run.\n const handleBlur = useCallback((event: React.FocusEvent<HTMLElement>) => {\n if (jumpTargetRef.current === null) return;\n const viewport = viewportRef.current;\n if (!viewport) return;\n if (event.relatedTarget && viewport.contains(event.relatedTarget)) return;\n viewport.focus({ preventScroll: true });\n }, []);\n\n const viewportProps = useMemo(\n () => ({ ...scrollGuardProps, onBlur: handleBlur, onKeyDownCapture: handleKeyDownCapture }),\n [handleBlur, handleKeyDownCapture, scrollGuardProps],\n );\n\n return { keepMountedIndices, viewportProps, viewportRef, virtualChildren, virtualizerRef };\n}\n"],"mappings":"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"}