Compare commits

...

10 Commits

Author SHA1 Message Date
Samuel Newman c60181f731 use floating-ui/react to make an accessible autocomplete 2026-02-19 15:27:56 +02:00
Samuel Newman d8026315bc add floating-ui/react, pin floating-ui deps 2026-02-19 14:36:05 +02:00
Samuel Newman f0b3248422 don't defer onSubmit 2026-02-19 14:32:54 +02:00
Samuel Newman 55915322a4 fix icon import 2026-02-19 14:29:32 +02:00
Samuel Newman 5c730d7800 simplify style 2026-02-19 14:25:44 +02:00
Samuel Newman 137e7e7848 rm forwardRef 2026-02-19 14:24:12 +02:00
Samuel Newman d16ca5083f fix type error, extract and plat-split a component 2026-02-19 14:23:50 +02:00
Samuel Newman a2f1db3027 let's just ALF the hell out of it 2026-02-19 14:23:08 +02:00
Samuel Newman 9eb2f88651 defer and ALF sidebar search 2026-02-19 14:22:37 +02:00
Samuel Newman f80479744e defer search screen 2026-02-19 14:22:37 +02:00
8 changed files with 460 additions and 262 deletions
+6 -3
View File
@@ -83,8 +83,9 @@
"@emoji-mart/react": "^1.1.1",
"@expo/html-elements": "^0.12.5",
"@expo/webpack-config": "^19.0.1",
"@floating-ui/dom": "^1.6.3",
"@floating-ui/react-dom": "^2.0.8",
"@floating-ui/dom": "^1.7.5",
"@floating-ui/react": "^0.27.18",
"@floating-ui/react-dom": "^2.1.7",
"@formatjs/intl-displaynames": "^6.8.13",
"@formatjs/intl-locale": "^4.2.13",
"@formatjs/intl-numberformat": "^8.15.6",
@@ -294,7 +295,9 @@
"**/expo-device": "7.1.4",
"**/multiformats": "9.9.0",
"unicode-segmenter": "0.14.5",
"@types/estree": "1.0.6"
"@types/estree": "1.0.6",
"@floating-ui/dom": "1.7.5",
"@floating-ui/react-dom": "2.1.7"
},
"jest": {
"preset": "jest-expo/ios",
+79 -62
View File
@@ -1,5 +1,9 @@
import React from 'react'
import {type TextInput, View} from 'react-native'
import {
type NativeSyntheticEvent,
type TextInput,
type TextInputKeyPressEventData,
View,
} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -12,72 +16,85 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {IS_NATIVE} from '#/env'
type SearchInputProps = Omit<TextField.InputProps, 'label'> & {
ref?: React.Ref<TextInput>
label?: TextField.InputProps['label']
/**
* Called when the user presses the (X) button
*/
onClearText?: () => void
/**
* Called when the user presses the Escape key
*/
onEscape?: () => void
}
export const SearchInput = React.forwardRef<TextInput, SearchInputProps>(
function SearchInput({value, label, onClearText, ...rest}, ref) {
const t = useTheme()
const {_} = useLingui()
const showClear = value && value.length > 0
export function SearchInput({
ref,
value,
label,
onClearText,
onEscape,
...rest
}: SearchInputProps) {
const t = useTheme()
const {_} = useLingui()
const showClear = value && value.length > 0
return (
<View style={[a.w_full, a.relative]}>
<TextField.Root>
<TextField.Icon icon={MagnifyingGlassIcon} />
<TextField.Input
inputRef={ref}
label={label || _(msg`Search`)}
value={value}
placeholder={_(msg`Search`)}
returnKeyType="search"
keyboardAppearance={t.scheme}
selectTextOnFocus={IS_NATIVE}
autoFocus={false}
accessibilityRole="search"
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
style={[
showClear
? {
paddingRight: 24,
}
: {},
]}
{...rest}
/>
</TextField.Root>
const onKeyPress = (
evt: NativeSyntheticEvent<TextInputKeyPressEventData>,
) => {
if (evt.nativeEvent.key === 'Escape') {
onEscape?.()
}
}
{showClear && (
<View
style={[
a.absolute,
a.z_20,
a.my_auto,
a.inset_0,
a.justify_center,
a.pr_sm,
{left: 'auto'},
]}>
<Button
testID="searchTextInputClearBtn"
onPress={onClearText}
label={_(msg`Clear search query`)}
hitSlop={HITSLOP_10}
size="tiny"
shape="round"
variant="ghost"
color="secondary">
<ButtonIcon icon={X} size="xs" />
</Button>
</View>
)}
</View>
)
},
)
return (
<View style={[a.w_full, a.relative]}>
<TextField.Root>
<TextField.Icon icon={MagnifyingGlassIcon} />
<TextField.Input
inputRef={ref}
label={label || _(msg`Search`)}
value={value}
placeholder={_(msg`Search`)}
returnKeyType="search"
keyboardAppearance={t.scheme}
selectTextOnFocus={IS_NATIVE}
autoFocus={false}
accessibilityRole="search"
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
onKeyPress={onKeyPress}
style={[!!showClear && a.pr_2xl]}
{...rest}
/>
</TextField.Root>
{showClear && (
<View
style={[
a.absolute,
a.z_20,
a.my_auto,
a.inset_0,
a.justify_center,
a.pr_sm,
{left: 'auto'},
]}>
<Button
testID="searchTextInputClearBtn"
onPress={onClearText}
label={_(msg`Clear search query`)}
hitSlop={HITSLOP_10}
size="tiny"
shape="round"
variant="ghost"
color="secondary">
<ButtonIcon icon={X} size="xs" />
</Button>
</View>
)}
</View>
)
}
+7 -6
View File
@@ -1,6 +1,7 @@
import {
memo,
useCallback,
useDeferredValue,
useLayoutEffect,
useMemo,
useRef,
@@ -17,8 +18,7 @@ import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {HITSLOP_20} from '#/lib/constants'
import {HITSLOP_10} from '#/lib/constants'
import {HITSLOP_10, HITSLOP_20} from '#/lib/constants'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {MagnifyingGlassIcon} from '#/lib/icons'
import {type NavigationProp} from '#/lib/routes/types'
@@ -75,9 +75,10 @@ export function SearchScreenShell({
const queryClient = useQueryClient()
// Query terms
const [searchText, setSearchText] = useState<string>(queryParam)
const [searchText, setSearchText] = useState(queryParam)
const deferredSearchText = useDeferredValue(searchText)
const {data: autocompleteData, isFetching: isAutocompleteFetching} =
useActorAutocompleteQuery(searchText, true)
useActorAutocompleteQuery(deferredSearchText, true)
const [showAutocomplete, setShowAutocomplete] = useState(false)
@@ -397,11 +398,11 @@ export function SearchScreenShell({
display: showAutocomplete && !fixedParams ? 'flex' : 'none',
flex: 1,
}}>
{searchText.length > 0 ? (
{deferredSearchText.length > 0 ? (
<AutocompleteResults
isAutocompleteFetching={isAutocompleteFetching}
autocompleteData={autocompleteData}
searchText={searchText}
searchText={deferredSearchText}
onSubmit={onSubmit}
onResultPress={onAutocompleteResultPress}
onProfileClick={handleProfileClick}
@@ -5,11 +5,10 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {SearchLinkCard} from '#/view/shell/desktop/Search'
import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard'
import {atoms as a, native} from '#/alf'
import {atoms as a} from '#/alf'
import * as Layout from '#/components/Layout'
import {IS_NATIVE} from '#/env'
import {SearchLinkCard} from './SearchLinkCard'
let AutocompleteResults = ({
isAutocompleteFetching,
@@ -43,12 +42,8 @@ let AutocompleteResults = ({
keyboardDismissMode="on-drag">
<SearchLinkCard
label={_(msg`Search for "${searchText}"`)}
onPress={native(onSubmit)}
to={
IS_NATIVE
? undefined
: `/search?q=${encodeURIComponent(searchText)}`
}
onPress={onSubmit}
to={{screen: 'Search', params: {q: searchText}}}
style={a.border_b}
/>
{autocompleteData?.map(item => (
@@ -0,0 +1,42 @@
import {type StyleProp, View, type ViewStyle} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
export function SearchLinkCard({
label,
onPress,
style,
}: {
label: string
/**
* @platform web
*/
to: LinkProps['to']
/**
* @platform native
*/
onPress: () => void
style?: StyleProp<ViewStyle>
}) {
const t = useTheme()
return (
<Button label={label} onPress={onPress}>
{({focused, hovered, pressed}) => (
<View
style={[
a.w_full,
t.atoms.border_contrast_low,
a.p_lg,
(focused || hovered || pressed) && t.atoms.bg_contrast_25,
style,
]}>
<Text style={[a.text_sm, a.leading_snug]}>{label}</Text>
</View>
)}
</Button>
)
}
@@ -0,0 +1,42 @@
import {type StyleProp, View, type ViewStyle} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {Link, type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
export function SearchLinkCard({
label,
to,
style,
}: {
label: string
/**
* @platform web
*/
to: LinkProps['to']
/**
* @platform native
*/
onPress: () => void
style?: StyleProp<ViewStyle>
}) {
const t = useTheme()
return (
<Link to={to} label={label}>
{({focused, hovered, pressed}) => (
<View
style={[
a.w_full,
t.atoms.border_contrast_low,
a.py_lg,
a.px_md,
(focused || hovered || pressed) && t.atoms.bg_contrast_25,
style,
]}>
<Text style={[a.text_sm, a.leading_snug]}>{label}</Text>
</View>
)}
</Link>
)
}
+248 -137
View File
@@ -1,167 +1,278 @@
import React from 'react'
import {useDeferredValue, useRef, useState} from 'react'
import {type Role, type TextInput, View} from 'react-native'
import {
ActivityIndicator,
StyleSheet,
TouchableOpacity,
View,
type ViewStyle,
} from 'react-native'
import {msg} from '@lingui/macro'
useDismiss,
useFloating,
useId,
useInteractions,
useListNavigation,
useRole,
} from '@floating-ui/react'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {StackActions, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {usePalette} from '#/lib/hooks/usePalette'
import {type NavigationProp} from '#/lib/routes/types'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {Link} from '#/view/com/util/Link'
import {Text} from '#/view/com/util/text/Text'
import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard'
import {atoms as a} from '#/alf'
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
import {atoms as a, flatten, useTheme} from '#/alf'
import {SearchInput} from '#/components/forms/SearchInput'
let SearchLinkCard = ({
label,
to,
onPress,
style,
}: {
label: string
to?: string
onPress?: () => void
style?: ViewStyle
}): React.ReactNode => {
const pal = usePalette('default')
const inner = (
<View
style={[pal.border, {paddingVertical: 16, paddingHorizontal: 12}, style]}>
<Text type="md" style={[pal.text]}>
{label}
</Text>
</View>
)
if (onPress) {
return (
<TouchableOpacity
onPress={onPress}
accessibilityLabel={label}
accessibilityHint="">
{inner}
</TouchableOpacity>
)
}
return (
<Link href={to} asAnchor anchorNoUnderline>
<View
style={[
pal.border,
{paddingVertical: 16, paddingHorizontal: 12},
style,
]}>
<Text type="md" style={[pal.text]}>
{label}
</Text>
</View>
</Link>
)
}
SearchLinkCard = React.memo(SearchLinkCard)
export {SearchLinkCard}
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
export function DesktopSearch() {
const {_} = useLingui()
const pal = usePalette('default')
const t = useTheme()
const navigation = useNavigation<NavigationProp>()
const [isActive, setIsActive] = React.useState<boolean>(false)
const [query, setQuery] = React.useState<string>('')
const qc = useQueryClient()
const searchInputRef = useRef<TextInput>(null)
const [open, setOpen] = useState(false)
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const [activeIndex, setActiveIndex] = useState<number | null>(null)
const listRef = useRef<Array<HTMLElement | null>>([])
const {data: autocompleteData, isFetching} = useActorAutocompleteQuery(
query,
deferredQuery,
true,
)
const moderationOpts = useModerationOpts()
const profiles = autocompleteData ?? []
const hasSearchLink = deferredQuery.length > 0
const onChangeText = React.useCallback((text: string) => {
// Floating UI setup — used for interaction hooks (ARIA combobox pattern),
// not for positioning (we keep the existing CSS absolute layout).
const {refs, context} = useFloating({
open,
onOpenChange(nextOpen, _event, reason) {
setOpen(nextOpen)
if (!nextOpen && reason === 'escape-key') {
setQuery('')
searchInputRef.current?.blur()
}
},
})
const role = useRole(context, {role: 'listbox'})
const dismiss = useDismiss(context)
const listNav = useListNavigation(context, {
listRef,
activeIndex,
onNavigate: setActiveIndex,
virtual: true,
loop: true,
})
const {getReferenceProps, getFloatingProps, getItemProps} = useInteractions([
role,
dismiss,
listNav,
])
const listboxId = useId()
const navigateToSearch = () => {
if (!deferredQuery.length) return
navigation.dispatch(StackActions.push('Search', {q: deferredQuery}))
setQuery('')
setOpen(false)
searchInputRef.current?.blur()
}
const navigateToProfile = (profileIndex: number) => {
const profile = profiles[profileIndex]
if (!profile) return
unstableCacheProfileView(qc, profile)
navigation.dispatch(StackActions.push('Profile', {name: profile.did}))
setQuery('')
setOpen(false)
searchInputRef.current?.blur()
}
const selectItem = (index: number) => {
if (hasSearchLink && index === 0) {
navigateToSearch()
} else {
navigateToProfile(hasSearchLink ? index - 1 : index)
}
}
const onChangeText = (text: string) => {
setQuery(text)
setIsActive(text.length > 0)
}, [])
if (!open) setOpen(true)
setActiveIndex(text.length > 0 ? 0 : null)
}
const onPressCancelSearch = React.useCallback(() => {
const onPressCancelSearch = () => {
setQuery('')
setIsActive(false)
}, [setQuery])
setOpen(false)
}
const onSubmit = React.useCallback(() => {
setIsActive(false)
if (!query.length) return
navigation.dispatch(StackActions.push('Search', {q: query}))
}, [query, navigation])
// getReferenceProps produces the merged keyboard + ARIA props.
// We must use onKeyDownCapture because RNW's TextInput internally calls
// stopPropagation() on all keydown events, preventing normal bubbling.
// Capture phase (parent→child) fires before the target's handler.
const referenceProps = getReferenceProps({
onKeyDown(e: React.KeyboardEvent) {
if (e.key === 'Enter') {
e.preventDefault()
if (activeIndex != null) {
selectItem(activeIndex)
} else {
navigateToSearch()
}
}
},
})
const {onKeyDown: refOnKeyDown, ...refAriaProps} = referenceProps
const onSearchProfileCardPress = React.useCallback(() => {
setQuery('')
setIsActive(false)
}, [])
// Extract role/id from floating props for the listbox View
const floatingProps = getFloatingProps()
return (
<View style={[styles.container, pal.view]}>
<SearchInput
value={query}
onChangeText={onChangeText}
onClearText={onPressCancelSearch}
onSubmitEditing={onSubmit}
/>
{query !== '' && isActive && moderationOpts && (
<View
style={[
pal.view,
pal.borderDark,
styles.resultsContainer,
a.overflow_hidden,
]}>
{isFetching && !autocompleteData?.length ? (
<View style={{padding: 8}}>
<ActivityIndicator />
</View>
) : (
<>
<SearchLinkCard
label={_(msg`Search for "${query}"`)}
to={`/search?q=${encodeURIComponent(query)}`}
style={
(autocompleteData?.length ?? 0) > 0
? {borderBottomWidth: 1}
: undefined
}
/>
{autocompleteData?.map(item => (
<SearchProfileCard
key={item.did}
profile={item}
moderationOpts={moderationOpts}
onPress={onSearchProfileCardPress}
/>
))}
</>
)}
<View style={[a.w_full, a.z_10]}>
{/* Wrapper div receives floating-ui reference + ARIA props.
onKeyDownCapture is needed because RNW's TextInput stops keydown
propagation — capture phase fires before that happens. */}
<div
ref={refs.setReference}
onKeyDownCapture={
refOnKeyDown as React.KeyboardEventHandler<HTMLDivElement>
}
{...(refAriaProps as React.HTMLAttributes<HTMLDivElement>)}
style={{width: '100%'}}>
<SearchInput
ref={searchInputRef}
value={query}
onChangeText={onChangeText}
onClearText={onPressCancelSearch}
onFocus={() => setOpen(true)}
onBlur={(e: any) => {
const relatedTarget = (e as React.FocusEvent)
.relatedTarget as Node | null
if (
relatedTarget &&
refs.floating.current?.contains(relatedTarget)
) {
return
}
setOpen(false)
}}
/>
</div>
{open && moderationOpts && (
<View style={[a.w_full]}>
<View
ref={refs.setFloating}
role={floatingProps.role as Role}
id={floatingProps.id as string}
style={[
t.atoms.bg,
t.atoms.border_contrast_low,
a.w_full,
a.border,
a.mt_sm,
a.rounded_sm,
a.overflow_hidden,
a.absolute,
a.shadow_lg,
a.zoom_fade_in,
]}>
{deferredQuery.length === 0 ? (
<View style={[a.py_xl, a.gap_sm, a.align_center]}>
<SearchIcon size="2xl" style={[t.atoms.text_contrast_low]} />
<Text
style={[a.text_sm, t.atoms.text_contrast_low, a.text_center]}>
<Trans>Start typing to search</Trans>
</Text>
</View>
) : (
<>
{/* Search link option */}
<div
ref={node => {
listRef.current[0] = node
}}
id={`${listboxId}-option-0`}
role="option"
aria-selected={activeIndex === 0}
style={flatten([
a.w_full,
a.py_lg,
a.px_md,
a.pointer,
(profiles.length > 0 || isFetching) && a.border_b,
t.atoms.border_contrast_low,
activeIndex === 0 && t.atoms.bg_contrast_25,
])}
{...getItemProps({
onClick() {
navigateToSearch()
},
})}>
<Text style={[a.text_sm, a.leading_snug]}>
{_(msg`Search for "${deferredQuery}"`)}
</Text>
</div>
{/* Loading state */}
{isFetching && !profiles.length ? (
<View style={[a.p_xl, a.align_center]}>
<Loader size="md" />
</View>
) : (
profiles.map((profile, i) => {
const itemIndex = 1 + i
return (
<div
key={profile.did}
ref={node => {
listRef.current[itemIndex] = node
}}
id={`${listboxId}-option-${itemIndex}`}
role="option"
aria-selected={activeIndex === itemIndex}
aria-label={_(msg`View ${profile.handle}'s profile`)}
style={flatten([
a.flex,
a.flex_col,
{paddingLeft: 6, paddingRight: 6},
a.px_sm,
a.pointer,
activeIndex === itemIndex && t.atoms.bg_contrast_25,
])}
{...getItemProps({
onClick() {
navigateToProfile(i)
},
})}>
<ProfileCard.Outer>
<ProfileCard.Header>
<ProfileCard.Avatar
profile={profile}
moderationOpts={moderationOpts}
/>
<ProfileCard.NameAndHandle
profile={profile}
moderationOpts={moderationOpts}
/>
</ProfileCard.Header>
</ProfileCard.Outer>
</div>
)
})
)}
</>
)}
</View>
</View>
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
position: 'relative',
width: '100%',
},
resultsContainer: {
marginTop: 10,
flexDirection: 'column',
width: '100%',
borderWidth: 1,
borderRadius: 6,
},
})
+32 -45
View File
@@ -4419,59 +4419,41 @@
dependencies:
dequal "^2.0.3"
"@floating-ui/core@^1.0.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
"@floating-ui/core@^1.7.4":
version "1.7.4"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4"
integrity sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==
dependencies:
"@floating-ui/utils" "^0.2.1"
"@floating-ui/utils" "^0.2.10"
"@floating-ui/core@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17"
integrity sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==
"@floating-ui/dom@1.7.5", "@floating-ui/dom@^1.7.5":
version "1.7.5"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.5.tgz#60bfc83a4d1275b2a90db76bf42ca2a5f2c231c2"
integrity sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==
dependencies:
"@floating-ui/utils" "^0.1.1"
"@floating-ui/core" "^1.7.4"
"@floating-ui/utils" "^0.2.10"
"@floating-ui/dom@^1.3.0":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.1.tgz#88b70defd002fe851f17b4a25efb2d3c04d7a8d7"
integrity sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==
"@floating-ui/react-dom@2.1.7", "@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.7.tgz#529475cc16ee4976ba3387968117e773d9aa703e"
integrity sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==
dependencies:
"@floating-ui/core" "^1.4.1"
"@floating-ui/utils" "^0.1.1"
"@floating-ui/dom" "^1.7.5"
"@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.6.3":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef"
integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==
"@floating-ui/react@^0.27.18":
version "0.27.18"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.18.tgz#a664a45a867ed5e2999a858b5236a53cf60cf412"
integrity sha512-xJWJxvmy3a05j643gQt+pRbht5XnTlGpsEsAPnMi5F5YTOEEJymA90uZKBD8OvIv5XvZ1qi4GcccSlqT3Bq44Q==
dependencies:
"@floating-ui/core" "^1.0.0"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/react-dom" "^2.1.7"
"@floating-ui/utils" "^0.2.10"
tabbable "^6.0.0"
"@floating-ui/react-dom@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.1.tgz#7972a4fc488a8c746cded3cfe603b6057c308a91"
integrity sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==
dependencies:
"@floating-ui/dom" "^1.3.0"
"@floating-ui/react-dom@^2.0.8":
version "2.0.8"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d"
integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==
dependencies:
"@floating-ui/dom" "^1.6.1"
"@floating-ui/utils@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.1.tgz#1a5b1959a528e374e8037c4396c3e825d6cf4a83"
integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==
"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
"@floating-ui/utils@^0.2.10":
version "0.2.10"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c"
integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==
"@formatjs/ecma402-abstract@2.3.6":
version "2.3.6"
@@ -19051,6 +19033,11 @@ symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
tabbable@^6.0.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581"
integrity sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==
tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"