refactor: auto completion component
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
e93831e650
commit
75cfd554ca
|
|
@ -15,6 +15,7 @@
|
|||
"@follow/electron-main": "workspace:*",
|
||||
"@follow/shared": "workspace:*",
|
||||
"@fontsource/sn-pro": "5.1.0",
|
||||
"@headlessui/react": "2.1.8",
|
||||
"@hono/auth-js": "1.0.10",
|
||||
"@hookform/resolvers": "3.9.0",
|
||||
"@iconify/tools": "4.0.6",
|
||||
|
|
|
|||
|
|
@ -1,24 +1,11 @@
|
|||
import clsx from "clsx"
|
||||
import { AnimatePresence } from "framer-motion"
|
||||
import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react"
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import Fuse from "fuse.js"
|
||||
import { merge, throttle } from "lodash-es"
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import { useEventCallback } from "usehooks-ts"
|
||||
import { forwardRef, Fragment, useCallback, useEffect, useState } from "react"
|
||||
|
||||
import { useRefValue } from "~/hooks/common"
|
||||
import { nextFrame, stopPropagation } from "~/lib/dom"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
import { Input } from "../input"
|
||||
import { RootPortal } from "../portal"
|
||||
|
||||
export type Suggestion = {
|
||||
name: string
|
||||
|
|
@ -28,277 +15,110 @@ export interface AutocompleteProps extends React.InputHTMLAttributes<HTMLInputEl
|
|||
suggestions: Suggestion[]
|
||||
renderSuggestion?: (suggestion: Suggestion) => any
|
||||
|
||||
onSuggestionSelected: (suggestion: Suggestion) => void
|
||||
onConfirm?: (value: string) => void
|
||||
onEndReached?: () => void
|
||||
onSuggestionSelected: (suggestion: NoInfer<Suggestion> | null) => void
|
||||
|
||||
portal?: boolean
|
||||
|
||||
// classnames
|
||||
|
||||
wrapperClassName?: string
|
||||
|
||||
maxHeight?: number
|
||||
}
|
||||
|
||||
const defaultRenderSuggestion = (suggestion: any) => suggestion.name
|
||||
const defaultRenderSuggestion = (suggestion: any) => suggestion?.name
|
||||
export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
|
||||
(
|
||||
{
|
||||
suggestions,
|
||||
renderSuggestion = defaultRenderSuggestion,
|
||||
onSuggestionSelected,
|
||||
onConfirm,
|
||||
onEndReached,
|
||||
onChange,
|
||||
portal,
|
||||
wrapperClassName,
|
||||
maxHeight,
|
||||
portal,
|
||||
value,
|
||||
|
||||
defaultValue,
|
||||
...inputProps
|
||||
},
|
||||
forwardedRef,
|
||||
) => {
|
||||
const [filterableSuggestions, setFilterableSuggestions] = useState(suggestions)
|
||||
const [inputValue, setInputValue] = useState(inputProps.value || inputProps.defaultValue || "")
|
||||
const [selectedOptions, setSelectedOptions] = useState<NoInfer<Suggestion> | null>(
|
||||
() => suggestions.find((suggestion) => suggestion.value === value) || null,
|
||||
)
|
||||
|
||||
const doFilter = useEventCallback(() => {
|
||||
const [filterableSuggestions, setFilterableSuggestions] = useState(suggestions)
|
||||
|
||||
const doFilter = useCallback(() => {
|
||||
const fuse = new Fuse(suggestions, {
|
||||
keys: ["name", "value"],
|
||||
})
|
||||
const trimInputValue = (inputValue as string).trim()
|
||||
|
||||
const trimInputValue = (value as string)?.trim()
|
||||
|
||||
if (!trimInputValue) return setFilterableSuggestions(suggestions)
|
||||
|
||||
const results = fuse.search(trimInputValue)
|
||||
setFilterableSuggestions(results.map((result) => result.item))
|
||||
})
|
||||
}, [suggestions, value])
|
||||
useEffect(() => {
|
||||
doFilter()
|
||||
}, [inputValue, suggestions])
|
||||
|
||||
useEffect(() => {
|
||||
const $input = inputRef.current
|
||||
if (!$input) return
|
||||
if (document.activeElement !== $input) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsOpen(filterableSuggestions.length > 0)
|
||||
}, [filterableSuggestions])
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const [listRef, setListRef] = useState<HTMLElement | null>(null)
|
||||
const onBlur = useEventCallback((e: any) => {
|
||||
inputProps.onBlur?.(e)
|
||||
if (listRef?.contains(e.relatedTarget)) {
|
||||
return
|
||||
}
|
||||
setIsOpen(false)
|
||||
})
|
||||
|
||||
const handleInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> = useEventCallback(
|
||||
(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
onConfirm?.((e.target as HTMLInputElement).value)
|
||||
setIsOpen(false)
|
||||
}
|
||||
inputProps.onKeyDown?.(e)
|
||||
},
|
||||
)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
useImperativeHandle(forwardedRef, () => inputRef.current!)
|
||||
|
||||
const [currentActiveIndex, setCurrentActiveIndex] = useState(0)
|
||||
const currentActiveIndexRef = useRefValue(currentActiveIndex)
|
||||
const filterableSuggestionsRef = useRefValue(filterableSuggestions)
|
||||
// Bind hotkey
|
||||
useEffect(() => {
|
||||
const $input = inputRef.current
|
||||
if (!$input) return
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const currentActiveIndex = currentActiveIndexRef.current
|
||||
const filterableSuggestionsLength = filterableSuggestionsRef.current.length
|
||||
const filterableSuggestions = filterableSuggestionsRef.current
|
||||
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.preventDefault()
|
||||
const nextIndex =
|
||||
currentActiveIndex + (e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0)
|
||||
setCurrentActiveIndex(Math.max(0, Math.min(nextIndex, filterableSuggestionsLength - 1)))
|
||||
} else if (
|
||||
(e.key === "Enter" || e.key === "Tab") &&
|
||||
currentActiveIndex >= 0 &&
|
||||
currentActiveIndex < filterableSuggestionsLength
|
||||
) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
onSuggestionSelected(filterableSuggestions[currentActiveIndex])
|
||||
setInputValue(filterableSuggestions[currentActiveIndex].name)
|
||||
|
||||
nextFrame(() => {
|
||||
setIsOpen(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$input.addEventListener("keydown", handleKeyDown)
|
||||
return () => {
|
||||
$input.removeEventListener("keydown", handleKeyDown)
|
||||
}
|
||||
}, [currentActiveIndexRef, filterableSuggestionsRef, onSuggestionSelected])
|
||||
|
||||
const [inputRect, setInputRect] = useState<DOMRect | null>(null)
|
||||
const [dropdownPos, setDropdownPos] = useState("down")
|
||||
const [dropdownHeight, setDropdownHeight] = useState(maxHeight || 200)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const $input = inputRef.current
|
||||
if (!$input) return
|
||||
|
||||
const handler = () => {
|
||||
const rect = $input.getBoundingClientRect()
|
||||
|
||||
setInputRect(rect)
|
||||
|
||||
const { top, height } = rect
|
||||
|
||||
if (top + height + dropdownHeight > window.innerHeight) {
|
||||
setDropdownPos("up")
|
||||
} else {
|
||||
setDropdownPos("down")
|
||||
}
|
||||
}
|
||||
handler()
|
||||
|
||||
const resizeObserver = new ResizeObserver(handler)
|
||||
resizeObserver.observe($input)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [dropdownHeight])
|
||||
|
||||
const handleScroll = useEventCallback(
|
||||
throttle(() => {
|
||||
if (!listRef) return
|
||||
const { scrollHeight, scrollTop, clientHeight } = listRef!
|
||||
// gap 50px
|
||||
if (scrollHeight - scrollTop - clientHeight < 50) {
|
||||
onEndReached?.()
|
||||
}
|
||||
}, 30),
|
||||
)
|
||||
|
||||
const handleChange = useEventCallback((e: any) => {
|
||||
setInputValue(e.target.value)
|
||||
onChange?.(e)
|
||||
})
|
||||
|
||||
const ListElement = (
|
||||
<div
|
||||
className={clsx(
|
||||
"pointer-events-auto z-[999] mt-1 overflow-hidden",
|
||||
portal ? "absolute flex flex-col" : "absolute w-full",
|
||||
)}
|
||||
ref={useCallback((el) => {
|
||||
setListRef(el)
|
||||
|
||||
const height = el?.getBoundingClientRect().height
|
||||
if (height) {
|
||||
setDropdownHeight(height)
|
||||
}
|
||||
}, [])}
|
||||
style={merge(
|
||||
{},
|
||||
portal && inputRect
|
||||
? {
|
||||
width: `${inputRect.width}px`,
|
||||
left: `${inputRect.x}px`,
|
||||
top: `${inputRect.y + inputRect.height}px`,
|
||||
bottom: dropdownPos === "up" ? 0 : undefined,
|
||||
transform: dropdownPos === "up" ? `translateY(-${dropdownHeight}px)` : undefined,
|
||||
maxHeight,
|
||||
}
|
||||
: {},
|
||||
)}
|
||||
>
|
||||
<ul
|
||||
data-state={isOpen ? "open" : "closed"}
|
||||
className={clsx(
|
||||
"pointer-events-auto max-h-48 grow",
|
||||
"overflow-auto rounded-md border border-border bg-popover text-popover-foreground",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
||||
)}
|
||||
// FIXME: https://github.com/radix-ui/primitives/issues/2125
|
||||
onWheel={stopPropagation}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{filterableSuggestions.map((suggestion, index) => {
|
||||
const handleClick = () => {
|
||||
onSuggestionSelected(suggestion)
|
||||
setIsOpen(false)
|
||||
|
||||
setInputValue(suggestion.name)
|
||||
}
|
||||
return (
|
||||
<li
|
||||
className={cn(
|
||||
"cursor-default px-4 py-1.5 text-sm",
|
||||
|
||||
currentActiveIndex === index && "bg-theme-item-hover dark:bg-neutral-800",
|
||||
)}
|
||||
key={suggestion.value}
|
||||
onMouseDown={handleClick}
|
||||
onMouseEnter={() => setCurrentActiveIndex(index)}
|
||||
>
|
||||
{renderSuggestion(suggestion)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
const handleFocus: React.FocusEventHandler<HTMLInputElement> = useEventCallback((e) => {
|
||||
setIsOpen(true)
|
||||
inputProps.onFocus?.(e)
|
||||
})
|
||||
}, [doFilter])
|
||||
|
||||
return (
|
||||
<div className={cn("pointer-events-auto relative", wrapperClassName)}>
|
||||
<div className="relative">
|
||||
<Input
|
||||
value={inputValue}
|
||||
ref={inputRef}
|
||||
className="pr-8"
|
||||
{...inputProps}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
{!!inputValue && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setInputValue("")
|
||||
onChange?.({ target: { value: "" } } as any)
|
||||
}}
|
||||
type="button"
|
||||
className="center absolute inset-y-0 right-0 flex px-2 opacity-80 duration-200 hover:opacity-100"
|
||||
>
|
||||
<i className="i-mingcute-close-circle-fill" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{isOpen &&
|
||||
filterableSuggestions.length > 0 &&
|
||||
(portal ? <RootPortal>{ListElement}</RootPortal> : ListElement)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<Combobox
|
||||
immediate
|
||||
value={selectedOptions}
|
||||
onChange={(suggestion) => {
|
||||
setSelectedOptions(suggestion)
|
||||
onSuggestionSelected(suggestion)
|
||||
}}
|
||||
>
|
||||
{({ open }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<ComboboxInput
|
||||
ref={forwardedRef}
|
||||
as={Input}
|
||||
aria-label="Select Category"
|
||||
displayValue={renderSuggestion}
|
||||
value={value}
|
||||
{...inputProps}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<ComboboxOptions
|
||||
portal={portal}
|
||||
static
|
||||
as={m.div}
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.98 }}
|
||||
anchor="bottom"
|
||||
className={cn(
|
||||
"pointer-events-auto z-[99] max-h-48 grow",
|
||||
"shadow-perfect overflow-auto rounded-md border border-border bg-popover text-popover-foreground",
|
||||
"w-[var(--input-width)] empty:invisible",
|
||||
)}
|
||||
>
|
||||
<div style={{ maxHeight }}>
|
||||
{filterableSuggestions.map((suggestion) => (
|
||||
<ComboboxOption
|
||||
key={suggestion.value}
|
||||
value={suggestion}
|
||||
className={cn(
|
||||
"data-[focus]:bg-theme-item-hover dark:data-[focus]:bg-neutral-800",
|
||||
"px-4 py-1.5 text-sm",
|
||||
)}
|
||||
>
|
||||
{suggestion.name}
|
||||
</ComboboxOption>
|
||||
))}
|
||||
</div>
|
||||
</ComboboxOptions>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
</Combobox>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,9 +49,10 @@ export const useInputComposition = <E = HTMLInputElement>(
|
|||
onKeyDown?.(e)
|
||||
|
||||
if (e.key === "Escape") {
|
||||
e.currentTarget.blur()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
e.currentTarget.blur()
|
||||
}
|
||||
},
|
||||
[onKeyDown],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useEffect, useRef } from "react"
|
||||
import { useEffect, useMemo, useRef } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -25,8 +25,7 @@ import { LoadingCircle } from "~/components/ui/loading"
|
|||
import { useCurrentModal } from "~/components/ui/modal"
|
||||
import { Switch } from "~/components/ui/switch"
|
||||
import { views } from "~/constants"
|
||||
import { useDeleteSubscription } from "~/hooks/biz/useSubscriptionActions"
|
||||
import { useAuthQuery } from "~/hooks/common"
|
||||
import { useAuthQuery, useI18n } from "~/hooks/common"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { tipcClient } from "~/lib/client"
|
||||
import { FeedViewType } from "~/lib/enum"
|
||||
|
|
@ -58,6 +57,7 @@ export const FeedForm: Component<{
|
|||
onSuccess?: () => void
|
||||
}> = ({ id: _id, defaultValues = defaultValue, url, asWidget, onSuccess }) => {
|
||||
const queryParams = { id: _id, url }
|
||||
|
||||
const feedQuery = useFeed(queryParams)
|
||||
|
||||
const id = feedQuery.data?.feed.id || _id
|
||||
|
|
@ -171,7 +171,7 @@ const FeedInnerForm = ({
|
|||
defaultValues,
|
||||
})
|
||||
|
||||
const { setClickOutSideToDismiss } = useCurrentModal()
|
||||
const { setClickOutSideToDismiss, dismiss } = useCurrentModal()
|
||||
|
||||
useEffect(() => {
|
||||
setClickOutSideToDismiss(!form.formState.isDirty)
|
||||
|
|
@ -233,27 +233,22 @@ const FeedInnerForm = ({
|
|||
},
|
||||
})
|
||||
|
||||
const deleteSubscription = useDeleteSubscription({
|
||||
onSuccess: () => {
|
||||
if (!asWidget && !isSubscribed) {
|
||||
window.close()
|
||||
}
|
||||
|
||||
onSuccess?.()
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
followMutation.mutate(values)
|
||||
}
|
||||
|
||||
const { t } = useTranslation()
|
||||
const t = useI18n()
|
||||
|
||||
const categories = useAuthQuery(subscriptionQuery.categories())
|
||||
|
||||
// useEffect(() => {
|
||||
// if (feed.isSuccess) nextFrame(() => buttonRef.current?.focus());
|
||||
// }, [feed.isSuccess]);
|
||||
const suggestions = useMemo(
|
||||
() =>
|
||||
categories.data?.map((i) => ({
|
||||
name: i,
|
||||
value: i,
|
||||
})) || [],
|
||||
[categories.data],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col gap-y-4">
|
||||
|
|
@ -333,15 +328,12 @@ const FeedInnerForm = ({
|
|||
<Autocomplete
|
||||
maxHeight={window.innerHeight < 600 ? 120 : 240}
|
||||
portal
|
||||
suggestions={
|
||||
categories.data?.map((i) => ({
|
||||
name: i,
|
||||
value: i,
|
||||
})) || []
|
||||
}
|
||||
suggestions={suggestions}
|
||||
{...(field as any)}
|
||||
onSuggestionSelected={(suggestion) => {
|
||||
field.onChange(suggestion.value)
|
||||
if (suggestion) {
|
||||
field.onChange(suggestion.value)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -378,16 +370,11 @@ const FeedInnerForm = ({
|
|||
type="button"
|
||||
ref={buttonRef}
|
||||
variant="text"
|
||||
isLoading={deleteSubscription.isPending}
|
||||
className="text-red-500"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
if (subscription) {
|
||||
deleteSubscription.mutate(subscription)
|
||||
}
|
||||
onClick={() => {
|
||||
dismiss()
|
||||
}}
|
||||
>
|
||||
{t("feed_form.unfollow")}
|
||||
{t.common("cancel")}
|
||||
</Button>
|
||||
)}
|
||||
<Button ref={buttonRef} type="submit" isLoading={followMutation.isPending}>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
"1001": "创建会话失败",
|
||||
"1002": "参数无效",
|
||||
"1003": "邀请码无效",
|
||||
"2000": "只有管理员可以刷新动态",
|
||||
"2000": "只有管理员可以刷新 Feed",
|
||||
"2001": "未找到动态",
|
||||
"2002": "需要 feedId 或 url",
|
||||
"2003": "动态获取错误",
|
||||
"2004": "动态解析失败",
|
||||
"2003": "Feed 获取错误",
|
||||
"2004": "Feed 解析失败",
|
||||
"2010": "所有权验证失败",
|
||||
"2011": "订阅限制已超出",
|
||||
"3000": "未找到条目",
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"4000": "已领取",
|
||||
"4001": "用户钱包错误",
|
||||
"4002": "余额不足",
|
||||
"4003": "动态可提余额不足",
|
||||
"4003": "可提余额不足",
|
||||
"4004": "目标用户钱包错误",
|
||||
"5000": "邀请限制已超出。请几天后再试。",
|
||||
"5001": "邀请已存在。",
|
||||
|
|
|
|||
150
pnpm-lock.yaml
150
pnpm-lock.yaml
|
|
@ -381,6 +381,9 @@ importers:
|
|||
'@fontsource/sn-pro':
|
||||
specifier: 5.1.0
|
||||
version: 5.1.0
|
||||
'@headlessui/react':
|
||||
specifier: 2.1.8
|
||||
version: 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@hono/auth-js':
|
||||
specifier: 1.0.10
|
||||
version: 1.0.10(@auth/core@0.34.2)(hono@4.6.2(patch_hash=iej2g43ojhll5opwbnvyorjq4e))(react@18.3.1)
|
||||
|
|
@ -2428,15 +2431,37 @@ packages:
|
|||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/react-dom@2.1.2':
|
||||
resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/react@0.26.24':
|
||||
resolution: {integrity: sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.7':
|
||||
resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==}
|
||||
|
||||
'@floating-ui/utils@0.2.8':
|
||||
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
|
||||
|
||||
'@fontsource/sn-pro@5.1.0':
|
||||
resolution: {integrity: sha512-k7cdU1hftD/pyrnrmQg+egKAssbuPORbtgQM/9JG5b76EchEKZdl/juO8xBjN3O/H5BQ16iu8/9vNPgzyExZeg==}
|
||||
|
||||
'@gar/promisify@1.1.3':
|
||||
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
|
||||
|
||||
'@headlessui/react@2.1.8':
|
||||
resolution: {integrity: sha512-uajqVkAcVG/wHwG9Fh5PFMcFpf2VxM4vNRNKxRjuK009kePVur8LkuuygHfIE+2uZ7z7GnlTtYsyUe6glPpTLg==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: ^18
|
||||
react-dom: ^18
|
||||
|
||||
'@hono/auth-js@1.0.10':
|
||||
resolution: {integrity: sha512-hnkZzPMHveX2GzX/zS53uP6OqzB6SCtJCaimXdjewCWWiNi4FFtv1jcsca3PV7haiaLKict5JPZWzfMq+t3Bzw==}
|
||||
engines: {node: '>=18.4.0'}
|
||||
|
|
@ -3501,6 +3526,37 @@ packages:
|
|||
'@radix-ui/rect@1.1.0':
|
||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||
|
||||
'@react-aria/focus@3.18.2':
|
||||
resolution: {integrity: sha512-Jc/IY+StjA3uqN73o6txKQ527RFU7gnG5crEl5Xy3V+gbYp2O5L3ezAo/E0Ipi2cyMbG6T5Iit1IDs7hcGu8aw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/interactions@3.22.2':
|
||||
resolution: {integrity: sha512-xE/77fRVSlqHp2sfkrMeNLrqf2amF/RyuAS6T5oDJemRSgYM3UoxTbWjucPhfnoW7r32pFPHHgz4lbdX8xqD/g==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/ssr@3.9.5':
|
||||
resolution: {integrity: sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==}
|
||||
engines: {node: '>= 12'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/utils@3.25.2':
|
||||
resolution: {integrity: sha512-GdIvG8GBJJZygB4L2QJP1Gabyn2mjFsha73I2wSe+o4DYeGWoJiMZRM06PyTIxLH4S7Sn7eVDtsSBfkc2VY/NA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-stately/utils@3.10.3':
|
||||
resolution: {integrity: sha512-moClv7MlVSHpbYtQIkm0Cx+on8Pgt1XqtPx6fy9rQFb2DNc9u1G3AUVnqA17buOkH1vLxAtX4MedlxMWyRCYYA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-types/shared@3.24.1':
|
||||
resolution: {integrity: sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@remix-run/router@1.19.2':
|
||||
resolution: {integrity: sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
@ -3767,6 +3823,9 @@ packages:
|
|||
peerDependencies:
|
||||
eslint: '>=8.40.0'
|
||||
|
||||
'@swc/helpers@0.5.13':
|
||||
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
|
||||
|
||||
'@szmarczak/http-timer@4.0.6':
|
||||
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -3819,6 +3878,15 @@ packages:
|
|||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
|
||||
'@tanstack/react-virtual@3.10.8':
|
||||
resolution: {integrity: sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
'@tanstack/virtual-core@3.10.8':
|
||||
resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==}
|
||||
|
||||
'@tootallnate/once@2.0.0':
|
||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
|
@ -8465,6 +8533,9 @@ packages:
|
|||
systemjs@6.15.1:
|
||||
resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==}
|
||||
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
tailwind-merge@2.5.2:
|
||||
resolution: {integrity: sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==}
|
||||
|
||||
|
|
@ -10747,12 +10818,37 @@ snapshots:
|
|||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.6.10
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@floating-ui/react@0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@floating-ui/utils': 0.2.8
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
tabbable: 6.2.0
|
||||
|
||||
'@floating-ui/utils@0.2.7': {}
|
||||
|
||||
'@floating-ui/utils@0.2.8': {}
|
||||
|
||||
'@fontsource/sn-pro@5.1.0': {}
|
||||
|
||||
'@gar/promisify@1.1.3': {}
|
||||
|
||||
'@headlessui/react@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react': 0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@react-aria/focus': 3.18.2(react@18.3.1)
|
||||
'@react-aria/interactions': 3.22.2(react@18.3.1)
|
||||
'@tanstack/react-virtual': 3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@hono/auth-js@1.0.10(@auth/core@0.34.2)(hono@4.6.2(patch_hash=iej2g43ojhll5opwbnvyorjq4e))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@auth/core': 0.34.2
|
||||
|
|
@ -11976,6 +12072,46 @@ snapshots:
|
|||
|
||||
'@radix-ui/rect@1.1.0': {}
|
||||
|
||||
'@react-aria/focus@3.18.2(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.22.2(react@18.3.1)
|
||||
'@react-aria/utils': 3.25.2(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.13
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/interactions@3.22.2(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-aria/utils': 3.25.2(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.13
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/ssr@3.9.5(react@18.3.1)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.13
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/utils@3.25.2(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-stately/utils': 3.10.3(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.13
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
'@react-stately/utils@3.10.3(react@18.3.1)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.13
|
||||
react: 18.3.1
|
||||
|
||||
'@react-types/shared@3.24.1(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@remix-run/router@1.19.2': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.21.2':
|
||||
|
|
@ -12309,6 +12445,10 @@ snapshots:
|
|||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@swc/helpers@0.5.13':
|
||||
dependencies:
|
||||
tslib: 2.7.0
|
||||
|
||||
'@szmarczak/http-timer@4.0.6':
|
||||
dependencies:
|
||||
defer-to-connect: 2.0.1
|
||||
|
|
@ -12361,6 +12501,14 @@ snapshots:
|
|||
'@tanstack/query-core': 5.56.2
|
||||
react: 18.3.1
|
||||
|
||||
'@tanstack/react-virtual@3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.10.8
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@tanstack/virtual-core@3.10.8': {}
|
||||
|
||||
'@tootallnate/once@2.0.0': {}
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
|
|
@ -17953,6 +18101,8 @@ snapshots:
|
|||
|
||||
systemjs@6.15.1: {}
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@2.5.2: {}
|
||||
|
||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.11):
|
||||
|
|
|
|||
Loading…
Reference in New Issue