Add virtual prop to Combobox component (#2740)
* type timezones in playground data * add `@tanstack/react-virtual` and `@tanstack/vue-virtual` * use latest stable Tailwind CSS version * add Combobox with virtual prop example * add `virtual` prop to `Combobox` Co-authored-by: Jordan Pittman <jordan@cryptica.me> * add tests for `virtual` prop - Also wrap `click` helpers in `act` for React (use `rawClick` without `act` in tests related to `Transition`) * update changelog --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This commit is contained in:
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))
|
||||
- Add `virtual` prop to `Combobox` component ([#2740](https://github.com/tailwindlabs/headlessui/pull/2740))
|
||||
|
||||
## [1.7.17] - 2023-08-17
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"snapshot-diff": "^0.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.60",
|
||||
"client-only": "^0.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
import { useVirtualizer, Virtualizer } from '@tanstack/react-virtual'
|
||||
import React, {
|
||||
createContext,
|
||||
createRef,
|
||||
@@ -8,6 +9,7 @@ import React, {
|
||||
useMemo,
|
||||
useReducer,
|
||||
useRef,
|
||||
type CSSProperties,
|
||||
type ElementType,
|
||||
type FocusEvent as ReactFocusEvent,
|
||||
type KeyboardEvent as ReactKeyboardEvent,
|
||||
@@ -49,7 +51,6 @@ import {
|
||||
type PropsForFeatures,
|
||||
type RefProp,
|
||||
} from '../../utils/render'
|
||||
|
||||
import { Keys } from '../keyboard'
|
||||
|
||||
enum ComboboxState {
|
||||
@@ -69,10 +70,11 @@ enum ActivationTrigger {
|
||||
}
|
||||
|
||||
type ComboboxOptionDataRef<T> = MutableRefObject<{
|
||||
textValue?: string
|
||||
disabled: boolean
|
||||
value: T
|
||||
domRef: MutableRefObject<HTMLElement | null>
|
||||
order: number | null
|
||||
onVirtualRangeUpdate: (virtualizer: Virtualizer<any, any>) => void
|
||||
}>
|
||||
|
||||
interface StateDefinition<T> {
|
||||
@@ -107,10 +109,13 @@ function adjustOrderedState<T>(
|
||||
let currentActiveOption =
|
||||
state.activeOptionIndex !== null ? state.options[state.activeOptionIndex] : null
|
||||
|
||||
let sortedOptions = sortByDomNode(
|
||||
adjustment(state.options.slice()),
|
||||
(option) => option.dataRef.current.domRef.current
|
||||
)
|
||||
let list = adjustment(state.options.slice())
|
||||
let sortedOptions =
|
||||
list.length > 0 && list[0].dataRef.current.order !== null
|
||||
? // Prefer sorting based on the `order`
|
||||
list.sort((a, z) => a.dataRef.current.order! - z.dataRef.current.order!)
|
||||
: // Fallback to much slower DOM order
|
||||
sortByDomNode(list, (option) => option.dataRef.current.domRef.current)
|
||||
|
||||
// If we inserted an option before the current active option then the active option index
|
||||
// would be wrong. To fix this, we will re-lookup the correct index.
|
||||
@@ -203,17 +208,29 @@ let reducers: {
|
||||
resolveId: (item) => item.id,
|
||||
resolveDisabled: (item) => item.dataRef.current.disabled,
|
||||
})
|
||||
let activationTrigger = action.trigger ?? ActivationTrigger.Other
|
||||
|
||||
if (
|
||||
state.activeOptionIndex === activeOptionIndex &&
|
||||
state.activationTrigger === activationTrigger
|
||||
) {
|
||||
return state
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...adjustedState,
|
||||
activeOptionIndex,
|
||||
activationTrigger: action.trigger ?? ActivationTrigger.Other,
|
||||
activationTrigger,
|
||||
}
|
||||
},
|
||||
[ActionTypes.RegisterOption]: (state, action) => {
|
||||
let option = { id: action.id, dataRef: action.dataRef }
|
||||
let adjustedState = adjustOrderedState(state, (options) => [...options, option])
|
||||
|
||||
let adjustedState = adjustOrderedState(state, (options) => {
|
||||
options.push(option)
|
||||
return options
|
||||
})
|
||||
|
||||
// Check if we need to make the newly registered option active.
|
||||
if (state.activeOptionIndex === null) {
|
||||
@@ -286,6 +303,69 @@ function useActions(component: string) {
|
||||
}
|
||||
type _Actions = ReturnType<typeof useActions>
|
||||
|
||||
let VirtualContext = createContext<Virtualizer<any, any> | null>(null)
|
||||
|
||||
function VirtualProvider(props: React.PropsWithChildren<{}>) {
|
||||
let data = useData('VirtualProvider')
|
||||
|
||||
let firstAvailableOption = data.options.find((option) => option.dataRef.current.domRef.current)
|
||||
let measuredHeight = useMemo(() => {
|
||||
let height =
|
||||
firstAvailableOption?.dataRef.current.domRef.current?.getBoundingClientRect().height
|
||||
return height ?? 40
|
||||
}, [firstAvailableOption])
|
||||
|
||||
let [paddingStart, paddingEnd] = useMemo(() => {
|
||||
let el = data.optionsRef.current
|
||||
if (!el) return [0, 0]
|
||||
|
||||
let styles = window.getComputedStyle(el)
|
||||
|
||||
return [
|
||||
parseFloat(styles.paddingBlockStart || styles.paddingTop),
|
||||
parseFloat(styles.paddingBlockEnd || styles.paddingBottom),
|
||||
]
|
||||
}, [data.optionsRef.current])
|
||||
|
||||
let virtualizer = useVirtualizer({
|
||||
scrollPaddingStart: paddingStart,
|
||||
scrollPaddingEnd: paddingEnd,
|
||||
count: data.options.length,
|
||||
estimateSize() {
|
||||
return measuredHeight
|
||||
},
|
||||
getScrollElement() {
|
||||
return (data.optionsRef.current ?? null) as HTMLElement | null
|
||||
},
|
||||
overscan: 12,
|
||||
onChange(event) {
|
||||
let list = event.getVirtualItems()
|
||||
if (list.length === 0) return
|
||||
|
||||
let min = list[0].index
|
||||
let max = list[list.length - 1].index + 1
|
||||
|
||||
for (let option of data.options.slice(min, max)) {
|
||||
option.dataRef.current.onVirtualRangeUpdate(event)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<VirtualContext.Provider value={virtualizer}>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</VirtualContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
let ComboboxDataContext = createContext<
|
||||
| ({
|
||||
value: unknown
|
||||
@@ -299,6 +379,8 @@ let ComboboxDataContext = createContext<
|
||||
isSelected(value: unknown): boolean
|
||||
__demoMode: boolean
|
||||
|
||||
virtual: boolean
|
||||
|
||||
optionsPropsRef: MutableRefObject<{
|
||||
static: boolean
|
||||
hold: boolean
|
||||
@@ -393,6 +475,7 @@ export type ComboboxProps<
|
||||
form?: string
|
||||
name?: string
|
||||
immediate?: boolean
|
||||
virtual?: boolean
|
||||
}
|
||||
|
||||
function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG>(
|
||||
@@ -428,6 +511,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
|
||||
nullable = false,
|
||||
multiple = false,
|
||||
immediate = false,
|
||||
virtual = false,
|
||||
...theirProps
|
||||
} = props
|
||||
let [value = multiple ? [] : undefined, theirOnChange] = useControllable<any>(
|
||||
@@ -474,7 +558,6 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
|
||||
}),
|
||||
[value]
|
||||
)
|
||||
|
||||
let data = useMemo<_Data>(
|
||||
() => ({
|
||||
...state,
|
||||
@@ -488,6 +571,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
|
||||
defaultValue,
|
||||
disabled,
|
||||
mode: multiple ? ValueMode.Multi : ValueMode.Single,
|
||||
virtual,
|
||||
get activeOptionIndex() {
|
||||
if (
|
||||
defaultToFirstOption.current &&
|
||||
@@ -510,7 +594,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
|
||||
nullable,
|
||||
__demoMode,
|
||||
}),
|
||||
[value, defaultValue, disabled, multiple, nullable, __demoMode, state]
|
||||
[value, defaultValue, disabled, multiple, nullable, __demoMode, state, virtual]
|
||||
)
|
||||
|
||||
let lastActiveOption = useRef(
|
||||
@@ -1185,6 +1269,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
|
||||
if (data.comboboxState === ComboboxState.Closed) {
|
||||
actions.openCombobox()
|
||||
}
|
||||
|
||||
return d.nextFrame(() => data.inputRef.current?.focus({ preventScroll: true }))
|
||||
|
||||
case Keys.ArrowUp:
|
||||
@@ -1377,6 +1462,13 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
ref: optionsRef,
|
||||
}
|
||||
|
||||
// Map the children in a scrollable container when virtualization is enabled
|
||||
if (data.virtual && data.comboboxState === ComboboxState.Open) {
|
||||
Object.assign(theirProps, {
|
||||
children: <VirtualProvider>{theirProps.children}</VirtualProvider>,
|
||||
})
|
||||
}
|
||||
|
||||
return render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
@@ -1405,6 +1497,7 @@ export type ComboboxOptionProps<TTag extends ElementType, TType> = Props<
|
||||
{
|
||||
disabled?: boolean
|
||||
value: TType
|
||||
order?: number
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1419,41 +1512,57 @@ function OptionFn<
|
||||
id = `headlessui-combobox-option-${internalId}`,
|
||||
disabled = false,
|
||||
value,
|
||||
order = null,
|
||||
...theirProps
|
||||
} = props
|
||||
|
||||
let data = useData('Combobox.Option')
|
||||
let actions = useActions('Combobox.Option')
|
||||
|
||||
let active =
|
||||
data.activeOptionIndex !== null ? data.options[data.activeOptionIndex].id === id : false
|
||||
|
||||
if (order === null && data.virtual) {
|
||||
throw new Error(
|
||||
`The \`order\` prop on <Combobox.Option /> is required when using <Combobox virtual />.`
|
||||
)
|
||||
}
|
||||
|
||||
let [, rerender] = useReducer((v) => !v, true)
|
||||
let selected = data.isSelected(value)
|
||||
let internalOptionRef = useRef<HTMLLIElement | null>(null)
|
||||
let bag = useLatestValue<ComboboxOptionDataRef<TType>['current']>({
|
||||
disabled,
|
||||
value,
|
||||
domRef: internalOptionRef,
|
||||
textValue: internalOptionRef.current?.textContent?.toLowerCase(),
|
||||
order,
|
||||
onVirtualRangeUpdate: rerender,
|
||||
})
|
||||
let optionRef = useSyncRefs(ref, internalOptionRef)
|
||||
let virtualizer = useContext(VirtualContext)
|
||||
let optionRef = useSyncRefs(
|
||||
ref,
|
||||
internalOptionRef,
|
||||
virtualizer ? virtualizer.measureElement : null
|
||||
)
|
||||
|
||||
let select = useEvent(() => actions.selectOption(id))
|
||||
useIsoMorphicEffect(() => actions.registerOption(id, bag), [bag, id])
|
||||
|
||||
let enableScrollIntoView = useRef(data.__demoMode ? false : true)
|
||||
let enableScrollIntoView = useRef(data.virtual || data.__demoMode ? false : true)
|
||||
useIsoMorphicEffect(() => {
|
||||
if (!data.virtual) return
|
||||
if (!data.__demoMode) return
|
||||
let d = disposables()
|
||||
d.requestAnimationFrame(() => {
|
||||
enableScrollIntoView.current = true
|
||||
})
|
||||
return d.dispose
|
||||
}, [])
|
||||
}, [data.virtual, data.__demoMode])
|
||||
|
||||
useIsoMorphicEffect(() => {
|
||||
if (!enableScrollIntoView.current) return
|
||||
if (data.comboboxState !== ComboboxState.Open) return
|
||||
if (!active) return
|
||||
if (!enableScrollIntoView.current) return
|
||||
if (data.activationTrigger === ActivationTrigger.Pointer) return
|
||||
let d = disposables()
|
||||
d.requestAnimationFrame(() => {
|
||||
@@ -1522,6 +1631,43 @@ function OptionFn<
|
||||
[active, selected, disabled]
|
||||
)
|
||||
|
||||
let virtualIdx = useMemo(() => {
|
||||
if (!data.virtual) return -1
|
||||
return data.options.findIndex((o) => o.id === id) ?? 0
|
||||
}, [virtualizer, data.options, id])
|
||||
|
||||
let virtualItem =
|
||||
virtualIdx === -1
|
||||
? undefined
|
||||
: (virtualizer?.getVirtualItems() ?? []).find((item) => item.index === virtualIdx)
|
||||
|
||||
let d = useDisposables()
|
||||
let shouldScroll =
|
||||
virtualizer && data.activationTrigger !== ActivationTrigger.Pointer && data.virtual && active
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldScroll) return
|
||||
|
||||
// Try scrolling to the item
|
||||
virtualizer!.scrollToIndex(virtualIdx)
|
||||
|
||||
// Ensure we scrolled to the correct location
|
||||
;(function ensureScrolledCorrectly() {
|
||||
if (virtualizer?.isScrolling) {
|
||||
d.requestAnimationFrame(ensureScrolledCorrectly)
|
||||
return
|
||||
}
|
||||
|
||||
virtualizer!.scrollToIndex(virtualIdx)
|
||||
})()
|
||||
|
||||
return d.dispose
|
||||
}, [active, virtualizer, virtualIdx, shouldScroll])
|
||||
|
||||
if (data.virtual && !virtualItem) {
|
||||
return null
|
||||
}
|
||||
|
||||
let ourProps = {
|
||||
id,
|
||||
ref: optionRef,
|
||||
@@ -1532,6 +1678,9 @@ function OptionFn<
|
||||
// multi-select,but Voice-Over disagrees. So we use aria-checked instead for
|
||||
// both single and multi-select.
|
||||
'aria-selected': selected,
|
||||
'data-index': virtualizer && virtualIdx !== -1 ? virtualIdx : undefined,
|
||||
'aria-setsize': virtualizer ? data.options.length : undefined,
|
||||
'aria-posinset': virtualizer && virtualIdx !== -1 ? virtualIdx + 1 : undefined,
|
||||
disabled: undefined, // Never forward the `disabled` prop
|
||||
onClick: handleClick,
|
||||
onFocus: handleFocus,
|
||||
@@ -1543,6 +1692,21 @@ function OptionFn<
|
||||
onMouseLeave: handleLeave,
|
||||
}
|
||||
|
||||
if (virtualItem) {
|
||||
let localOurProps = ourProps as typeof ourProps & { style: CSSProperties }
|
||||
|
||||
localOurProps.style = {
|
||||
...localOurProps.style,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}
|
||||
|
||||
// Technically unnecessary
|
||||
ourProps = localOurProps
|
||||
}
|
||||
|
||||
return render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
mouseLeave,
|
||||
mouseMove,
|
||||
press,
|
||||
rawClick,
|
||||
shift,
|
||||
type,
|
||||
word,
|
||||
@@ -1353,7 +1354,7 @@ describe('Composition', () => {
|
||||
})
|
||||
assertListbox({ state: ListboxState.InvisibleUnmounted })
|
||||
|
||||
await click(getListboxButton())
|
||||
await rawClick(getListboxButton())
|
||||
|
||||
assertListboxButton({
|
||||
state: ListboxState.Visible,
|
||||
@@ -1364,7 +1365,7 @@ describe('Composition', () => {
|
||||
textContent: JSON.stringify({ active: false, selected: false, disabled: false }),
|
||||
})
|
||||
|
||||
await click(getListboxButton())
|
||||
await rawClick(getListboxButton())
|
||||
|
||||
// Verify that we tracked the `mounts` and `unmounts` in the correct order
|
||||
expect(orderFn.mock.calls).toEqual([
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
mouseLeave,
|
||||
mouseMove,
|
||||
press,
|
||||
rawClick,
|
||||
shift,
|
||||
type,
|
||||
word,
|
||||
@@ -646,7 +647,7 @@ describe('Composition', () => {
|
||||
})
|
||||
assertMenu({ state: MenuState.InvisibleUnmounted })
|
||||
|
||||
await click(getMenuButton())
|
||||
await rawClick(getMenuButton())
|
||||
|
||||
assertMenuButton({
|
||||
state: MenuState.Visible,
|
||||
@@ -657,7 +658,7 @@ describe('Composition', () => {
|
||||
textContent: JSON.stringify({ active: false, disabled: false }),
|
||||
})
|
||||
|
||||
await click(getMenuButton())
|
||||
await rawClick(getMenuButton())
|
||||
|
||||
// Verify that we tracked the `mounts` and `unmounts` in the correct order
|
||||
expect(orderFn.mock.calls).toEqual([
|
||||
@@ -700,7 +701,7 @@ describe('Composition', () => {
|
||||
})
|
||||
assertMenu({ state: MenuState.InvisibleUnmounted })
|
||||
|
||||
await click(getMenuButton())
|
||||
await rawClick(getMenuButton())
|
||||
|
||||
assertMenuButton({
|
||||
state: MenuState.Visible,
|
||||
@@ -711,7 +712,7 @@ describe('Composition', () => {
|
||||
textContent: JSON.stringify({ active: false, disabled: false }),
|
||||
})
|
||||
|
||||
await click(getMenuButton())
|
||||
await rawClick(getMenuButton())
|
||||
|
||||
// Verify that we tracked the `mounts` and `unmounts` in the correct order
|
||||
expect(orderFn.mock.calls).toEqual([
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fireEvent } from '@testing-library/react'
|
||||
import { act, fireEvent } from '@testing-library/react'
|
||||
import { pointer } from './fake-pointer'
|
||||
|
||||
function nextFrame(cb: Function): void {
|
||||
@@ -227,6 +227,13 @@ export enum MouseButton {
|
||||
export async function click(
|
||||
element: Document | Element | Window | Node | null,
|
||||
button = MouseButton.Left
|
||||
) {
|
||||
return act(() => rawClick(element, button))
|
||||
}
|
||||
|
||||
export async function rawClick(
|
||||
element: Document | Element | Window | Node | null,
|
||||
button = MouseButton.Left
|
||||
) {
|
||||
try {
|
||||
if (element === null) return expect(element).not.toBe(null)
|
||||
|
||||
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))
|
||||
- Add `virtual` prop to `Combobox` component ([#2740](https://github.com/tailwindlabs/headlessui/pull/2740))
|
||||
|
||||
## [1.7.16] - 2023-08-17
|
||||
|
||||
|
||||
@@ -44,5 +44,8 @@
|
||||
"@testing-library/vue": "^5.8.2",
|
||||
"@vue/test-utils": "^2.0.0-rc.18",
|
||||
"vue": "^3.2.29"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/vue-virtual": "^3.0.0-beta.60"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
import type { Virtualizer } from '@tanstack/virtual-core'
|
||||
import { useVirtualizer } from '@tanstack/vue-virtual'
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
@@ -8,15 +10,20 @@ import {
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
provide,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
watch,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
type ComputedRef,
|
||||
type CSSProperties,
|
||||
type InjectionKey,
|
||||
type PropType,
|
||||
type Ref,
|
||||
type UnwrapNestedRefs,
|
||||
type UnwrapRef,
|
||||
} from 'vue'
|
||||
import { useControllable } from '../../hooks/use-controllable'
|
||||
import { useId } from '../../hooks/use-id'
|
||||
@@ -62,6 +69,8 @@ type ComboboxOptionData = {
|
||||
disabled: boolean
|
||||
value: unknown
|
||||
domRef: Ref<HTMLElement | null>
|
||||
order: Ref<number | null>
|
||||
onVirtualRangeUpdate: (virtualizer: Virtualizer<any, any>) => void
|
||||
}
|
||||
type StateDefinition = {
|
||||
// State
|
||||
@@ -72,6 +81,7 @@ type StateDefinition = {
|
||||
mode: ComputedRef<ValueMode>
|
||||
nullable: ComputedRef<boolean>
|
||||
immediate: ComputedRef<boolean>
|
||||
virtual: ComputedRef<boolean>
|
||||
|
||||
compare: (a: unknown, z: unknown) => boolean
|
||||
|
||||
@@ -84,6 +94,7 @@ type StateDefinition = {
|
||||
|
||||
disabled: Ref<boolean>
|
||||
options: Ref<{ id: string; dataRef: ComputedRef<ComboboxOptionData> }[]>
|
||||
indexes: Ref<Record<string, number>>
|
||||
activeOptionIndex: Ref<number | null>
|
||||
activationTrigger: Ref<ActivationTrigger>
|
||||
|
||||
@@ -116,6 +127,82 @@ function useComboboxContext(component: string) {
|
||||
|
||||
// ---
|
||||
|
||||
let VirtualContext = Symbol('VirtualContext') as InjectionKey<Ref<Virtualizer<any, any>> | null>
|
||||
|
||||
let VirtualProvider = defineComponent({
|
||||
name: 'VirtualProvider',
|
||||
setup(_, { slots }) {
|
||||
let api = useComboboxContext('VirtualProvider')
|
||||
|
||||
let measuredHeight = computed(() => {
|
||||
let firstAvailableOption = api.options.value.find(
|
||||
(option) => dom(option.dataRef.value.domRef) !== null
|
||||
)
|
||||
let height = dom(firstAvailableOption?.dataRef.value.domRef)?.getBoundingClientRect().height
|
||||
return height ?? 40
|
||||
})
|
||||
|
||||
let padding = computed(() => {
|
||||
let el = dom(api.optionsRef)
|
||||
if (!el) return { start: 0, end: 0 }
|
||||
|
||||
let styles = window.getComputedStyle(el)
|
||||
|
||||
return {
|
||||
start: parseFloat(styles.paddingBlockStart || styles.paddingTop),
|
||||
end: parseFloat(styles.paddingBlockEnd || styles.paddingBottom),
|
||||
}
|
||||
})
|
||||
|
||||
let virtualizer = useVirtualizer<HTMLDivElement, HTMLLIElement>(
|
||||
computed(() => {
|
||||
return {
|
||||
scrollPaddingStart: padding.value.start,
|
||||
scrollPaddingEnd: padding.value.end,
|
||||
count: api.options.value.length,
|
||||
estimateSize() {
|
||||
return measuredHeight.value
|
||||
},
|
||||
getScrollElement() {
|
||||
return dom(api.optionsRef)
|
||||
},
|
||||
overscan: 12,
|
||||
onChange(event) {
|
||||
let list = event.getVirtualItems()
|
||||
if (list.length === 0) return
|
||||
|
||||
let min = list[0].index
|
||||
let max = list[list.length - 1].index + 1
|
||||
|
||||
for (let option of api.options.value.slice(min, max)) {
|
||||
let dataRef = option.dataRef as unknown as UnwrapRef<typeof option.dataRef>
|
||||
dataRef.onVirtualRangeUpdate(event)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
provide(VirtualContext, api.virtual.value ? virtualizer : null)
|
||||
|
||||
return () => [
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: `${virtualizer.value.getTotalSize()}px`,
|
||||
},
|
||||
},
|
||||
slots.default?.()
|
||||
),
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
// ---
|
||||
|
||||
export let Combobox = defineComponent({
|
||||
name: 'Combobox',
|
||||
emits: { 'update:modelValue': (_value: any) => true },
|
||||
@@ -140,6 +227,7 @@ export let Combobox = defineComponent({
|
||||
nullable: { type: Boolean, default: false },
|
||||
multiple: { type: [Boolean], default: false },
|
||||
immediate: { type: [Boolean], default: false },
|
||||
virtual: { type: [Boolean], default: false },
|
||||
},
|
||||
inheritAttrs: false,
|
||||
setup(props, { slots, attrs, emit }) {
|
||||
@@ -155,12 +243,19 @@ export let Combobox = defineComponent({
|
||||
hold: false,
|
||||
}) as StateDefinition['optionsPropsRef']
|
||||
let options = ref<StateDefinition['options']['value']>([])
|
||||
let indexes = shallowRef<Record<string, number>>({})
|
||||
let activeOptionIndex = ref<StateDefinition['activeOptionIndex']['value']>(null)
|
||||
let activationTrigger = ref<StateDefinition['activationTrigger']['value']>(
|
||||
ActivationTrigger.Other
|
||||
)
|
||||
let defaultToFirstOption = ref(false)
|
||||
|
||||
// This is not a "computed" ref because we eventually
|
||||
// want to calculate this only when the length or order can actually change
|
||||
function recalculateIndexes() {
|
||||
indexes.value = Object.fromEntries(options.value.map((v, idx) => [v.id, idx]))
|
||||
}
|
||||
|
||||
function adjustOrderedState(
|
||||
adjustment: (
|
||||
options: UnwrapNestedRefs<StateDefinition['options']['value']>
|
||||
@@ -169,9 +264,14 @@ export let Combobox = defineComponent({
|
||||
let currentActiveOption =
|
||||
activeOptionIndex.value !== null ? options.value[activeOptionIndex.value] : null
|
||||
|
||||
let sortedOptions = sortByDomNode(adjustment(options.value.slice()), (option) =>
|
||||
dom(option.dataRef.domRef)
|
||||
)
|
||||
let list = adjustment(options.value.slice())
|
||||
|
||||
let sortedOptions =
|
||||
list.length > 0 && list[0].dataRef.order.value !== null
|
||||
? // Prefer sorting based on the `order`
|
||||
list.sort((a, z) => a.dataRef.order.value! - z.dataRef.order.value!)
|
||||
: // Fallback to much slower DOM order
|
||||
sortByDomNode(list, (option) => dom(option.dataRef.domRef))
|
||||
|
||||
// If we inserted an option before the current active option then the active option index
|
||||
// would be wrong. To fix this, we will re-lookup the correct index.
|
||||
@@ -224,12 +324,14 @@ export let Combobox = defineComponent({
|
||||
defaultValue: computed(() => props.defaultValue),
|
||||
nullable,
|
||||
immediate: computed(() => props.immediate),
|
||||
virtual: computed(() => props.virtual),
|
||||
inputRef,
|
||||
labelRef,
|
||||
buttonRef,
|
||||
optionsRef,
|
||||
disabled: computed(() => props.disabled),
|
||||
options,
|
||||
indexes,
|
||||
change(value: unknown) {
|
||||
theirOnChange(value as typeof props.modelValue)
|
||||
},
|
||||
@@ -241,7 +343,7 @@ export let Combobox = defineComponent({
|
||||
) {
|
||||
let localActiveOptionIndex = options.value.findIndex((option) => !option.dataRef.disabled)
|
||||
if (localActiveOptionIndex !== -1) {
|
||||
activeOptionIndex.value = localActiveOptionIndex
|
||||
return localActiveOptionIndex
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +435,7 @@ export let Combobox = defineComponent({
|
||||
activeOptionIndex.value = nextActiveOptionIndex
|
||||
activationTrigger.value = trigger ?? ActivationTrigger.Other
|
||||
options.value = adjustedState.options
|
||||
recalculateIndexes()
|
||||
})
|
||||
},
|
||||
selectOption(id: string) {
|
||||
@@ -389,7 +492,10 @@ export let Combobox = defineComponent({
|
||||
registerOption(id: string, dataRef: ComboboxOptionData) {
|
||||
if (orderOptionsRaf) cancelAnimationFrame(orderOptionsRaf)
|
||||
|
||||
let option = { id, dataRef }
|
||||
let option = reactive({ id, dataRef }) as unknown as {
|
||||
id: typeof id
|
||||
dataRef: typeof dataRef
|
||||
}
|
||||
|
||||
let adjustedState = adjustOrderedState((options) => {
|
||||
options.push(option)
|
||||
@@ -415,13 +521,16 @@ export let Combobox = defineComponent({
|
||||
options.value = adjustedState.options
|
||||
activeOptionIndex.value = adjustedState.activeOptionIndex
|
||||
activationTrigger.value = ActivationTrigger.Other
|
||||
recalculateIndexes()
|
||||
|
||||
// If some of the DOM elements aren't ready yet, then we can retry in the next tick.
|
||||
if (adjustedState.options.some((option) => !dom(option.dataRef.domRef))) {
|
||||
orderOptionsRaf = requestAnimationFrame(() => {
|
||||
let adjustedState = adjustOrderedState()
|
||||
options.value = adjustedState.options
|
||||
|
||||
activeOptionIndex.value = adjustedState.activeOptionIndex
|
||||
recalculateIndexes()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -451,6 +560,7 @@ export let Combobox = defineComponent({
|
||||
options.value = adjustedState.options
|
||||
activeOptionIndex.value = adjustedState.activeOptionIndex
|
||||
activationTrigger.value = ActivationTrigger.Other
|
||||
recalculateIndexes()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -533,13 +643,14 @@ export let Combobox = defineComponent({
|
||||
theirProps: {
|
||||
...attrs,
|
||||
...omit(theirProps, [
|
||||
'modelValue',
|
||||
'defaultValue',
|
||||
'nullable',
|
||||
'multiple',
|
||||
'immediate',
|
||||
'onUpdate:modelValue',
|
||||
'by',
|
||||
'defaultValue',
|
||||
'immediate',
|
||||
'modelValue',
|
||||
'multiple',
|
||||
'nullable',
|
||||
'onUpdate:modelValue',
|
||||
'virtual',
|
||||
]),
|
||||
},
|
||||
ourProps: {},
|
||||
@@ -1164,7 +1275,13 @@ export let ComboboxOptions = defineComponent({
|
||||
theirProps,
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
slots:
|
||||
api.virtual.value && api.comboboxState.value === ComboboxStates.Open
|
||||
? {
|
||||
...slots,
|
||||
default: () => [h(VirtualProvider, {}, slots.default)],
|
||||
}
|
||||
: slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'ComboboxOptions',
|
||||
@@ -1183,6 +1300,7 @@ export let ComboboxOption = defineComponent({
|
||||
>,
|
||||
},
|
||||
disabled: { type: Boolean, default: false },
|
||||
order: { type: [Number], default: null },
|
||||
},
|
||||
setup(props, { slots, attrs, expose }) {
|
||||
let api = useComboboxContext('ComboboxOption')
|
||||
@@ -1191,6 +1309,14 @@ export let ComboboxOption = defineComponent({
|
||||
|
||||
expose({ el: internalOptionRef, $el: internalOptionRef })
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.order === null && api.virtual.value) {
|
||||
throw new Error(
|
||||
`The \`order\` prop on <ComboboxOption /> is required when using <Combobox virtual />.`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
let active = computed(() => {
|
||||
return api.activeOptionIndex.value !== null
|
||||
? api.options.value[api.activeOptionIndex.value].id === id
|
||||
@@ -1207,18 +1333,29 @@ export let ComboboxOption = defineComponent({
|
||||
})
|
||||
)
|
||||
|
||||
let virtualizer = inject(VirtualContext, null)
|
||||
let dataRef = computed<ComboboxOptionData>(() => ({
|
||||
disabled: props.disabled,
|
||||
value: props.value,
|
||||
domRef: internalOptionRef,
|
||||
order: computed(() => props.order),
|
||||
onVirtualRangeUpdate: () => {},
|
||||
}))
|
||||
|
||||
onMounted(() => api.registerOption(id, dataRef))
|
||||
onUnmounted(() => api.unregisterOption(id))
|
||||
|
||||
watchEffect(() => {
|
||||
let el = dom(internalOptionRef)
|
||||
if (!el) return
|
||||
|
||||
virtualizer?.value.measureElement(el)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (api.comboboxState.value !== ComboboxStates.Open) return
|
||||
if (!active.value) return
|
||||
if (api.virtual.value) return
|
||||
if (api.activationTrigger.value === ActivationTrigger.Pointer) return
|
||||
nextTick(() => dom(internalOptionRef)?.scrollIntoView?.({ block: 'nearest' }))
|
||||
})
|
||||
@@ -1274,7 +1411,53 @@ export let ComboboxOption = defineComponent({
|
||||
api.goToOption(Focus.Nothing)
|
||||
}
|
||||
|
||||
let virtualIdx = computed(() => {
|
||||
if (!api.virtual.value) return -1
|
||||
return api.indexes.value[id] ?? 0
|
||||
})
|
||||
|
||||
let virtualItem = computed(() => {
|
||||
return virtualIdx.value === -1
|
||||
? undefined
|
||||
: virtualizer?.value.getVirtualItems().find((item) => item.index === virtualIdx.value)
|
||||
})
|
||||
|
||||
let d = disposables()
|
||||
onUnmounted(() => d.dispose())
|
||||
|
||||
let shouldScroll = computed(() => {
|
||||
return (
|
||||
virtualizer?.value &&
|
||||
api.activationTrigger.value !== ActivationTrigger.Pointer &&
|
||||
api.virtual.value &&
|
||||
active.value
|
||||
)
|
||||
})
|
||||
|
||||
watchPostEffect((onCleanup) => {
|
||||
if (!shouldScroll.value) return
|
||||
|
||||
// Try scrolling to the item
|
||||
virtualizer!.value.scrollToIndex(virtualIdx.value)
|
||||
|
||||
// Ensure we scrolled to the correct location
|
||||
;(function ensureScrolledCorrectly() {
|
||||
if (virtualizer?.value.isScrolling) {
|
||||
d.requestAnimationFrame(ensureScrolledCorrectly)
|
||||
return
|
||||
}
|
||||
|
||||
virtualizer!.value.scrollToIndex(virtualIdx.value)
|
||||
})()
|
||||
|
||||
onCleanup(d.dispose)
|
||||
})
|
||||
|
||||
return () => {
|
||||
if (api.virtual.value && !virtualItem.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
let { disabled } = props
|
||||
let slot = { active: active.value, selected: selected.value, disabled }
|
||||
let ourProps = {
|
||||
@@ -1287,6 +1470,9 @@ export let ComboboxOption = defineComponent({
|
||||
// multi-select,but Voice-Over disagrees. So we use aria-selected instead for
|
||||
// both single and multi-select.
|
||||
'aria-selected': selected.value,
|
||||
'data-index': virtualizer && virtualIdx.value !== -1 ? virtualIdx.value : undefined,
|
||||
'aria-setsize': virtualizer ? api.options.value.length : undefined,
|
||||
'aria-posinset': virtualizer && virtualIdx.value !== -1 ? virtualIdx.value + 1 : undefined,
|
||||
disabled: undefined, // Never forward the `disabled` prop
|
||||
onClick: handleClick,
|
||||
onFocus: handleFocus,
|
||||
@@ -1298,7 +1484,22 @@ export let ComboboxOption = defineComponent({
|
||||
onMouseleave: handleLeave,
|
||||
}
|
||||
|
||||
let theirProps = props
|
||||
if (virtualItem.value) {
|
||||
let localOurProps = ourProps as typeof ourProps & { style: CSSProperties }
|
||||
|
||||
localOurProps.style = {
|
||||
...localOurProps.style,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
transform: `translateY(${virtualItem.value!.start}px)`,
|
||||
}
|
||||
|
||||
// Technically unnecessary
|
||||
ourProps = localOurProps
|
||||
}
|
||||
|
||||
let theirProps = omit(props, ['order'])
|
||||
|
||||
return render({
|
||||
ourProps,
|
||||
|
||||
@@ -249,3 +249,6 @@ export let countries = [
|
||||
'Zimbabwe',
|
||||
'Åland Islands',
|
||||
]
|
||||
|
||||
// @ts-expect-error
|
||||
export let timezones: string[] = Intl.supportedValuesOf('timeZone')
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"react-dom": "^18.0.0",
|
||||
"react-flatpickr": "^3.10.9",
|
||||
"react-hot-toast": "2.3.0",
|
||||
"tailwindcss": "^0.0.0-insiders.9faf109"
|
||||
"tailwindcss": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@floating-ui/react": "^0.24.8"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Combobox } from '@headlessui/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Button } from '../../components/button'
|
||||
import { timezones as allTimezones } from '../../data'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex">
|
||||
<Example virtual={true} initial="Europe/Brussels" />
|
||||
<Example virtual={false} initial="Europe/Brussels" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Example({ virtual = true, initial }: { virtual?: boolean; initial: string }) {
|
||||
let [query, setQuery] = useState('')
|
||||
let [activeTimezone, setActiveTimezone] = useState(initial)
|
||||
|
||||
let timezones =
|
||||
query === ''
|
||||
? allTimezones
|
||||
: allTimezones.filter((timezone) => timezone.toLowerCase().includes(query.toLowerCase()))
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-screen justify-center bg-gray-50 p-12">
|
||||
<div className="mx-auto w-full max-w-xs">
|
||||
<div className="py-8 font-mono text-xs">Selected timezone: {activeTimezone}</div>
|
||||
<div className="space-y-1">
|
||||
<Combobox
|
||||
virtual={virtual}
|
||||
value={activeTimezone}
|
||||
nullable
|
||||
onChange={(value) => {
|
||||
setActiveTimezone(value)
|
||||
setQuery('')
|
||||
}}
|
||||
as="div"
|
||||
>
|
||||
<Combobox.Label className="block text-sm font-medium leading-5 text-gray-700">
|
||||
Timezone {virtual ? `(virtual)` : ''}
|
||||
</Combobox.Label>
|
||||
|
||||
<div className="relative">
|
||||
<span className="relative inline-flex flex-row overflow-hidden rounded-md border shadow-sm">
|
||||
<Combobox.Input
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="border-none px-3 py-1 outline-none"
|
||||
/>
|
||||
<Combobox.Button as={Button}>
|
||||
<span className="pointer-events-none flex items-center px-2">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M7 7l3-3 3 3m0 6l-3 3-3-3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Combobox.Button>
|
||||
</span>
|
||||
|
||||
<div className="absolute mt-1 w-full rounded-md bg-white shadow-lg">
|
||||
<Combobox.Options className="shadow-xs max-h-60 overflow-auto rounded-md py-1 text-base leading-6 focus:outline-none sm:text-sm sm:leading-5">
|
||||
{timezones.map((timezone, idx) => {
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={timezone}
|
||||
order={virtual ? idx : undefined}
|
||||
value={timezone}
|
||||
className={({ active }) => {
|
||||
return classNames(
|
||||
'relative w-full cursor-default select-none py-2 pl-3 pr-9 focus:outline-none',
|
||||
active ? 'bg-indigo-600 text-white' : 'text-gray-900'
|
||||
)
|
||||
}}
|
||||
>
|
||||
{({ active, selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames(
|
||||
'block truncate',
|
||||
selected ? 'font-semibold' : 'font-normal'
|
||||
)}
|
||||
>
|
||||
{timezone}
|
||||
</span>
|
||||
{selected && (
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||
active ? 'text-white' : 'text-indigo-600'
|
||||
)}
|
||||
>
|
||||
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
)
|
||||
})}
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
</div>
|
||||
</Combobox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^0.0.0-insiders.9faf109",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"vue": "^3.2.27",
|
||||
"vue-flatpickr-component": "^9.0.5",
|
||||
"vue-router": "^4.0.0"
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div class="flex h-full w-screen justify-center bg-gray-50 p-12">
|
||||
<div class="mx-auto w-full max-w-xs">
|
||||
<div class="py-8 font-mono text-xs">Selected timezone: {{ activeTimezone }}</div>
|
||||
<div class="space-y-1">
|
||||
<Combobox nullable v-model="activeTimezone" as="div" :virtual="virtual">
|
||||
<ComboboxLabel class="block text-sm font-medium leading-5 text-gray-700">
|
||||
Timezone {{ virtual ? '(virtual)' : '' }}
|
||||
</ComboboxLabel>
|
||||
|
||||
<div class="relative">
|
||||
<span class="relative inline-flex flex-row overflow-hidden rounded-md border shadow-sm">
|
||||
<ComboboxInput
|
||||
@change="query = $event.target.value"
|
||||
class="border-none px-3 py-1 outline-none"
|
||||
/>
|
||||
<ComboboxButton
|
||||
class="cursor-default border-l bg-gray-100 px-1 text-indigo-600 focus:outline-none"
|
||||
>
|
||||
<span class="pointer-events-none flex items-center px-2">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M7 7l3-3 3 3m0 6l-3 3-3-3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</ComboboxButton>
|
||||
</span>
|
||||
|
||||
<div class="absolute mt-1 w-full rounded-md bg-white shadow-lg">
|
||||
<ComboboxOptions
|
||||
class="shadow-xs max-h-60 overflow-auto rounded-md py-1 text-base leading-6 focus:outline-none sm:text-sm sm:leading-5"
|
||||
>
|
||||
<ComboboxOption
|
||||
v-for="(timezone, idx) in timezones"
|
||||
:key="timezone"
|
||||
:value="timezone"
|
||||
:order="virtual ? idx : undefined"
|
||||
v-slot="{ active, selected }"
|
||||
as="template"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
'relative w-full cursor-default select-none py-2 pl-3 pr-9 focus:outline-none',
|
||||
active ? 'bg-indigo-600 text-white' : 'text-gray-900',
|
||||
]"
|
||||
>
|
||||
<span :class="['block truncate', selected ? 'font-semibold' : 'font-normal']">
|
||||
{{ timezone }}
|
||||
</span>
|
||||
<span
|
||||
v-if="selected"
|
||||
:class="[
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||
active ? 'text-white' : 'text-indigo-600',
|
||||
]"
|
||||
>
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
</ComboboxOptions>
|
||||
</div>
|
||||
</div>
|
||||
</Combobox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { timezones as allTimezones } from '../../data'
|
||||
import { ref, computed } from 'vue'
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxButton,
|
||||
ComboboxInput,
|
||||
ComboboxLabel,
|
||||
ComboboxOption,
|
||||
ComboboxOptions,
|
||||
} from '@headlessui/vue'
|
||||
|
||||
defineProps({
|
||||
virtual: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
let query = ref('')
|
||||
let activeTimezone = ref('Europe/Brussels')
|
||||
let timezones = computed(() => {
|
||||
return query.value === ''
|
||||
? allTimezones
|
||||
: allTimezones.filter((timezone) => timezone.toLowerCase().includes(query.value.toLowerCase()))
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<Example :virtual="true" />
|
||||
<Example :virtual="false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Example from './_virtual-example.vue'
|
||||
</script>
|
||||
@@ -249,3 +249,6 @@ export let countries = [
|
||||
'Zimbabwe',
|
||||
'Åland Islands',
|
||||
]
|
||||
|
||||
// @ts-expect-error
|
||||
export let timezones: string[] = Intl.supportedValuesOf('timeZone')
|
||||
|
||||
@@ -932,66 +932,6 @@
|
||||
dependencies:
|
||||
mini-svg-data-uri "^1.2.3"
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-0.0.0-insiders.9faf109.tgz#a44f63ca1e8f1fc1355ab4e65d54a3249dc31196"
|
||||
integrity sha512-SnJBw4j8uZddhXEhfsQHXUgpECVLn+icCXDTLh58cRPekKZ9JTj/uYyFYbnZHcMyIHiAKx1mQhoCP0FDXCUiWA==
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-0.0.0-insiders.9faf109.tgz#9146d714997e485292515011152319108862b923"
|
||||
integrity sha512-lZvruT3X8tBQ/m/ShvGUxoMSaqfRh1jXoqKX6oZjuqRuTNPgG0hV2u02VDst+kL3WsZ+NoNuzDYk5jYQNUIV9A==
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-0.0.0-insiders.9faf109.tgz#de09732e1aefa3c1046b2d0a022091f92685aa0b"
|
||||
integrity sha512-FGh/kwk2oVYJXed+GCmVkqv0COYdNwYBeW5rLVumHT8S5yyZo7xQtEto9ehKjfHuhuN49rD5asgtNK2/pbCN0Q==
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-0.0.0-insiders.9faf109.tgz#8db1007aec8c08b150262d3c4733cce238bafb68"
|
||||
integrity sha512-7rN2EuKTfZpAajhWRf+Qe0YgnomRpBYFwjL8KV8RvZ9z3rRlO1hG72JKLEegCoCH6atTqKO1c8oGRMZMhPQFDg==
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-0.0.0-insiders.9faf109.tgz#f23a8b1a6f1e8fd2cdd71c612f39e2a14bf7e7ec"
|
||||
integrity sha512-4pczdi52hqp2Hm9zO0OtMQyAr6JWrKNIoMF/UQQ2H61iBfaNmvaqWdpo4OVx2MjFq983nvLr8/fLC06cJp/zpA==
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-0.0.0-insiders.9faf109.tgz#d0c155b72e88dd9556e04cc7162123009a1132af"
|
||||
integrity sha512-OHGesCnEx8sMvle3mNLHVveDXCNIM2nlFyBxoHb7Xj58bmbgwDBXYI0GDl/KxhV+zYhGJxJm1K5+7XbUBXfgYw==
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-0.0.0-insiders.9faf109.tgz#02b1d2470c020494f8cf547bca236779bb23ab4e"
|
||||
integrity sha512-cSmIxJrPsns31RcWHah2XxQs1BBfyWy4Q7hTKen8aIGfYhjY9jhXVt0jeMm64ZOmmWzirV9y4Qkt4nt9ULSU2w==
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-0.0.0-insiders.9faf109.tgz#3771a21c87c511944d8bb341e0a6abd60f477f4b"
|
||||
integrity sha512-zvNLNfmPtoXQnEsVnRh+dkeJCMqa6/XVYG3eSlejSzzKcz9+ZM6XXl+LBkm15a9gGYvew8jfQ9IsohIexErCbw==
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-0.0.0-insiders.9faf109.tgz#df51683c2d82cd743e3503e83de270e84b54ab8e"
|
||||
integrity sha512-VVIGzP3WB/xfOqf9QJRvtvs2uWqq3VAxYjdR6ilIxP6tn3cbJdOjqN7w9c8dhzgHAulEmBgW1Ea1roXeZKd39g==
|
||||
|
||||
"@tailwindcss/oxide@0.0.0-insiders.9faf109":
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-0.0.0-insiders.9faf109.tgz#031ec5a8d731e58b4782918d3a9d9a4315e69fe7"
|
||||
integrity sha512-MsYO1lUlUigkiQCcqhS+V2OdseN59fn1odJpraX8Nqk70Xfz/6U2JS0mcXsCOYrtPTt0u6zTSxTojEc8MYPlaA==
|
||||
optionalDependencies:
|
||||
"@tailwindcss/oxide-darwin-arm64" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-darwin-x64" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-freebsd-x64" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-linux-arm64-gnu" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-linux-arm64-musl" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-linux-x64-gnu" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-linux-x64-musl" "0.0.0-insiders.9faf109"
|
||||
"@tailwindcss/oxide-win32-x64-msvc" "0.0.0-insiders.9faf109"
|
||||
|
||||
"@tailwindcss/typography@^0.5.2":
|
||||
version "0.5.2"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.2.tgz"
|
||||
@@ -1001,6 +941,25 @@
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.merge "^4.6.2"
|
||||
|
||||
"@tanstack/react-virtual@^3.0.0-beta.60":
|
||||
version "3.0.0-beta.60"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.60.tgz#2b37c0d72997a54f7927f6b159a77311429fec1e"
|
||||
integrity sha512-F0wL9+byp7lf/tH6U5LW0ZjBqs+hrMXJrj5xcIGcklI0pggvjzMNW9DdIBcyltPNr6hmHQ0wt8FDGe1n1ZAThA==
|
||||
dependencies:
|
||||
"@tanstack/virtual-core" "3.0.0-beta.60"
|
||||
|
||||
"@tanstack/virtual-core@3.0.0-beta.60":
|
||||
version "3.0.0-beta.60"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.60.tgz#fcac07cb182d41929208899062de8c9510cf42ed"
|
||||
integrity sha512-QlCdhsV1+JIf0c0U6ge6SQmpwsyAT0oQaOSZk50AtEeAyQl9tQrd6qCHAslxQpgphrfe945abvKG8uYvw3hIGA==
|
||||
|
||||
"@tanstack/vue-virtual@^3.0.0-beta.60":
|
||||
version "3.0.0-beta.60"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/vue-virtual/-/vue-virtual-3.0.0-beta.60.tgz#f32c41f1b5dfacc40f8d427874947a24f71aba60"
|
||||
integrity sha512-sJdNB4IAHzM8a4rEozQlp7RjXJ/0nFf9tIaJNfJ1mCygORCmoJBBoepvkVSgzPLxJROQNNNm2sSlp+2d+R15rw==
|
||||
dependencies:
|
||||
"@tanstack/virtual-core" "3.0.0-beta.60"
|
||||
|
||||
"@testing-library/dom@^7.26.6":
|
||||
version "7.31.2"
|
||||
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz"
|
||||
@@ -1680,16 +1639,6 @@ browserslist@^4.20.3:
|
||||
node-releases "^2.0.3"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
browserslist@^4.21.10:
|
||||
version "4.21.10"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0"
|
||||
integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001517"
|
||||
electron-to-chromium "^1.4.477"
|
||||
node-releases "^2.0.13"
|
||||
update-browserslist-db "^1.0.11"
|
||||
|
||||
bser@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz"
|
||||
@@ -1760,11 +1709,6 @@ caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335:
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz"
|
||||
integrity sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==
|
||||
|
||||
caniuse-lite@^1.0.30001517:
|
||||
version "1.0.30001532"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz#c6a4d5d2da6d2b967f0ee5e12e7f680db6ad2fca"
|
||||
integrity sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz"
|
||||
@@ -2161,11 +2105,6 @@ delayed-stream@~1.0.0:
|
||||
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
detect-libc@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
|
||||
|
||||
detect-newline@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz"
|
||||
@@ -2242,11 +2181,6 @@ electron-to-chromium@^1.4.17:
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.49.tgz"
|
||||
integrity sha512-k/0t1TRfonHIp8TJKfjBu2cKj8MqYTiEpOhci+q7CVEE5xnCQnx1pTa+V8b/sdhe4S3PR4p4iceEQWhGrKQORQ==
|
||||
|
||||
electron-to-chromium@^1.4.477:
|
||||
version "1.4.513"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.513.tgz#41a50bf749aa7d8058ffbf7a131fc3327a7b1675"
|
||||
integrity sha512-cOB0xcInjm+E5qIssHeXJ29BaUyWpMyFKT5RB3bsLENDheCja0wMkHJyiPl0NBE/VzDI7JDuNEQWhe6RitEUcw==
|
||||
|
||||
emittery@^0.7.1:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz"
|
||||
@@ -2647,17 +2581,6 @@ fast-glob@^3.2.12:
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-glob@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
|
||||
integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
|
||||
@@ -3828,7 +3751,7 @@ jest@26:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^26.6.3"
|
||||
|
||||
jiti@^1.19.3:
|
||||
jiti@^1.18.2:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42"
|
||||
integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==
|
||||
@@ -3953,68 +3876,6 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lightningcss-darwin-arm64@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.21.8.tgz#b4ea8d5133236bff361623ce8c30639a1b024240"
|
||||
integrity sha512-BOMoGfcgkk2f4ltzsJqmkjiqRtlZUK+UdwhR+P6VgIsnpQBV3G01mlL6GzYxYqxq+6/3/n/D+4oy2NeknmADZw==
|
||||
|
||||
lightningcss-darwin-x64@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.21.8.tgz#81f4671cf9c245bb25a6536c01ddac76973fd283"
|
||||
integrity sha512-YhF64mcVDPKKufL4aNFBnVH7uvzE0bW3YUsPXdP4yUcT/8IXChypOZ/PE1pmt2RlbmsyVuuIIeZU4zTyZe5Amw==
|
||||
|
||||
lightningcss-freebsd-x64@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.21.8.tgz#d1b18c5a1b894e1332b23870afdbe23d07f22614"
|
||||
integrity sha512-CV6A/vTG2Ryd3YpChEgfWWv4TXCAETo9TcHSNx0IP0dnKcnDEiAko4PIKhCqZL11IGdN1ZLBCVPw+vw5ZYwzfA==
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.21.8.tgz#523366a683d3545d3a36c133079ff6af0a3d95c0"
|
||||
integrity sha512-9PMbqh8n/Xq0F4/j2NR/hHM2HRDiFXFSF0iOvV67pNWKJkHIO6mR8jBw/88Aro5Ye/ILsX5OuWsxIVJDFv0NXA==
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.21.8.tgz#6a74eff0680dd0759591962a3b92353f9b2bf49a"
|
||||
integrity sha512-JTM/TuMMllkzaXV7/eDjG4IJKLlCl+RfYZwtsVmC82gc0QX0O37csGAcY2OGleiuA4DnEo/Qea5WoFfZUNC6zg==
|
||||
|
||||
lightningcss-linux-arm64-musl@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.21.8.tgz#98c74b70d99e08efb3cc6dacd0c57d516a15c2e7"
|
||||
integrity sha512-01gWShXrgoIb8urzShpn1RWtZuaSyKSzF2hfO+flzlTPoACqcO3rgcu/3af4Cw54e8vKzL5hPRo4kROmgaOMLg==
|
||||
|
||||
lightningcss-linux-x64-gnu@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.21.8.tgz#96c691c0852eaae9b6a15d238b7bdd9fbfc3cc85"
|
||||
integrity sha512-yVB5vYJjJb/Aku0V9QaGYIntvK/1TJOlNB9GmkNpXX5bSSP2pYW4lWW97jxFMHO908M0zjEt1qyOLMyqojHL+Q==
|
||||
|
||||
lightningcss-linux-x64-musl@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.21.8.tgz#19787f71eeabdcec34af6e74509a2902548d45f9"
|
||||
integrity sha512-TYi+KNtBVK0+FZvxTX/d5XJb+tw3Jq+2Rr9hW359wp1afsi1Vkg+uVGgbn+m2dipa5XwpCseQq81ylMlXuyfPw==
|
||||
|
||||
lightningcss-win32-x64-msvc@1.21.8:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.21.8.tgz#eb10b607b464bd19c966de0065c95ff47e6acb1b"
|
||||
integrity sha512-mww+kqbPx0/C44l2LEloECtRUuOFDjq9ftp+EHTPiCp2t+avy0sh8MaFwGsrKkj2XfZhaRhi4CPVKBoqF1Qlwg==
|
||||
|
||||
lightningcss@^1.21.7:
|
||||
version "1.21.8"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.21.8.tgz#a02e4a8979208ffb61d7c6deebb75c4abce0b5d6"
|
||||
integrity sha512-jEqaL7m/ZckZJjlMAfycr1Kpz7f93k6n7KGF5SJjuPSm6DWI6h3ayLZmgRHgy1OfrwoCed6h4C/gHYPOd1OFMA==
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
optionalDependencies:
|
||||
lightningcss-darwin-arm64 "1.21.8"
|
||||
lightningcss-darwin-x64 "1.21.8"
|
||||
lightningcss-freebsd-x64 "1.21.8"
|
||||
lightningcss-linux-arm-gnueabihf "1.21.8"
|
||||
lightningcss-linux-arm64-gnu "1.21.8"
|
||||
lightningcss-linux-arm64-musl "1.21.8"
|
||||
lightningcss-linux-x64-gnu "1.21.8"
|
||||
lightningcss-linux-x64-musl "1.21.8"
|
||||
lightningcss-win32-x64-msvc "1.21.8"
|
||||
|
||||
lilconfig@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz"
|
||||
@@ -4395,11 +4256,6 @@ node-releases@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz"
|
||||
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
|
||||
|
||||
node-releases@^2.0.13:
|
||||
version "2.0.13"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
|
||||
integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
|
||||
|
||||
node-releases@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz"
|
||||
@@ -4823,14 +4679,6 @@ postcss-selector-parser@^6.0.11:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-selector-parser@^6.0.12:
|
||||
version "6.0.13"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
|
||||
integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||
@@ -4881,7 +4729,7 @@ postcss@^8.4.16:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.28:
|
||||
postcss@^8.4.23:
|
||||
version "8.4.29"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd"
|
||||
integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==
|
||||
@@ -5161,7 +5009,7 @@ resolve@^1.10.0, resolve@^1.18.1:
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
resolve@^1.22.4:
|
||||
resolve@^1.22.2:
|
||||
version "1.22.4"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34"
|
||||
integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==
|
||||
@@ -5667,7 +5515,7 @@ styled-jsx@5.0.1:
|
||||
resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.1.tgz"
|
||||
integrity sha512-+PIZ/6Uk40mphiQJJI1202b+/dYeTVd9ZnMPR80pgiWbjIwvN2zIp4r9et0BgqBuShh48I0gttPlAXA7WVvBxw==
|
||||
|
||||
sucrase@^3.34.0:
|
||||
sucrase@^3.32.0:
|
||||
version "3.34.0"
|
||||
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f"
|
||||
integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==
|
||||
@@ -5722,38 +5570,6 @@ tabbable@^6.0.1:
|
||||
resolved "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz"
|
||||
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
|
||||
|
||||
tailwindcss@^0.0.0-insiders.9faf109:
|
||||
version "0.0.0-insiders.9faf109"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-0.0.0-insiders.9faf109.tgz#d0dfe2f0a5013dae9eab576a9503878bad1d12f4"
|
||||
integrity sha512-WJ++yMXHE9TvwxU8Pl7Cw9tBQtCmtaH72XOwfGfhpI0pT9NPYzO3I+8PT+SNAwAwy/CMPYUn9Nm8UoZ8EHC3lw==
|
||||
dependencies:
|
||||
"@alloc/quick-lru" "^5.2.0"
|
||||
"@tailwindcss/oxide" "0.0.0-insiders.9faf109"
|
||||
arg "^5.0.2"
|
||||
browserslist "^4.21.10"
|
||||
chokidar "^3.5.3"
|
||||
didyoumean "^1.2.2"
|
||||
dlv "^1.1.3"
|
||||
fast-glob "^3.3.1"
|
||||
glob-parent "^6.0.2"
|
||||
is-glob "^4.0.3"
|
||||
jiti "^1.19.3"
|
||||
lightningcss "^1.21.7"
|
||||
lilconfig "^2.1.0"
|
||||
micromatch "^4.0.5"
|
||||
normalize-path "^3.0.0"
|
||||
object-hash "^3.0.0"
|
||||
picocolors "^1.0.0"
|
||||
postcss "^8.4.28"
|
||||
postcss-import "^15.1.0"
|
||||
postcss-js "^4.0.1"
|
||||
postcss-load-config "^4.0.1"
|
||||
postcss-nested "^6.0.1"
|
||||
postcss-selector-parser "^6.0.12"
|
||||
postcss-value-parser "^4.2.0"
|
||||
resolve "^1.22.4"
|
||||
sucrase "^3.34.0"
|
||||
|
||||
tailwindcss@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz"
|
||||
@@ -5783,6 +5599,34 @@ tailwindcss@^3.2.7:
|
||||
quick-lru "^5.1.1"
|
||||
resolve "^1.22.1"
|
||||
|
||||
tailwindcss@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
||||
integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==
|
||||
dependencies:
|
||||
"@alloc/quick-lru" "^5.2.0"
|
||||
arg "^5.0.2"
|
||||
chokidar "^3.5.3"
|
||||
didyoumean "^1.2.2"
|
||||
dlv "^1.1.3"
|
||||
fast-glob "^3.2.12"
|
||||
glob-parent "^6.0.2"
|
||||
is-glob "^4.0.3"
|
||||
jiti "^1.18.2"
|
||||
lilconfig "^2.1.0"
|
||||
micromatch "^4.0.5"
|
||||
normalize-path "^3.0.0"
|
||||
object-hash "^3.0.0"
|
||||
picocolors "^1.0.0"
|
||||
postcss "^8.4.23"
|
||||
postcss-import "^15.1.0"
|
||||
postcss-js "^4.0.1"
|
||||
postcss-load-config "^4.0.1"
|
||||
postcss-nested "^6.0.1"
|
||||
postcss-selector-parser "^6.0.11"
|
||||
resolve "^1.22.2"
|
||||
sucrase "^3.32.0"
|
||||
|
||||
terminal-link@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz"
|
||||
@@ -5969,14 +5813,6 @@ unset-value@^1.0.0:
|
||||
has-value "^0.3.1"
|
||||
isobject "^3.0.0"
|
||||
|
||||
update-browserslist-db@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
|
||||
integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
|
||||
dependencies:
|
||||
escalade "^3.1.1"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
urix@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
|
||||
@@ -6222,9 +6058,9 @@ yaml@^1.10.0, yaml@^1.10.2:
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
||||
yaml@^2.1.1:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144"
|
||||
integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
||||
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
||||
|
||||
yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
|
||||
Reference in New Issue
Block a user