{ "version": 3, "sources": ["../src/components/combobox/combobox.tsx", "../src/hooks/use-computed.ts", "../src/hooks/use-iso-morphic-effect.ts", "../src/utils/ssr.ts", "../src/hooks/use-latest-value.ts", "../src/hooks/use-disposables.ts", "../src/utils/micro-task.ts", "../src/utils/disposables.ts", "../src/hooks/use-event.ts", "../src/hooks/use-id.ts", "../src/hooks/use-server-handoff-complete.ts", "../src/hooks/use-outside-click.ts", "../src/utils/match.ts", "../src/utils/owner.ts", "../src/utils/focus-management.ts", "../src/hooks/use-document-event.ts", "../src/hooks/use-resolve-button-type.ts", "../src/hooks/use-sync-refs.ts", "../src/hooks/use-tree-walker.ts", "../src/utils/calculate-active-index.ts", "../src/utils/render.ts", "../src/utils/bugs.ts", "../src/utils/form.ts", "../src/internal/hidden.tsx", "../src/internal/open-closed.tsx", "../src/hooks/use-controllable.ts", "../src/hooks/use-watch.ts", "../src/hooks/use-tracked-pointer.ts", "../src/components/dialog/dialog.tsx", "../src/components/focus-trap/focus-trap.tsx", "../src/hooks/use-tab-direction.ts", "../src/hooks/use-window-event.ts", "../src/hooks/use-is-mounted.ts", "../src/hooks/use-owner.ts", "../src/hooks/use-event-listener.ts", "../src/hooks/use-inert-others.ts", "../src/components/portal/portal.tsx", "../src/internal/portal-force-root.tsx", "../src/components/description/description.tsx", "../src/internal/stack-context.tsx", "../src/utils/platform.ts", "../src/components/disclosure/disclosure.tsx", "../src/components/listbox/listbox.tsx", "../src/components/menu/menu.tsx", "../src/components/popover/popover.tsx", "../src/components/radio-group/radio-group.tsx", "../src/hooks/use-flags.ts", "../src/components/label/label.tsx", "../src/components/switch/switch.tsx", "../src/components/tabs/tabs.tsx", "../src/internal/focus-sentinel.tsx", "../src/components/transitions/transition.tsx", "../src/utils/once.ts", "../src/components/transitions/utils/transition.ts", "../src/hooks/use-transition.ts", "../src/utils/class-names.ts"], "sourcesContent": ["import React, {\n Fragment,\n createContext,\n createRef,\n useCallback,\n useContext,\n useMemo,\n useReducer,\n useRef,\n\n // Types\n ElementType,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n useEffect,\n} from 'react'\nimport { ByComparator, EnsureArray, Expand, Props } from '../../types'\n\nimport { useComputed } from '../../hooks/use-computed'\nimport { useDisposables } from '../../hooks/use-disposables'\nimport { useEvent } from '../../hooks/use-event'\nimport { useId } from '../../hooks/use-id'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { useLatestValue } from '../../hooks/use-latest-value'\nimport { useOutsideClick } from '../../hooks/use-outside-click'\nimport { useResolveButtonType } from '../../hooks/use-resolve-button-type'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useTreeWalker } from '../../hooks/use-tree-walker'\n\nimport { calculateActiveIndex, Focus } from '../../utils/calculate-active-index'\nimport { disposables } from '../../utils/disposables'\nimport { forwardRefWithAs, render, compact, PropsForFeatures, Features } from '../../utils/render'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport { match } from '../../utils/match'\nimport { objectToFormEntries } from '../../utils/form'\nimport { sortByDomNode } from '../../utils/focus-management'\n\nimport { Hidden, Features as HiddenFeatures } from '../../internal/hidden'\nimport { useOpenClosed, State, OpenClosedProvider } from '../../internal/open-closed'\n\nimport { Keys } from '../keyboard'\nimport { useControllable } from '../../hooks/use-controllable'\nimport { useWatch } from '../../hooks/use-watch'\nimport { useTrackedPointer } from '../../hooks/use-tracked-pointer'\n\nenum ComboboxState {\n Open,\n Closed,\n}\n\nenum ValueMode {\n Single,\n Multi,\n}\n\nenum ActivationTrigger {\n Pointer,\n Other,\n}\n\ntype ComboboxOptionDataRef = MutableRefObject<{\n textValue?: string\n disabled: boolean\n value: T\n domRef: MutableRefObject\n}>\n\ninterface StateDefinition {\n dataRef: MutableRefObject<_Data>\n labelId: string | null\n\n comboboxState: ComboboxState\n\n options: { id: string; dataRef: ComboboxOptionDataRef }[]\n activeOptionIndex: number | null\n activationTrigger: ActivationTrigger\n}\n\nenum ActionTypes {\n OpenCombobox,\n CloseCombobox,\n\n GoToOption,\n\n RegisterOption,\n UnregisterOption,\n\n RegisterLabel,\n}\n\nfunction adjustOrderedState(\n state: StateDefinition,\n adjustment: (options: StateDefinition['options']) => StateDefinition['options'] = (i) => i\n) {\n let currentActiveOption =\n state.activeOptionIndex !== null ? state.options[state.activeOptionIndex] : null\n\n let sortedOptions = sortByDomNode(\n adjustment(state.options.slice()),\n (option) => option.dataRef.current.domRef.current\n )\n\n // If we inserted an option before the current active option then the active option index\n // would be wrong. To fix this, we will re-lookup the correct index.\n let adjustedActiveOptionIndex = currentActiveOption\n ? sortedOptions.indexOf(currentActiveOption)\n : null\n\n // Reset to `null` in case the currentActiveOption was removed.\n if (adjustedActiveOptionIndex === -1) {\n adjustedActiveOptionIndex = null\n }\n\n return {\n options: sortedOptions,\n activeOptionIndex: adjustedActiveOptionIndex,\n }\n}\n\ntype Actions =\n | { type: ActionTypes.CloseCombobox }\n | { type: ActionTypes.OpenCombobox }\n | { type: ActionTypes.GoToOption; focus: Focus.Specific; id: string; trigger?: ActivationTrigger }\n | {\n type: ActionTypes.GoToOption\n focus: Exclude\n trigger?: ActivationTrigger\n }\n | { type: ActionTypes.RegisterOption; id: string; dataRef: ComboboxOptionDataRef }\n | { type: ActionTypes.RegisterLabel; id: string | null }\n | { type: ActionTypes.UnregisterOption; id: string }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract, { type: P }>\n ) => StateDefinition\n} = {\n [ActionTypes.CloseCombobox](state) {\n if (state.dataRef.current.disabled) return state\n if (state.comboboxState === ComboboxState.Closed) return state\n return { ...state, activeOptionIndex: null, comboboxState: ComboboxState.Closed }\n },\n [ActionTypes.OpenCombobox](state) {\n if (state.dataRef.current.disabled) return state\n if (state.comboboxState === ComboboxState.Open) return state\n\n // Check if we have a selected value that we can make active\n let activeOptionIndex = state.activeOptionIndex\n let { isSelected } = state.dataRef.current\n let optionIdx = state.options.findIndex((option) => isSelected(option.dataRef.current.value))\n\n if (optionIdx !== -1) {\n activeOptionIndex = optionIdx\n }\n\n return { ...state, comboboxState: ComboboxState.Open, activeOptionIndex }\n },\n [ActionTypes.GoToOption](state, action) {\n if (state.dataRef.current.disabled) return state\n if (\n state.dataRef.current.optionsRef.current &&\n !state.dataRef.current.optionsPropsRef.current.static &&\n state.comboboxState === ComboboxState.Closed\n ) {\n return state\n }\n\n let adjustedState = adjustOrderedState(state)\n\n // It's possible that the activeOptionIndex is set to `null` internally, but\n // this means that we will fallback to the first non-disabled option by default.\n // We have to take this into account.\n if (adjustedState.activeOptionIndex === null) {\n let localActiveOptionIndex = adjustedState.options.findIndex(\n (option) => !option.dataRef.current.disabled\n )\n\n if (localActiveOptionIndex !== -1) {\n adjustedState.activeOptionIndex = localActiveOptionIndex\n }\n }\n\n let activeOptionIndex = calculateActiveIndex(action, {\n resolveItems: () => adjustedState.options,\n resolveActiveIndex: () => adjustedState.activeOptionIndex,\n resolveId: (item) => item.id,\n resolveDisabled: (item) => item.dataRef.current.disabled,\n })\n\n return {\n ...state,\n ...adjustedState,\n activeOptionIndex,\n activationTrigger: action.trigger ?? ActivationTrigger.Other,\n }\n },\n [ActionTypes.RegisterOption]: (state, action) => {\n let option = { id: action.id, dataRef: action.dataRef }\n let adjustedState = adjustOrderedState(state, (options) => [...options, option])\n\n // Check if we need to make the newly registered option active.\n if (state.activeOptionIndex === null) {\n if (state.dataRef.current.isSelected(action.dataRef.current.value)) {\n adjustedState.activeOptionIndex = adjustedState.options.indexOf(option)\n }\n }\n\n let nextState = {\n ...state,\n ...adjustedState,\n activationTrigger: ActivationTrigger.Other,\n }\n\n if (state.dataRef.current.__demoMode && state.dataRef.current.value === undefined) {\n nextState.activeOptionIndex = 0\n }\n\n return nextState\n },\n [ActionTypes.UnregisterOption]: (state, action) => {\n let adjustedState = adjustOrderedState(state, (options) => {\n let idx = options.findIndex((a) => a.id === action.id)\n if (idx !== -1) options.splice(idx, 1)\n return options\n })\n\n return {\n ...state,\n ...adjustedState,\n activationTrigger: ActivationTrigger.Other,\n }\n },\n [ActionTypes.RegisterLabel]: (state, action) => {\n return {\n ...state,\n labelId: action.id,\n }\n },\n}\n\nlet ComboboxActionsContext = createContext<{\n openCombobox(): void\n closeCombobox(): void\n registerOption(id: string, dataRef: ComboboxOptionDataRef): () => void\n registerLabel(id: string): () => void\n goToOption(focus: Focus.Specific, id: string, trigger?: ActivationTrigger): void\n goToOption(focus: Focus, id?: string, trigger?: ActivationTrigger): void\n selectOption(id: string): void\n selectActiveOption(): void\n onChange(value: unknown): void\n} | null>(null)\nComboboxActionsContext.displayName = 'ComboboxActionsContext'\n\nfunction useActions(component: string) {\n let context = useContext(ComboboxActionsContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useActions)\n throw err\n }\n return context\n}\ntype _Actions = ReturnType\n\nlet ComboboxDataContext = createContext<\n | ({\n value: unknown\n defaultValue: unknown\n disabled: boolean\n mode: ValueMode\n activeOptionIndex: number | null\n nullable: boolean\n compare(a: unknown, z: unknown): boolean\n isSelected(value: unknown): boolean\n __demoMode: boolean\n\n optionsPropsRef: MutableRefObject<{\n static: boolean\n hold: boolean\n }>\n\n labelRef: MutableRefObject\n inputRef: MutableRefObject\n buttonRef: MutableRefObject\n optionsRef: MutableRefObject\n } & Omit, 'dataRef'>)\n | null\n>(null)\nComboboxDataContext.displayName = 'ComboboxDataContext'\n\nfunction useData(component: string) {\n let context = useContext(ComboboxDataContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useData)\n throw err\n }\n return context\n}\ntype _Data = ReturnType\n\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_COMBOBOX_TAG = Fragment\ninterface ComboboxRenderPropArg {\n open: boolean\n disabled: boolean\n activeIndex: number | null\n activeOption: T | null\n value: T\n}\n\ntype O = 'value' | 'defaultValue' | 'nullable' | 'multiple' | 'onChange' | 'by'\n\ntype ComboboxValueProps<\n TValue,\n TNullable extends boolean | undefined,\n TMultiple extends boolean | undefined,\n TTag extends ElementType\n> = Extract<\n | ({\n value?: EnsureArray\n defaultValue?: EnsureArray\n nullable: true // We ignore `nullable` in multiple mode\n multiple: true\n onChange?(value: EnsureArray): void\n by?: ByComparator\n } & Props>, O>)\n | ({\n value?: TValue | null\n defaultValue?: TValue | null\n nullable: true\n multiple?: false\n onChange?(value: TValue | null): void\n by?: ByComparator\n } & Expand, O>>)\n | ({\n value?: EnsureArray\n defaultValue?: EnsureArray\n nullable?: false\n multiple: true\n onChange?(value: EnsureArray): void\n by?: ByComparator ? U : TValue>\n } & Expand>, O>>)\n | ({\n value?: TValue\n nullable?: false\n multiple?: false\n defaultValue?: TValue\n onChange?(value: TValue): void\n by?: ByComparator\n } & Props, O>),\n { nullable?: TNullable; multiple?: TMultiple }\n>\n\ntype ComboboxProps<\n TValue,\n TNullable extends boolean | undefined,\n TMultiple extends boolean | undefined,\n TTag extends ElementType\n> = ComboboxValueProps & {\n disabled?: boolean\n __demoMode?: boolean\n name?: string\n}\n\nfunction ComboboxFn(\n props: ComboboxProps,\n ref: Ref\n): JSX.Element\nfunction ComboboxFn(\n props: ComboboxProps,\n ref: Ref\n): JSX.Element\nfunction ComboboxFn(\n props: ComboboxProps,\n ref: Ref\n): JSX.Element\nfunction ComboboxFn(\n props: ComboboxProps,\n ref: Ref\n): JSX.Element\n\nfunction ComboboxFn(\n props: ComboboxProps,\n ref: Ref\n) {\n let {\n value: controlledValue,\n defaultValue,\n onChange: controlledOnChange,\n name,\n by = (a: any, z: any) => a === z,\n disabled = false,\n __demoMode = false,\n nullable = false,\n multiple = false,\n ...theirProps\n } = props\n let [value = multiple ? [] : undefined, theirOnChange] = useControllable(\n controlledValue,\n controlledOnChange,\n defaultValue\n )\n\n let [state, dispatch] = useReducer(stateReducer, {\n dataRef: createRef(),\n comboboxState: __demoMode ? ComboboxState.Open : ComboboxState.Closed,\n options: [],\n activeOptionIndex: null,\n activationTrigger: ActivationTrigger.Other,\n labelId: null,\n } as StateDefinition)\n\n let defaultToFirstOption = useRef(false)\n\n let optionsPropsRef = useRef<_Data['optionsPropsRef']['current']>({ static: false, hold: false })\n\n let labelRef = useRef<_Data['labelRef']['current']>(null)\n let inputRef = useRef<_Data['inputRef']['current']>(null)\n let buttonRef = useRef<_Data['buttonRef']['current']>(null)\n let optionsRef = useRef<_Data['optionsRef']['current']>(null)\n\n let compare = useEvent(\n typeof by === 'string'\n ? (a, z) => {\n let property = by as unknown as keyof TValue\n return a?.[property] === z?.[property]\n }\n : by\n )\n\n let isSelected: (value: unknown) => boolean = useCallback(\n (compareValue) =>\n match(data.mode, {\n [ValueMode.Multi]: () =>\n (value as EnsureArray).some((option) => compare(option, compareValue)),\n [ValueMode.Single]: () => compare(value as TValue, compareValue),\n }),\n [value]\n )\n\n let data = useMemo<_Data>(\n () => ({\n ...state,\n optionsPropsRef,\n labelRef,\n inputRef,\n buttonRef,\n optionsRef,\n value,\n defaultValue,\n disabled,\n mode: multiple ? ValueMode.Multi : ValueMode.Single,\n get activeOptionIndex() {\n if (\n defaultToFirstOption.current &&\n state.activeOptionIndex === null &&\n state.options.length > 0\n ) {\n let localActiveOptionIndex = state.options.findIndex(\n (option) => !option.dataRef.current.disabled\n )\n\n if (localActiveOptionIndex !== -1) {\n return localActiveOptionIndex\n }\n }\n\n return state.activeOptionIndex\n },\n compare,\n isSelected,\n nullable,\n __demoMode,\n }),\n [value, defaultValue, disabled, multiple, nullable, __demoMode, state]\n )\n\n useIsoMorphicEffect(() => {\n state.dataRef.current = data\n }, [data])\n\n // Handle outside click\n useOutsideClick(\n [data.buttonRef, data.inputRef, data.optionsRef],\n () => actions.closeCombobox(),\n data.comboboxState === ComboboxState.Open\n )\n\n let slot = useMemo>(\n () => ({\n open: data.comboboxState === ComboboxState.Open,\n disabled,\n activeIndex: data.activeOptionIndex,\n activeOption:\n data.activeOptionIndex === null\n ? null\n : (data.options[data.activeOptionIndex].dataRef.current.value as TValue),\n value,\n }),\n [data, disabled, value]\n )\n\n let selectOption = useEvent((id: string) => {\n let option = data.options.find((item) => item.id === id)\n if (!option) return\n\n onChange(option.dataRef.current.value)\n })\n\n let selectActiveOption = useEvent(() => {\n if (data.activeOptionIndex !== null) {\n let { dataRef, id } = data.options[data.activeOptionIndex]\n onChange(dataRef.current.value)\n\n // It could happen that the `activeOptionIndex` stored in state is actually null,\n // but we are getting the fallback active option back instead.\n actions.goToOption(Focus.Specific, id)\n }\n })\n\n let openCombobox = useEvent(() => {\n dispatch({ type: ActionTypes.OpenCombobox })\n defaultToFirstOption.current = true\n })\n\n let closeCombobox = useEvent(() => {\n dispatch({ type: ActionTypes.CloseCombobox })\n defaultToFirstOption.current = false\n })\n\n let goToOption = useEvent((focus, id, trigger) => {\n defaultToFirstOption.current = false\n\n if (focus === Focus.Specific) {\n return dispatch({ type: ActionTypes.GoToOption, focus: Focus.Specific, id: id!, trigger })\n }\n\n return dispatch({ type: ActionTypes.GoToOption, focus, trigger })\n })\n\n let registerOption = useEvent((id, dataRef) => {\n dispatch({ type: ActionTypes.RegisterOption, id, dataRef })\n return () => dispatch({ type: ActionTypes.UnregisterOption, id })\n })\n\n let registerLabel = useEvent((id) => {\n dispatch({ type: ActionTypes.RegisterLabel, id })\n return () => dispatch({ type: ActionTypes.RegisterLabel, id: null })\n })\n\n let onChange = useEvent((value: unknown) => {\n return match(data.mode, {\n [ValueMode.Single]() {\n return theirOnChange?.(value as TValue)\n },\n [ValueMode.Multi]() {\n let copy = (data.value as TValue[]).slice()\n\n let idx = copy.findIndex((item) => compare(item, value as TValue))\n if (idx === -1) {\n copy.push(value as TValue)\n } else {\n copy.splice(idx, 1)\n }\n\n return theirOnChange?.(copy as unknown as TValue[])\n },\n })\n })\n\n let actions = useMemo<_Actions>(\n () => ({\n onChange,\n registerOption,\n registerLabel,\n goToOption,\n closeCombobox,\n openCombobox,\n selectActiveOption,\n selectOption,\n }),\n []\n )\n\n let ourProps = ref === null ? {} : { ref }\n\n let form = useRef(null)\n let d = useDisposables()\n useEffect(() => {\n if (!form.current) return\n if (defaultValue === undefined) return\n\n d.addEventListener(form.current, 'reset', () => {\n onChange(defaultValue)\n })\n }, [form, onChange /* Explicitly ignoring `defaultValue` */])\n\n return (\n \n \n \n {name != null &&\n value != null &&\n objectToFormEntries({ [name]: value }).map(([name, value], idx) => (\n {\n form.current = element?.closest('form') ?? null\n }\n : undefined\n }\n {...compact({\n key: name,\n as: 'input',\n type: 'hidden',\n hidden: true,\n readOnly: true,\n name,\n value,\n })}\n />\n ))}\n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_COMBOBOX_TAG,\n name: 'Combobox',\n })}\n \n \n \n )\n}\nlet ComboboxRoot = forwardRefWithAs(ComboboxFn)\n\n// ---\n\nlet DEFAULT_INPUT_TAG = 'input' as const\ninterface InputRenderPropArg {\n open: boolean\n disabled: boolean\n}\ntype InputPropsWeControl =\n | 'role'\n | 'aria-labelledby'\n | 'aria-expanded'\n | 'aria-activedescendant'\n | 'onKeyDown'\n | 'onChange'\n | 'displayValue'\n\nlet Input = forwardRefWithAs(function Input<\n TTag extends ElementType = typeof DEFAULT_INPUT_TAG,\n // TODO: One day we will be able to infer this type from the generic in Combobox itself.\n // But today is not that day..\n TType = Parameters[0]['value']\n>(\n props: Props & {\n displayValue?(item: TType): string\n onChange(event: React.ChangeEvent): void\n },\n ref: Ref\n) {\n let internalId = useId()\n let {\n id = `headlessui-combobox-input-${internalId}`,\n onChange,\n displayValue,\n type = 'text',\n ...theirProps\n } = props\n let data = useData('Combobox.Input')\n let actions = useActions('Combobox.Input')\n\n let inputRef = useSyncRefs(data.inputRef, ref)\n\n let isTyping = useRef(false)\n\n let d = useDisposables()\n\n // When a `displayValue` prop is given, we should use it to transform the current selected\n // option(s) so that the format can be chosen by developers implementing this. This is useful if\n // your data is an object and you just want to pick a certain property or want to create a dynamic\n // value like `firstName + ' ' + lastName`.\n //\n // Note: This can also be used with multiple selected options, but this is a very simple transform\n // which should always result in a string (since we are filling in the value of the text input),\n // you don't have to use this at all, a more common UI is a \"tag\" based UI, which you can render\n // yourself using the selected option(s).\n let currentDisplayValue = (function () {\n if (typeof displayValue === 'function' && data.value !== undefined) {\n return displayValue(data.value as unknown as TType) ?? ''\n } else if (typeof data.value === 'string') {\n return data.value\n } else {\n return ''\n }\n })()\n\n // Syncing the input value has some rules attached to it to guarantee a smooth and expected user\n // experience:\n //\n // - When a user is not typing in the input field, it is safe to update the input value based on\n // the selected option(s). See `currentDisplayValue` computation from above.\n // - The value can be updated when:\n // - The `value` is set from outside of the component\n // - The `value` is set when the user uses their keyboard (confirm via enter or space)\n // - The `value` is set when the user clicks on a value to select it\n // - The value will be reset to the current selected option(s), when:\n // - The user is _not_ typing (otherwise you will loose your current state / query)\n // - The user cancels the current changes:\n // - By pressing `escape`\n // - By clicking `outside` of the Combobox\n useWatch(\n ([currentDisplayValue, state], [oldCurrentDisplayValue, oldState]) => {\n if (isTyping.current) return\n if (!data.inputRef.current) return\n if (oldState === ComboboxState.Open && state === ComboboxState.Closed) {\n data.inputRef.current.value = currentDisplayValue\n } else if (currentDisplayValue !== oldCurrentDisplayValue) {\n data.inputRef.current.value = currentDisplayValue\n }\n },\n [currentDisplayValue, data.comboboxState]\n )\n\n let isComposing = useRef(false)\n let handleCompositionStart = useEvent(() => {\n isComposing.current = true\n })\n let handleCompositionEnd = useEvent(() => {\n setTimeout(() => {\n isComposing.current = false\n })\n })\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n isTyping.current = true\n switch (event.key) {\n // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-12\n\n case Keys.Backspace:\n case Keys.Delete:\n if (data.mode !== ValueMode.Single) return\n if (!data.nullable) return\n\n let input = event.currentTarget\n d.requestAnimationFrame(() => {\n if (input.value === '') {\n actions.onChange(null)\n if (data.optionsRef.current) {\n data.optionsRef.current.scrollTop = 0\n }\n actions.goToOption(Focus.Nothing)\n }\n })\n break\n\n case Keys.Enter:\n isTyping.current = false\n if (data.comboboxState !== ComboboxState.Open) return\n if (isComposing.current) return\n\n event.preventDefault()\n event.stopPropagation()\n\n if (data.activeOptionIndex === null) {\n actions.closeCombobox()\n return\n }\n\n actions.selectActiveOption()\n if (data.mode === ValueMode.Single) {\n actions.closeCombobox()\n }\n break\n\n case Keys.ArrowDown:\n isTyping.current = false\n event.preventDefault()\n event.stopPropagation()\n return match(data.comboboxState, {\n [ComboboxState.Open]: () => {\n actions.goToOption(Focus.Next)\n },\n [ComboboxState.Closed]: () => {\n actions.openCombobox()\n },\n })\n\n case Keys.ArrowUp:\n isTyping.current = false\n event.preventDefault()\n event.stopPropagation()\n return match(data.comboboxState, {\n [ComboboxState.Open]: () => {\n actions.goToOption(Focus.Previous)\n },\n [ComboboxState.Closed]: () => {\n actions.openCombobox()\n d.nextFrame(() => {\n if (!data.value) {\n actions.goToOption(Focus.Last)\n }\n })\n },\n })\n\n case Keys.Home:\n if (event.shiftKey) {\n break\n }\n\n isTyping.current = false\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.First)\n\n case Keys.PageUp:\n isTyping.current = false\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.First)\n\n case Keys.End:\n if (event.shiftKey) {\n break\n }\n\n isTyping.current = false\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.Last)\n\n case Keys.PageDown:\n isTyping.current = false\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.Last)\n\n case Keys.Escape:\n isTyping.current = false\n if (data.comboboxState !== ComboboxState.Open) return\n event.preventDefault()\n if (data.optionsRef.current && !data.optionsPropsRef.current.static) {\n event.stopPropagation()\n }\n return actions.closeCombobox()\n\n case Keys.Tab:\n isTyping.current = false\n if (data.comboboxState !== ComboboxState.Open) return\n if (data.mode === ValueMode.Single) actions.selectActiveOption()\n actions.closeCombobox()\n break\n }\n })\n\n let handleChange = useEvent((event: React.ChangeEvent) => {\n actions.openCombobox()\n onChange?.(event)\n })\n\n let handleBlur = useEvent(() => {\n isTyping.current = false\n })\n\n // TODO: Verify this. The spec says that, for the input/combobox, the label is the labelling element when present\n // Otherwise it's the ID of the non-label element\n let labelledby = useComputed(() => {\n if (!data.labelId) return undefined\n return [data.labelId].join(' ')\n }, [data.labelId])\n\n let slot = useMemo(\n () => ({ open: data.comboboxState === ComboboxState.Open, disabled: data.disabled }),\n [data]\n )\n\n let ourProps = {\n ref: inputRef,\n id,\n role: 'combobox',\n type,\n 'aria-controls': data.optionsRef.current?.id,\n 'aria-expanded': data.disabled ? undefined : data.comboboxState === ComboboxState.Open,\n 'aria-activedescendant':\n data.activeOptionIndex === null ? undefined : data.options[data.activeOptionIndex]?.id,\n 'aria-multiselectable': data.mode === ValueMode.Multi ? true : undefined,\n 'aria-labelledby': labelledby,\n defaultValue:\n props.defaultValue ??\n (data.defaultValue !== undefined\n ? displayValue?.(data.defaultValue as unknown as TType)\n : null) ??\n data.defaultValue,\n disabled: data.disabled,\n onCompositionStart: handleCompositionStart,\n onCompositionEnd: handleCompositionEnd,\n onKeyDown: handleKeyDown,\n onChange: handleChange,\n onBlur: handleBlur,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_INPUT_TAG,\n name: 'Combobox.Input',\n })\n})\n\n// ---\n\nlet DEFAULT_BUTTON_TAG = 'button' as const\ninterface ButtonRenderPropArg {\n open: boolean\n disabled: boolean\n value: any\n}\ntype ButtonPropsWeControl =\n | 'type'\n | 'tabIndex'\n | 'aria-haspopup'\n | 'aria-controls'\n | 'aria-expanded'\n | 'aria-labelledby'\n | 'disabled'\n | 'onClick'\n | 'onKeyDown'\n\nlet Button = forwardRefWithAs(function Button(\n props: Props,\n ref: Ref\n) {\n let data = useData('Combobox.Button')\n let actions = useActions('Combobox.Button')\n let buttonRef = useSyncRefs(data.buttonRef, ref)\n let internalId = useId()\n let { id = `headlessui-combobox-button-${internalId}`, ...theirProps } = props\n let d = useDisposables()\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-12\n\n case Keys.ArrowDown:\n event.preventDefault()\n event.stopPropagation()\n if (data.comboboxState === ComboboxState.Closed) {\n actions.openCombobox()\n }\n return d.nextFrame(() => data.inputRef.current?.focus({ preventScroll: true }))\n\n case Keys.ArrowUp:\n event.preventDefault()\n event.stopPropagation()\n if (data.comboboxState === ComboboxState.Closed) {\n actions.openCombobox()\n d.nextFrame(() => {\n if (!data.value) {\n actions.goToOption(Focus.Last)\n }\n })\n }\n return d.nextFrame(() => data.inputRef.current?.focus({ preventScroll: true }))\n\n case Keys.Escape:\n if (data.comboboxState !== ComboboxState.Open) return\n event.preventDefault()\n if (data.optionsRef.current && !data.optionsPropsRef.current.static) {\n event.stopPropagation()\n }\n actions.closeCombobox()\n return d.nextFrame(() => data.inputRef.current?.focus({ preventScroll: true }))\n\n default:\n return\n }\n })\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n if (data.comboboxState === ComboboxState.Open) {\n actions.closeCombobox()\n } else {\n event.preventDefault()\n actions.openCombobox()\n }\n\n d.nextFrame(() => data.inputRef.current?.focus({ preventScroll: true }))\n })\n\n let labelledby = useComputed(() => {\n if (!data.labelId) return undefined\n return [data.labelId, id].join(' ')\n }, [data.labelId, id])\n\n let slot = useMemo(\n () => ({\n open: data.comboboxState === ComboboxState.Open,\n disabled: data.disabled,\n value: data.value,\n }),\n [data]\n )\n let ourProps = {\n ref: buttonRef,\n id,\n type: useResolveButtonType(props, data.buttonRef),\n tabIndex: -1,\n 'aria-haspopup': 'listbox',\n 'aria-controls': data.optionsRef.current?.id,\n 'aria-expanded': data.disabled ? undefined : data.comboboxState === ComboboxState.Open,\n 'aria-labelledby': labelledby,\n disabled: data.disabled,\n onClick: handleClick,\n onKeyDown: handleKeyDown,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_BUTTON_TAG,\n name: 'Combobox.Button',\n })\n})\n\n// ---\n\nlet DEFAULT_LABEL_TAG = 'label' as const\ninterface LabelRenderPropArg {\n open: boolean\n disabled: boolean\n}\ntype LabelPropsWeControl = 'ref' | 'onClick'\n\nlet Label = forwardRefWithAs(function Label(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-combobox-label-${internalId}`, ...theirProps } = props\n let data = useData('Combobox.Label')\n let actions = useActions('Combobox.Label')\n let labelRef = useSyncRefs(data.labelRef, ref)\n\n useIsoMorphicEffect(() => actions.registerLabel(id), [id])\n\n let handleClick = useEvent(() => data.inputRef.current?.focus({ preventScroll: true }))\n\n let slot = useMemo(\n () => ({ open: data.comboboxState === ComboboxState.Open, disabled: data.disabled }),\n [data]\n )\n\n let ourProps = { ref: labelRef, id, onClick: handleClick }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_LABEL_TAG,\n name: 'Combobox.Label',\n })\n})\n\n// ---\n\nlet DEFAULT_OPTIONS_TAG = 'ul' as const\ninterface OptionsRenderPropArg {\n open: boolean\n}\ntype OptionsPropsWeControl =\n | 'aria-activedescendant'\n | 'aria-labelledby'\n | 'hold'\n | 'onKeyDown'\n | 'role'\n | 'tabIndex'\n\nlet OptionsRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet Options = forwardRefWithAs(function Options<\n TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG\n>(\n props: Props &\n PropsForFeatures & {\n hold?: boolean\n },\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-combobox-options-${internalId}`, hold = false, ...theirProps } = props\n let data = useData('Combobox.Options')\n\n let optionsRef = useSyncRefs(data.optionsRef, ref)\n\n let usesOpenClosedState = useOpenClosed()\n let visible = (() => {\n if (usesOpenClosedState !== null) {\n return usesOpenClosedState === State.Open\n }\n\n return data.comboboxState === ComboboxState.Open\n })()\n\n useIsoMorphicEffect(() => {\n data.optionsPropsRef.current.static = props.static ?? false\n }, [data.optionsPropsRef, props.static])\n useIsoMorphicEffect(() => {\n data.optionsPropsRef.current.hold = hold\n }, [data.optionsPropsRef, hold])\n\n useTreeWalker({\n container: data.optionsRef.current,\n enabled: data.comboboxState === ComboboxState.Open,\n accept(node) {\n if (node.getAttribute('role') === 'option') return NodeFilter.FILTER_REJECT\n if (node.hasAttribute('role')) return NodeFilter.FILTER_SKIP\n return NodeFilter.FILTER_ACCEPT\n },\n walk(node) {\n node.setAttribute('role', 'none')\n },\n })\n\n let labelledby = useComputed(\n () => data.labelId ?? data.buttonRef.current?.id,\n [data.labelId, data.buttonRef.current]\n )\n\n let slot = useMemo(\n () => ({ open: data.comboboxState === ComboboxState.Open }),\n [data]\n )\n let ourProps = {\n 'aria-activedescendant':\n data.activeOptionIndex === null ? undefined : data.options[data.activeOptionIndex]?.id,\n 'aria-labelledby': labelledby,\n role: 'listbox',\n id,\n ref: optionsRef,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OPTIONS_TAG,\n features: OptionsRenderFeatures,\n visible,\n name: 'Combobox.Options',\n })\n})\n\n// ---\n\nlet DEFAULT_OPTION_TAG = 'li' as const\ninterface OptionRenderPropArg {\n active: boolean\n selected: boolean\n disabled: boolean\n}\ntype ComboboxOptionPropsWeControl = 'role' | 'tabIndex' | 'aria-disabled' | 'aria-selected'\n\nlet Option = forwardRefWithAs(function Option<\n TTag extends ElementType = typeof DEFAULT_OPTION_TAG,\n // TODO: One day we will be able to infer this type from the generic in Combobox itself.\n // But today is not that day..\n TType = Parameters[0]['value']\n>(\n props: Props & {\n disabled?: boolean\n value: TType\n },\n ref: Ref\n) {\n let internalId = useId()\n let {\n id = `headlessui-combobox-option-${internalId}`,\n disabled = false,\n value,\n ...theirProps\n } = props\n let data = useData('Combobox.Option')\n let actions = useActions('Combobox.Option')\n\n let active =\n data.activeOptionIndex !== null ? data.options[data.activeOptionIndex].id === id : false\n\n let selected = data.isSelected(value)\n let internalOptionRef = useRef(null)\n let bag = useLatestValue['current']>({\n disabled,\n value,\n domRef: internalOptionRef,\n textValue: internalOptionRef.current?.textContent?.toLowerCase(),\n })\n let optionRef = useSyncRefs(ref, internalOptionRef)\n\n let select = useEvent(() => actions.selectOption(id))\n useIsoMorphicEffect(() => actions.registerOption(id, bag), [bag, id])\n\n let enableScrollIntoView = useRef(data.__demoMode ? false : true)\n useIsoMorphicEffect(() => {\n if (!data.__demoMode) return\n let d = disposables()\n d.requestAnimationFrame(() => {\n enableScrollIntoView.current = true\n })\n return d.dispose\n }, [])\n\n useIsoMorphicEffect(() => {\n if (data.comboboxState !== ComboboxState.Open) return\n if (!active) return\n if (!enableScrollIntoView.current) return\n if (data.activationTrigger === ActivationTrigger.Pointer) return\n let d = disposables()\n d.requestAnimationFrame(() => {\n internalOptionRef.current?.scrollIntoView?.({ block: 'nearest' })\n })\n return d.dispose\n }, [internalOptionRef, active, data.comboboxState, data.activationTrigger, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ data.activeOptionIndex])\n\n let handleClick = useEvent((event: { preventDefault: Function }) => {\n if (disabled) return event.preventDefault()\n select()\n if (data.mode === ValueMode.Single) {\n actions.closeCombobox()\n }\n })\n\n let handleFocus = useEvent(() => {\n if (disabled) return actions.goToOption(Focus.Nothing)\n actions.goToOption(Focus.Specific, id)\n })\n\n let pointer = useTrackedPointer()\n\n let handleEnter = useEvent((evt) => pointer.update(evt))\n\n let handleMove = useEvent((evt) => {\n if (!pointer.wasMoved(evt)) return\n if (disabled) return\n if (active) return\n actions.goToOption(Focus.Specific, id, ActivationTrigger.Pointer)\n })\n\n let handleLeave = useEvent((evt) => {\n if (!pointer.wasMoved(evt)) return\n if (disabled) return\n if (!active) return\n if (data.optionsPropsRef.current.hold) return\n actions.goToOption(Focus.Nothing)\n })\n\n let slot = useMemo(\n () => ({ active, selected, disabled }),\n [active, selected, disabled]\n )\n\n let ourProps = {\n id,\n ref: optionRef,\n role: 'option',\n tabIndex: disabled === true ? undefined : -1,\n 'aria-disabled': disabled === true ? true : undefined,\n // According to the WAI-ARIA best practices, we should use aria-checked for\n // multi-select,but Voice-Over disagrees. So we use aria-checked instead for\n // both single and multi-select.\n 'aria-selected': selected,\n disabled: undefined, // Never forward the `disabled` prop\n onClick: handleClick,\n onFocus: handleFocus,\n onPointerEnter: handleEnter,\n onMouseEnter: handleEnter,\n onPointerMove: handleMove,\n onMouseMove: handleMove,\n onPointerLeave: handleLeave,\n onMouseLeave: handleLeave,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OPTION_TAG,\n name: 'Combobox.Option',\n })\n})\n\n// ---\n\nexport let Combobox = Object.assign(ComboboxRoot, { Input, Button, Label, Options, Option })\n", "import { useState } from 'react'\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\nimport { useLatestValue } from './use-latest-value'\n\nexport function useComputed(cb: () => T, dependencies: React.DependencyList) {\n let [value, setValue] = useState(cb)\n let cbRef = useLatestValue(cb)\n useIsoMorphicEffect(() => setValue(cbRef.current), [cbRef, setValue, ...dependencies])\n return value\n}\n", "import { useLayoutEffect, useEffect } from 'react'\nimport { isServer } from '../utils/ssr'\n\nexport let useIsoMorphicEffect = isServer ? useEffect : useLayoutEffect\n", "export const isServer = typeof window === 'undefined' || typeof document === 'undefined'\n", "import { useRef } from 'react'\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\n\nexport function useLatestValue(value: T) {\n let cache = useRef(value)\n\n useIsoMorphicEffect(() => {\n cache.current = value\n }, [value])\n\n return cache\n}\n", "import { useState, useEffect } from 'react'\n\nimport { disposables } from '../utils/disposables'\n\nexport function useDisposables() {\n // Using useState instead of useRef so that we can use the initializer function.\n let [d] = useState(disposables)\n useEffect(() => () => d.dispose(), [d])\n return d\n}\n", "// Polyfill\nexport function microTask(cb: () => void) {\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(cb)\n } else {\n Promise.resolve()\n .then(cb)\n .catch((e) =>\n setTimeout(() => {\n throw e\n })\n )\n }\n}\n", "import { microTask } from './micro-task'\n\nexport function disposables() {\n let disposables: Function[] = []\n let queue: Function[] = []\n\n let api = {\n enqueue(fn: Function) {\n queue.push(fn)\n },\n\n addEventListener(\n element: HTMLElement | Window | Document,\n name: TEventName,\n listener: (event: WindowEventMap[TEventName]) => any,\n options?: boolean | AddEventListenerOptions\n ) {\n element.addEventListener(name, listener as any, options)\n return api.add(() => element.removeEventListener(name, listener as any, options))\n },\n\n requestAnimationFrame(...args: Parameters) {\n let raf = requestAnimationFrame(...args)\n return api.add(() => cancelAnimationFrame(raf))\n },\n\n nextFrame(...args: Parameters) {\n return api.requestAnimationFrame(() => {\n return api.requestAnimationFrame(...args)\n })\n },\n\n setTimeout(...args: Parameters) {\n let timer = setTimeout(...args)\n return api.add(() => clearTimeout(timer))\n },\n\n microTask(...args: Parameters) {\n let task = { current: true }\n microTask(() => {\n if (task.current) {\n args[0]()\n }\n })\n return api.add(() => {\n task.current = false\n })\n },\n\n add(cb: () => void) {\n disposables.push(cb)\n return () => {\n let idx = disposables.indexOf(cb)\n if (idx >= 0) {\n let [dispose] = disposables.splice(idx, 1)\n dispose()\n }\n }\n },\n\n dispose() {\n for (let dispose of disposables.splice(0)) {\n dispose()\n }\n },\n\n async workQueue() {\n for (let handle of queue.splice(0)) {\n await handle()\n }\n },\n }\n\n return api\n}\n", "import React from 'react'\nimport { useLatestValue } from './use-latest-value'\n\nexport let useEvent =\n // TODO: Add React.useEvent ?? once the useEvent hook is available\n function useEvent<\n F extends (...args: any[]) => any,\n P extends any[] = Parameters,\n R = ReturnType\n >(cb: (...args: P) => R) {\n let cache = useLatestValue(cb)\n return React.useCallback((...args: P) => cache.current(...args), [cache])\n }\n", "import React from 'react'\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\nimport { useServerHandoffComplete } from './use-server-handoff-complete'\n\n// We used a \"simple\" approach first which worked for SSR and rehydration on the client. However we\n// didn't take care of the Suspense case. To fix this we used the approach the @reach-ui/auto-id\n// uses.\n//\n// Credits: https://github.com/reach/reach-ui/blob/develop/packages/auto-id/src/index.tsx\n\nlet id = 0\nfunction generateId() {\n return ++id\n}\n\nexport let useId =\n // Prefer React's `useId` if it's available.\n // @ts-expect-error - `useId` doesn't exist in React < 18.\n React.useId ??\n function useId() {\n let ready = useServerHandoffComplete()\n let [id, setId] = React.useState(ready ? generateId : null)\n\n useIsoMorphicEffect(() => {\n if (id === null) setId(generateId())\n }, [id])\n\n return id != null ? '' + id : undefined\n }\n", "import { useState, useEffect } from 'react'\n\nlet state = { serverHandoffComplete: false }\n\nexport function useServerHandoffComplete() {\n let [serverHandoffComplete, setServerHandoffComplete] = useState(state.serverHandoffComplete)\n\n useEffect(() => {\n if (serverHandoffComplete === true) return\n\n setServerHandoffComplete(true)\n }, [serverHandoffComplete])\n\n useEffect(() => {\n if (state.serverHandoffComplete === false) state.serverHandoffComplete = true\n }, [])\n\n return serverHandoffComplete\n}\n", "import { MutableRefObject, useEffect, useRef } from 'react'\nimport { FocusableMode, isFocusableElement } from '../utils/focus-management'\nimport { useDocumentEvent } from './use-document-event'\n\ntype Container = MutableRefObject | HTMLElement | null\ntype ContainerCollection = Container[] | Set\ntype ContainerInput = Container | ContainerCollection\n\nexport function useOutsideClick(\n containers: ContainerInput | (() => ContainerInput),\n cb: (event: MouseEvent | PointerEvent | FocusEvent, target: HTMLElement) => void,\n enabled: boolean = true\n) {\n // TODO: remove this once the React bug has been fixed: https://github.com/facebook/react/issues/24657\n let enabledRef = useRef(false)\n useEffect(\n process.env.NODE_ENV === 'test'\n ? () => {\n enabledRef.current = enabled\n }\n : () => {\n requestAnimationFrame(() => {\n enabledRef.current = enabled\n })\n },\n [enabled]\n )\n\n function handleOutsideClick(\n event: E,\n resolveTarget: (event: E) => HTMLElement | null\n ) {\n if (!enabledRef.current) return\n\n // Check whether the event got prevented already. This can happen if you use the\n // useOutsideClick hook in both a Dialog and a Menu and the inner Menu \"cancels\" the default\n // behaviour so that only the Menu closes and not the Dialog (yet)\n if (event.defaultPrevented) return\n\n let _containers = (function resolve(containers): ContainerCollection {\n if (typeof containers === 'function') {\n return resolve(containers())\n }\n\n if (Array.isArray(containers)) {\n return containers\n }\n\n if (containers instanceof Set) {\n return containers\n }\n\n return [containers]\n })(containers)\n\n let target = resolveTarget(event)\n\n if (target === null) {\n return\n }\n\n // Ignore if the target doesn't exist in the DOM anymore\n if (!target.getRootNode().contains(target)) return\n\n // Ignore if the target exists in one of the containers\n for (let container of _containers) {\n if (container === null) continue\n let domNode = container instanceof HTMLElement ? container : container.current\n if (domNode?.contains(target)) {\n return\n }\n\n // If the click crossed a shadow boundary, we need to check if the container\n // is inside the tree by using `composedPath` to \"pierce\" the shadow boundary\n if (event.composed && event.composedPath().includes(domNode as EventTarget)) {\n return\n }\n }\n\n // This allows us to check whether the event was defaultPrevented when you are nesting this\n // inside a `` for example.\n if (\n // This check alllows us to know whether or not we clicked on a \"focusable\" element like a\n // button or an input. This is a backwards compatibility check so that you can open a and click on another which should close Menu A and open Menu B. We might\n // revisit that so that you will require 2 clicks instead.\n !isFocusableElement(target, FocusableMode.Loose) &&\n // This could be improved, but the `Combobox.Button` adds tabIndex={-1} to make it\n // unfocusable via the keyboard so that tabbing to the next item from the input doesn't\n // first go to the button.\n target.tabIndex !== -1\n ) {\n event.preventDefault()\n }\n\n return cb(event, target)\n }\n\n let initialClickTarget = useRef(null)\n\n useDocumentEvent(\n 'mousedown',\n (event) => {\n if (enabledRef.current) {\n initialClickTarget.current = event.composedPath?.()?.[0] || event.target\n }\n },\n true\n )\n\n useDocumentEvent(\n 'click',\n (event) => {\n if (!initialClickTarget.current) {\n return\n }\n\n handleOutsideClick(event, () => {\n return initialClickTarget.current as HTMLElement\n })\n\n initialClickTarget.current = null\n },\n\n // We will use the `capture` phase so that layers in between with `event.stopPropagation()`\n // don't \"cancel\" this outside click check. E.g.: A `Menu` inside a `DialogPanel` if the `Menu`\n // is open, and you click outside of it in the `DialogPanel` the `Menu` should close. However,\n // the `DialogPanel` has a `onClick(e) { e.stopPropagation() }` which would cancel this.\n true\n )\n\n // When content inside an iframe is clicked `window` will receive a blur event\n // This can happen when an iframe _inside_ a window is clicked\n // Or, if headless UI is *in* the iframe, when a content in a window containing that iframe is clicked\n\n // In this case we care only about the first case so we check to see if the active element is the iframe\n // If so this was because of a click, focus, or other interaction with the child iframe\n // and we can consider it an \"outside click\"\n useDocumentEvent(\n 'blur',\n (event) =>\n handleOutsideClick(event, () =>\n window.document.activeElement instanceof HTMLIFrameElement\n ? window.document.activeElement\n : null\n ),\n true\n )\n}\n", "export function match(\n value: TValue,\n lookup: Record TReturnValue)>,\n ...args: any[]\n): TReturnValue {\n if (value in lookup) {\n let returnValue = lookup[value]\n return typeof returnValue === 'function' ? returnValue(...args) : returnValue\n }\n\n let error = new Error(\n `Tried to handle \"${value}\" but there is no handler defined. Only defined handlers are: ${Object.keys(\n lookup\n )\n .map((key) => `\"${key}\"`)\n .join(', ')}.`\n )\n if (Error.captureStackTrace) Error.captureStackTrace(error, match)\n throw error\n}\n", "import { MutableRefObject } from 'react'\nimport { isServer } from './ssr'\n\nexport function getOwnerDocument>(\n element: T | null | undefined\n) {\n if (isServer) return null\n if (element instanceof Node) return element.ownerDocument\n if (element?.hasOwnProperty('current')) {\n if (element.current instanceof Node) return element.current.ownerDocument\n }\n\n return document\n}\n", "import { disposables } from './disposables'\nimport { match } from './match'\nimport { getOwnerDocument } from './owner'\n\n// Credit:\n// - https://stackoverflow.com/a/30753870\nlet focusableSelector = [\n '[contentEditable=true]',\n '[tabindex]',\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'iframe',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n]\n .map(\n process.env.NODE_ENV === 'test'\n ? // TODO: Remove this once JSDOM fixes the issue where an element that is\n // \"hidden\" can be the document.activeElement, because this is not possible\n // in real browsers.\n (selector) => `${selector}:not([tabindex='-1']):not([style*='display: none'])`\n : (selector) => `${selector}:not([tabindex='-1'])`\n )\n .join(',')\n\nexport enum Focus {\n /** Focus the first non-disabled element */\n First = 1 << 0,\n\n /** Focus the previous non-disabled element */\n Previous = 1 << 1,\n\n /** Focus the next non-disabled element */\n Next = 1 << 2,\n\n /** Focus the last non-disabled element */\n Last = 1 << 3,\n\n /** Wrap tab around */\n WrapAround = 1 << 4,\n\n /** Prevent scrolling the focusable elements into view */\n NoScroll = 1 << 5,\n}\n\nexport enum FocusResult {\n /** Something went wrong while trying to focus. */\n Error,\n\n /** When `Focus.WrapAround` is enabled, going from position `N` to `N+1` where `N` is the last index in the array, then we overflow. */\n Overflow,\n\n /** Focus was successful. */\n Success,\n\n /** When `Focus.WrapAround` is enabled, going from position `N` to `N-1` where `N` is the first index in the array, then we underflow. */\n Underflow,\n}\n\nenum Direction {\n Previous = -1,\n Next = 1,\n}\n\nexport function getFocusableElements(container: HTMLElement | null = document.body) {\n if (container == null) return []\n return Array.from(container.querySelectorAll(focusableSelector)).sort(\n // We want to move `tabIndex={0}` to the end of the list, this is what the browser does as well.\n (a, z) =>\n Math.sign((a.tabIndex || Number.MAX_SAFE_INTEGER) - (z.tabIndex || Number.MAX_SAFE_INTEGER))\n )\n}\n\nexport enum FocusableMode {\n /** The element itself must be focusable. */\n Strict,\n\n /** The element should be inside of a focusable element. */\n Loose,\n}\n\nexport function isFocusableElement(\n element: HTMLElement,\n mode: FocusableMode = FocusableMode.Strict\n) {\n if (element === getOwnerDocument(element)?.body) return false\n\n return match(mode, {\n [FocusableMode.Strict]() {\n return element.matches(focusableSelector)\n },\n [FocusableMode.Loose]() {\n let next: HTMLElement | null = element\n\n while (next !== null) {\n if (next.matches(focusableSelector)) return true\n next = next.parentElement\n }\n\n return false\n },\n })\n}\n\nexport function restoreFocusIfNecessary(element: HTMLElement | null) {\n let ownerDocument = getOwnerDocument(element)\n disposables().nextFrame(() => {\n if (\n ownerDocument &&\n !isFocusableElement(ownerDocument.activeElement as HTMLElement, FocusableMode.Strict)\n ) {\n focusElement(element)\n }\n })\n}\n\nexport function focusElement(element: HTMLElement | null) {\n element?.focus({ preventScroll: true })\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select\nlet selectableSelector = ['textarea', 'input'].join(',')\nfunction isSelectableElement(\n element: Element | null\n): element is HTMLInputElement | HTMLTextAreaElement {\n return element?.matches?.(selectableSelector) ?? false\n}\n\nexport function sortByDomNode(\n nodes: T[],\n resolveKey: (item: T) => HTMLElement | null = (i) => i as unknown as HTMLElement | null\n): T[] {\n return nodes.slice().sort((aItem, zItem) => {\n let a = resolveKey(aItem)\n let z = resolveKey(zItem)\n\n if (a === null || z === null) return 0\n\n let position = a.compareDocumentPosition(z)\n\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1\n return 0\n })\n}\n\nexport function focusFrom(current: HTMLElement | null, focus: Focus) {\n return focusIn(getFocusableElements(), focus, { relativeTo: current })\n}\n\nexport function focusIn(\n container: HTMLElement | HTMLElement[],\n focus: Focus,\n {\n sorted = true,\n relativeTo = null,\n skipElements = [],\n }: Partial<{ sorted: boolean; relativeTo: HTMLElement | null; skipElements: HTMLElement[] }> = {}\n) {\n let ownerDocument = Array.isArray(container)\n ? container.length > 0\n ? container[0].ownerDocument\n : document\n : container.ownerDocument\n\n let elements = Array.isArray(container)\n ? sorted\n ? sortByDomNode(container)\n : container\n : getFocusableElements(container)\n\n if (skipElements.length > 0) {\n elements = elements.filter((x) => !skipElements.includes(x))\n }\n\n relativeTo = relativeTo ?? (ownerDocument.activeElement as HTMLElement)\n\n let direction = (() => {\n if (focus & (Focus.First | Focus.Next)) return Direction.Next\n if (focus & (Focus.Previous | Focus.Last)) return Direction.Previous\n\n throw new Error('Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last')\n })()\n\n let startIndex = (() => {\n if (focus & Focus.First) return 0\n if (focus & Focus.Previous) return Math.max(0, elements.indexOf(relativeTo)) - 1\n if (focus & Focus.Next) return Math.max(0, elements.indexOf(relativeTo)) + 1\n if (focus & Focus.Last) return elements.length - 1\n\n throw new Error('Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last')\n })()\n\n let focusOptions = focus & Focus.NoScroll ? { preventScroll: true } : {}\n\n let offset = 0\n let total = elements.length\n let next = undefined\n do {\n // Guard against infinite loops\n if (offset >= total || offset + total <= 0) return FocusResult.Error\n\n let nextIdx = startIndex + offset\n\n if (focus & Focus.WrapAround) {\n nextIdx = (nextIdx + total) % total\n } else {\n if (nextIdx < 0) return FocusResult.Underflow\n if (nextIdx >= total) return FocusResult.Overflow\n }\n\n next = elements[nextIdx]\n\n // Try the focus the next element, might not work if it is \"hidden\" to the user.\n next?.focus(focusOptions)\n\n // Try the next one in line\n offset += direction\n } while (next !== ownerDocument.activeElement)\n\n // By default if you to a text input or a textarea, the browser will\n // select all the text once the focus is inside these DOM Nodes. However,\n // since we are manually moving focus this behaviour is not happening. This\n // code will make sure that the text gets selected as-if you did it manually.\n // Note: We only do this when going forward / backward. Not for the\n // Focus.First or Focus.Last actions. This is similar to the `autoFocus`\n // behaviour on an input where the input will get focus but won't be\n // selected.\n if (focus & (Focus.Next | Focus.Previous) && isSelectableElement(next)) {\n next.select()\n }\n\n // This is a little weird, but let me try and explain: There are a few scenario's\n // in chrome for example where a focused `` tag does not get the default focus\n // styles and sometimes they do. This highly depends on whether you started by\n // clicking or by using your keyboard. When you programmatically add focus `anchor.focus()`\n // then the active element (document.activeElement) is this anchor, which is expected.\n // However in that case the default focus styles are not applied *unless* you\n // also add this tabindex.\n if (!next.hasAttribute('tabindex')) next.setAttribute('tabindex', '0')\n\n return FocusResult.Success\n}\n", "import { useEffect } from 'react'\n\nimport { useLatestValue } from './use-latest-value'\n\nexport function useDocumentEvent(\n type: TType,\n listener: (ev: DocumentEventMap[TType]) => any,\n options?: boolean | AddEventListenerOptions\n) {\n let listenerRef = useLatestValue(listener)\n\n useEffect(() => {\n function handler(event: DocumentEventMap[TType]) {\n listenerRef.current(event)\n }\n\n document.addEventListener(type, handler, options)\n return () => document.removeEventListener(type, handler, options)\n }, [type, options])\n}\n", "import { useState, MutableRefObject } from 'react'\n\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\n\nfunction resolveType(props: { type?: string; as?: TTag }) {\n if (props.type) return props.type\n\n let tag = props.as ?? 'button'\n if (typeof tag === 'string' && tag.toLowerCase() === 'button') return 'button'\n\n return undefined\n}\n\nexport function useResolveButtonType(\n props: { type?: string; as?: TTag },\n ref: MutableRefObject\n) {\n let [type, setType] = useState(() => resolveType(props))\n\n useIsoMorphicEffect(() => {\n setType(resolveType(props))\n }, [props.type, props.as])\n\n useIsoMorphicEffect(() => {\n if (type) return\n if (!ref.current) return\n\n if (ref.current instanceof HTMLButtonElement && !ref.current.hasAttribute('type')) {\n setType('button')\n }\n }, [type, ref])\n\n return type\n}\n", "import { useRef, useEffect } from 'react'\nimport { useEvent } from './use-event'\n\nlet Optional = Symbol()\n\nexport function optionalRef(cb: (ref: T) => void, isOptional = true) {\n return Object.assign(cb, { [Optional]: isOptional })\n}\n\nexport function useSyncRefs(\n ...refs: (React.MutableRefObject | ((instance: TType) => void) | null)[]\n) {\n let cache = useRef(refs)\n\n useEffect(() => {\n cache.current = refs\n }, [refs])\n\n let syncRefs = useEvent((value: TType) => {\n for (let ref of cache.current) {\n if (ref == null) continue\n if (typeof ref === 'function') ref(value)\n else ref.current = value\n }\n })\n\n return refs.every(\n (ref) =>\n ref == null ||\n // @ts-expect-error\n ref?.[Optional]\n )\n ? undefined\n : syncRefs\n}\n", "import { useRef, useEffect } from 'react'\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\nimport { getOwnerDocument } from '../utils/owner'\n\ntype AcceptNode = (\n node: HTMLElement\n) =>\n | typeof NodeFilter.FILTER_ACCEPT\n | typeof NodeFilter.FILTER_SKIP\n | typeof NodeFilter.FILTER_REJECT\n\nexport function useTreeWalker({\n container,\n accept,\n walk,\n enabled = true,\n}: {\n container: HTMLElement | null\n accept: AcceptNode\n walk(node: HTMLElement): void\n enabled?: boolean\n}) {\n let acceptRef = useRef(accept)\n let walkRef = useRef(walk)\n\n useEffect(() => {\n acceptRef.current = accept\n walkRef.current = walk\n }, [accept, walk])\n\n useIsoMorphicEffect(() => {\n if (!container) return\n if (!enabled) return\n let ownerDocument = getOwnerDocument(container)\n if (!ownerDocument) return\n\n let accept = acceptRef.current\n let walk = walkRef.current\n\n let acceptNode = Object.assign((node: HTMLElement) => accept(node), { acceptNode: accept })\n let walker = ownerDocument.createTreeWalker(\n container,\n NodeFilter.SHOW_ELEMENT,\n acceptNode,\n // @ts-expect-error This `false` is a simple small fix for older browsers\n false\n )\n\n while (walker.nextNode()) walk(walker.currentNode as HTMLElement)\n }, [container, enabled, acceptRef, walkRef])\n}\n", "function assertNever(x: never): never {\n throw new Error('Unexpected object: ' + x)\n}\n\nexport enum Focus {\n /** Focus the first non-disabled item. */\n First,\n\n /** Focus the previous non-disabled item. */\n Previous,\n\n /** Focus the next non-disabled item. */\n Next,\n\n /** Focus the last non-disabled item. */\n Last,\n\n /** Focus a specific item based on the `id` of the item. */\n Specific,\n\n /** Focus no items at all. */\n Nothing,\n}\n\nexport function calculateActiveIndex(\n action: { focus: Focus.Specific; id: string } | { focus: Exclude },\n resolvers: {\n resolveItems(): TItem[]\n resolveActiveIndex(): number | null\n resolveId(item: TItem): string\n resolveDisabled(item: TItem): boolean\n }\n) {\n let items = resolvers.resolveItems()\n if (items.length <= 0) return null\n\n let currentActiveIndex = resolvers.resolveActiveIndex()\n let activeIndex = currentActiveIndex ?? -1\n\n let nextActiveIndex = (() => {\n switch (action.focus) {\n case Focus.First:\n return items.findIndex((item) => !resolvers.resolveDisabled(item))\n\n case Focus.Previous: {\n let idx = items\n .slice()\n .reverse()\n .findIndex((item, idx, all) => {\n if (activeIndex !== -1 && all.length - idx - 1 >= activeIndex) return false\n return !resolvers.resolveDisabled(item)\n })\n if (idx === -1) return idx\n return items.length - 1 - idx\n }\n\n case Focus.Next:\n return items.findIndex((item, idx) => {\n if (idx <= activeIndex) return false\n return !resolvers.resolveDisabled(item)\n })\n\n case Focus.Last: {\n let idx = items\n .slice()\n .reverse()\n .findIndex((item) => !resolvers.resolveDisabled(item))\n if (idx === -1) return idx\n return items.length - 1 - idx\n }\n\n case Focus.Specific:\n return items.findIndex((item) => resolvers.resolveId(item) === action.id)\n\n case Focus.Nothing:\n return null\n\n default:\n assertNever(action)\n }\n })()\n\n return nextActiveIndex === -1 ? currentActiveIndex : nextActiveIndex\n}\n", "import {\n Fragment,\n cloneElement,\n createElement,\n forwardRef,\n isValidElement,\n\n // Types\n ElementType,\n ReactElement,\n} from 'react'\nimport { Props, XOR, __, Expand } from '../types'\nimport { match } from './match'\n\nexport enum Features {\n /** No features at all */\n None = 0,\n\n /**\n * When used, this will allow us to use one of the render strategies.\n *\n * **The render strategies are:**\n * - **Unmount** _(Will unmount the component.)_\n * - **Hidden** _(Will hide the component using the [hidden] attribute.)_\n */\n RenderStrategy = 1,\n\n /**\n * When used, this will allow the user of our component to be in control. This can be used when\n * you want to transition based on some state.\n */\n Static = 2,\n}\n\nexport enum RenderStrategy {\n Unmount,\n Hidden,\n}\n\ntype PropsForFeature = {\n [P in TPassedInFeatures]: P extends TForFeature ? TProps : __\n}[TPassedInFeatures]\n\nexport type PropsForFeatures = XOR<\n PropsForFeature,\n PropsForFeature\n>\n\nexport function render({\n ourProps,\n theirProps,\n slot,\n defaultTag,\n features,\n visible = true,\n name,\n}: {\n ourProps: Expand & PropsForFeatures>\n theirProps: Expand>\n slot?: TSlot\n defaultTag: ElementType\n features?: TFeature\n visible?: boolean\n name: string\n}) {\n let props = mergeProps(theirProps, ourProps)\n\n // Visible always render\n if (visible) return _render(props, slot, defaultTag, name)\n\n let featureFlags = features ?? Features.None\n\n if (featureFlags & Features.Static) {\n let { static: isStatic = false, ...rest } = props as PropsForFeatures\n\n // When the `static` prop is passed as `true`, then the user is in control, thus we don't care about anything else\n if (isStatic) return _render(rest, slot, defaultTag, name)\n }\n\n if (featureFlags & Features.RenderStrategy) {\n let { unmount = true, ...rest } = props as PropsForFeatures\n let strategy = unmount ? RenderStrategy.Unmount : RenderStrategy.Hidden\n\n return match(strategy, {\n [RenderStrategy.Unmount]() {\n return null\n },\n [RenderStrategy.Hidden]() {\n return _render(\n { ...rest, ...{ hidden: true, style: { display: 'none' } } },\n slot,\n defaultTag,\n name\n )\n },\n })\n }\n\n // No features enabled, just render\n return _render(props, slot, defaultTag, name)\n}\n\nfunction _render(\n props: Props & { ref?: unknown },\n slot: TSlot = {} as TSlot,\n tag: ElementType,\n name: string\n) {\n let {\n as: Component = tag,\n children,\n refName = 'ref',\n ...rest\n } = omit(props, ['unmount', 'static'])\n\n // This allows us to use ``\n let refRelatedProps = props.ref !== undefined ? { [refName]: props.ref } : {}\n\n let resolvedChildren = (typeof children === 'function' ? children(slot) : children) as\n | ReactElement\n | ReactElement[]\n\n // Allow for className to be a function with the slot as the contents\n if (rest.className && typeof rest.className === 'function') {\n ;(rest as any).className = rest.className(slot)\n }\n\n let dataAttributes: Record = {}\n if (slot) {\n let exposeState = false\n let states = []\n for (let [k, v] of Object.entries(slot)) {\n if (typeof v === 'boolean') {\n exposeState = true\n }\n if (v === true) {\n states.push(k)\n }\n }\n\n if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ')\n }\n\n if (Component === Fragment) {\n if (Object.keys(compact(rest)).length > 0) {\n if (\n !isValidElement(resolvedChildren) ||\n (Array.isArray(resolvedChildren) && resolvedChildren.length > 1)\n ) {\n throw new Error(\n [\n 'Passing props on \"Fragment\"!',\n '',\n `The current component <${name} /> is rendering a \"Fragment\".`,\n `However we need to passthrough the following props:`,\n Object.keys(rest)\n .map((line) => ` - ${line}`)\n .join('\\n'),\n '',\n 'You can apply a few solutions:',\n [\n 'Add an `as=\"...\"` prop, to ensure that we render an actual element instead of a \"Fragment\".',\n 'Render a single element as the child so that we can forward the props onto that element.',\n ]\n .map((line) => ` - ${line}`)\n .join('\\n'),\n ].join('\\n')\n )\n }\n\n return cloneElement(\n resolvedChildren,\n Object.assign(\n {},\n // Filter out undefined values so that they don't override the existing values\n mergeProps(resolvedChildren.props, compact(omit(rest, ['ref']))),\n dataAttributes,\n refRelatedProps,\n mergeRefs((resolvedChildren as any).ref, refRelatedProps.ref)\n )\n )\n }\n }\n\n return createElement(\n Component,\n Object.assign(\n {},\n omit(rest, ['ref']),\n Component !== Fragment && refRelatedProps,\n Component !== Fragment && dataAttributes\n ),\n resolvedChildren\n )\n}\n\nfunction mergeRefs(...refs: any[]) {\n return {\n ref: refs.every((ref) => ref == null)\n ? undefined\n : (value: any) => {\n for (let ref of refs) {\n if (ref == null) continue\n if (typeof ref === 'function') ref(value)\n else ref.current = value\n }\n },\n }\n}\n\nfunction mergeProps(...listOfProps: Props[]) {\n if (listOfProps.length === 0) return {}\n if (listOfProps.length === 1) return listOfProps[0]\n\n let target: Props = {}\n\n let eventHandlers: Record<\n string,\n ((event: { defaultPrevented: boolean }, ...args: any[]) => void | undefined)[]\n > = {}\n\n for (let props of listOfProps) {\n for (let prop in props) {\n // Collect event handlers\n if (prop.startsWith('on') && typeof props[prop] === 'function') {\n eventHandlers[prop] ??= []\n eventHandlers[prop].push(props[prop])\n } else {\n // Override incoming prop\n target[prop] = props[prop]\n }\n }\n }\n\n // Do not attach any event handlers when there is a `disabled` or `aria-disabled` prop set.\n if (target.disabled || target['aria-disabled']) {\n return Object.assign(\n target,\n // Set all event listeners that we collected to `undefined`. This is\n // important because of the `cloneElement` from above, which merges the\n // existing and new props, they don't just override therefore we have to\n // explicitly nullify them.\n Object.fromEntries(Object.keys(eventHandlers).map((eventName) => [eventName, undefined]))\n )\n }\n\n // Merge event handlers\n for (let eventName in eventHandlers) {\n Object.assign(target, {\n [eventName](event: { nativeEvent?: Event; defaultPrevented: boolean }, ...args: any[]) {\n let handlers = eventHandlers[eventName]\n\n for (let handler of handlers) {\n if (\n (event instanceof Event || event?.nativeEvent instanceof Event) &&\n event.defaultPrevented\n ) {\n return\n }\n\n handler(event, ...args)\n }\n },\n })\n }\n\n return target\n}\n\n/**\n * This is a hack, but basically we want to keep the full 'API' of the component, but we do want to\n * wrap it in a forwardRef so that we _can_ passthrough the ref\n */\nexport function forwardRefWithAs(\n component: T\n): T & { displayName: string } {\n return Object.assign(forwardRef(component as unknown as any) as any, {\n displayName: component.displayName ?? component.name,\n })\n}\n\nexport function compact>(object: T) {\n let clone = Object.assign({}, object)\n for (let key in clone) {\n if (clone[key] === undefined) delete clone[key]\n }\n return clone\n}\n\nfunction omit>(object: T, keysToOmit: string[] = []) {\n let clone = Object.assign({}, object)\n for (let key of keysToOmit) {\n if (key in clone) delete clone[key]\n }\n return clone\n}\n", "// See: https://github.com/facebook/react/issues/7711\n// See: https://github.com/facebook/react/pull/20612\n// See: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled (2.)\nexport function isDisabledReactIssue7711(element: Element): boolean {\n let parent = element.parentElement\n let legend = null\n\n while (parent && !(parent instanceof HTMLFieldSetElement)) {\n if (parent instanceof HTMLLegendElement) legend = parent\n parent = parent.parentElement\n }\n\n let isParentDisabled = parent?.getAttribute('disabled') === '' ?? false\n if (isParentDisabled && isFirstLegend(legend)) return false\n\n return isParentDisabled\n}\n\nfunction isFirstLegend(element: HTMLLegendElement | null): boolean {\n if (!element) return false\n\n let previous = element.previousElementSibling\n\n while (previous !== null) {\n if (previous instanceof HTMLLegendElement) return false\n previous = previous.previousElementSibling\n }\n\n return true\n}\n", "type Entries = [string, string][]\n\nexport function objectToFormEntries(\n source: Record = {},\n parentKey: string | null = null,\n entries: Entries = []\n): Entries {\n for (let [key, value] of Object.entries(source)) {\n append(entries, composeKey(parentKey, key), value)\n }\n\n return entries\n}\n\nfunction composeKey(parent: string | null, key: string): string {\n return parent ? parent + '[' + key + ']' : key\n}\n\nfunction append(entries: Entries, key: string, value: any): void {\n if (Array.isArray(value)) {\n for (let [subkey, subvalue] of value.entries()) {\n append(entries, composeKey(key, subkey.toString()), subvalue)\n }\n } else if (value instanceof Date) {\n entries.push([key, value.toISOString()])\n } else if (typeof value === 'boolean') {\n entries.push([key, value ? '1' : '0'])\n } else if (typeof value === 'string') {\n entries.push([key, value])\n } else if (typeof value === 'number') {\n entries.push([key, `${value}`])\n } else if (value === null || value === undefined) {\n entries.push([key, ''])\n } else {\n objectToFormEntries(value, key, entries)\n }\n}\n\nexport function attemptSubmit(element: HTMLElement) {\n let form = (element as any)?.form ?? element.closest('form')\n if (!form) return\n\n for (let element of form.elements) {\n if (\n (element.tagName === 'INPUT' && element.type === 'submit') ||\n (element.tagName === 'BUTTON' && element.type === 'submit') ||\n (element.nodeName === 'INPUT' && element.type === 'image')\n ) {\n // If you press `enter` in a normal input[type='text'] field, then the form will submit by\n // searching for the a submit element and \"click\" it. We could also use the\n // `form.requestSubmit()` function, but this has a downside where an `event.preventDefault()`\n // inside a `click` listener on the submit button won't stop the form from submitting.\n element.click()\n return\n }\n }\n}\n", "import { ElementType, Ref } from 'react'\nimport { Props } from '../types'\nimport { forwardRefWithAs, render } from '../utils/render'\n\nlet DEFAULT_VISUALLY_HIDDEN_TAG = 'div' as const\n\nexport enum Features {\n // The default, no features.\n None = 1 << 0,\n\n // Whether the element should be focusable or not.\n Focusable = 1 << 1,\n\n // Whether it should be completely hidden, even to assistive technologies.\n Hidden = 1 << 2,\n}\n\nexport let Hidden = forwardRefWithAs(function VisuallyHidden<\n TTag extends ElementType = typeof DEFAULT_VISUALLY_HIDDEN_TAG\n>(props: Props & { features?: Features }, ref: Ref) {\n let { features = Features.None, ...theirProps } = props\n let ourProps = {\n ref,\n 'aria-hidden': (features & Features.Focusable) === Features.Focusable ? true : undefined,\n style: {\n position: 'fixed',\n top: 1,\n left: 1,\n width: 1,\n height: 0,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n borderWidth: '0',\n ...((features & Features.Hidden) === Features.Hidden &&\n !((features & Features.Focusable) === Features.Focusable) && { display: 'none' }),\n },\n }\n\n return render({\n ourProps,\n theirProps,\n slot: {},\n defaultTag: DEFAULT_VISUALLY_HIDDEN_TAG,\n name: 'Hidden',\n })\n})\n", "import React, {\n createContext,\n useContext,\n\n // Types\n ReactNode,\n ReactElement,\n} from 'react'\n\nlet Context = createContext(null)\nContext.displayName = 'OpenClosedContext'\n\nexport enum State {\n Open,\n Closed,\n}\n\nexport function useOpenClosed() {\n return useContext(Context)\n}\n\ninterface Props {\n value: State\n children: ReactNode\n}\n\nexport function OpenClosedProvider({ value, children }: Props): ReactElement {\n return {children}\n}\n", "import { useRef, useState } from 'react'\nimport { useEvent } from './use-event'\n\nexport function useControllable(\n controlledValue: T | undefined,\n onChange?: (value: T) => void,\n defaultValue?: T\n) {\n let [internalValue, setInternalValue] = useState(defaultValue)\n\n let isControlled = controlledValue !== undefined\n let wasControlled = useRef(isControlled)\n let didWarnOnUncontrolledToControlled = useRef(false)\n let didWarnOnControlledToUncontrolled = useRef(false)\n\n if (isControlled && !wasControlled.current && !didWarnOnUncontrolledToControlled.current) {\n didWarnOnUncontrolledToControlled.current = true\n wasControlled.current = isControlled\n console.error(\n 'A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.'\n )\n } else if (!isControlled && wasControlled.current && !didWarnOnControlledToUncontrolled.current) {\n didWarnOnControlledToUncontrolled.current = true\n wasControlled.current = isControlled\n console.error(\n 'A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.'\n )\n }\n\n return [\n (isControlled ? controlledValue : internalValue)!,\n useEvent((value) => {\n if (isControlled) {\n return onChange?.(value)\n } else {\n setInternalValue(value)\n return onChange?.(value)\n }\n }),\n ] as const\n}\n", "import { useEffect, useRef } from 'react'\nimport { useEvent } from './use-event'\n\nexport function useWatch(\n cb: (newValues: [...T], oldValues: [...T]) => void | (() => void),\n dependencies: [...T]\n) {\n let track = useRef([] as unknown as [...T])\n let action = useEvent(cb)\n\n useEffect(() => {\n let oldValues = [...track.current] as unknown as [...T]\n\n for (let [idx, value] of dependencies.entries()) {\n if (track.current[idx] !== value) {\n // At least 1 item changed\n let returnValue = action(dependencies, oldValues)\n track.current = dependencies\n return returnValue\n }\n }\n }, [action, ...dependencies])\n}\n", "import { useRef } from 'react'\n\ntype PointerPosition = [x: number, y: number]\n\nfunction eventToPosition(evt: PointerEvent): PointerPosition {\n return [evt.screenX, evt.screenY]\n}\n\nexport function useTrackedPointer() {\n let lastPos = useRef([-1, -1])\n\n return {\n wasMoved(evt: PointerEvent) {\n // FIXME: Remove this once we use browser testing in all the relevant places.\n // NOTE: This is replaced with a compile-time define during the build process\n // This hack exists to work around a few failing tests caused by our inability to \"move\" the virtual pointer in JSDOM pointer events.\n if (process.env.TEST_BYPASS_TRACKED_POINTER) {\n return true\n }\n\n let newPos = eventToPosition(evt)\n\n if (lastPos.current[0] === newPos[0] && lastPos.current[1] === newPos[1]) {\n return false\n }\n\n lastPos.current = newPos\n return true\n },\n\n update(evt: PointerEvent) {\n lastPos.current = eventToPosition(evt)\n },\n }\n}\n", "// WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#dialog_modal\nimport React, {\n createContext,\n createRef,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n\n // Types\n ContextType,\n ElementType,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { match } from '../../utils/match'\nimport { forwardRefWithAs, render, Features, PropsForFeatures } from '../../utils/render'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { Keys } from '../keyboard'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport { useId } from '../../hooks/use-id'\nimport { FocusTrap } from '../../components/focus-trap/focus-trap'\nimport { useInertOthers } from '../../hooks/use-inert-others'\nimport { Portal } from '../../components/portal/portal'\nimport { ForcePortalRoot } from '../../internal/portal-force-root'\nimport { Description, useDescriptions } from '../description/description'\nimport { useOpenClosed, State } from '../../internal/open-closed'\nimport { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'\nimport { StackProvider, StackMessage } from '../../internal/stack-context'\nimport { useOutsideClick } from '../../hooks/use-outside-click'\nimport { useOwnerDocument } from '../../hooks/use-owner'\nimport { useEventListener } from '../../hooks/use-event-listener'\nimport { Hidden, Features as HiddenFeatures } from '../../internal/hidden'\nimport { useEvent } from '../../hooks/use-event'\nimport { disposables } from '../../utils/disposables'\nimport { isIOS } from '../../utils/platform'\n\nenum DialogStates {\n Open,\n Closed,\n}\n\ninterface StateDefinition {\n titleId: string | null\n panelRef: MutableRefObject\n}\n\nenum ActionTypes {\n SetTitleId,\n}\n\ntype Actions = { type: ActionTypes.SetTitleId; id: string | null }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract\n ) => StateDefinition\n} = {\n [ActionTypes.SetTitleId](state, action) {\n if (state.titleId === action.id) return state\n return { ...state, titleId: action.id }\n },\n}\n\nlet DialogContext = createContext<\n | [\n {\n dialogState: DialogStates\n close(): void\n setTitleId(id: string | null): void\n },\n StateDefinition\n ]\n | null\n>(null)\nDialogContext.displayName = 'DialogContext'\n\nfunction useDialogContext(component: string) {\n let context = useContext(DialogContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useDialogContext)\n throw err\n }\n return context\n}\n\nfunction useScrollLock(\n ownerDocument: Document | null,\n enabled: boolean,\n resolveAllowedContainers: () => HTMLElement[] = () => [document.body]\n) {\n useEffect(() => {\n if (!enabled) return\n if (!ownerDocument) return\n\n let d = disposables()\n let scrollPosition = window.pageYOffset\n\n function style(node: HTMLElement, property: string, value: string) {\n let previous = node.style.getPropertyValue(property)\n Object.assign(node.style, { [property]: value })\n return d.add(() => {\n Object.assign(node.style, { [property]: previous })\n })\n }\n\n let documentElement = ownerDocument.documentElement\n let ownerWindow = ownerDocument.defaultView ?? window\n\n let scrollbarWidthBefore = ownerWindow.innerWidth - documentElement.clientWidth\n style(documentElement, 'overflow', 'hidden')\n\n if (scrollbarWidthBefore > 0) {\n let scrollbarWidthAfter = documentElement.clientWidth - documentElement.offsetWidth\n let scrollbarWidth = scrollbarWidthBefore - scrollbarWidthAfter\n style(documentElement, 'paddingRight', `${scrollbarWidth}px`)\n }\n\n if (isIOS()) {\n style(ownerDocument.body, 'marginTop', `-${scrollPosition}px`)\n window.scrollTo(0, 0)\n\n // Relatively hacky, but if you click a link like `` in the Dialog, and there\n // exists an element on the page (outside of the Dialog) with that id, then the browser will\n // scroll to that position. However, this is not the case if the element we want to scroll to\n // is higher and the browser needs to scroll up, but it doesn't do that.\n //\n // Let's try and capture that element and store it, so that we can later scroll to it once the\n // Dialog closes.\n let scrollToElement: HTMLElement | null = null\n d.addEventListener(\n ownerDocument,\n 'click',\n (e) => {\n if (e.target instanceof HTMLElement) {\n try {\n let anchor = e.target.closest('a')\n if (!anchor) return\n let { hash } = new URL(anchor.href)\n let el = ownerDocument.querySelector(hash)\n if (el && !resolveAllowedContainers().some((container) => container.contains(el))) {\n scrollToElement = el as HTMLElement\n }\n } catch (err) {}\n }\n },\n true\n )\n\n d.addEventListener(\n ownerDocument,\n 'touchmove',\n (e) => {\n // Check if we are scrolling inside any of the allowed containers, if not let's cancel the event!\n if (\n e.target instanceof HTMLElement &&\n !resolveAllowedContainers().some((container) =>\n container.contains(e.target as HTMLElement)\n )\n ) {\n e.preventDefault()\n }\n },\n { passive: false }\n )\n\n // Restore scroll position\n d.add(() => {\n // Before opening the Dialog, we capture the current pageYOffset, and offset the page with\n // this value so that we can also scroll to `(0, 0)`.\n //\n // If we want to restore a few things can happen:\n //\n // 1. The window.pageYOffset is still at 0, this means nothing happened, and we can safely\n // restore to the captured value earlier.\n // 2. The window.pageYOffset is **not** at 0. This means that something happened (e.g.: a\n // link was scrolled into view in the background). Ideally we want to restore to this _new_\n // position. To do this, we can take the new value into account with the captured value from\n // before.\n //\n // (Since the value of window.pageYOffset is 0 in the first case, we should be able to\n // always sum these values)\n window.scrollTo(0, window.pageYOffset + scrollPosition)\n\n // If we captured an element that should be scrolled to, then we can try to do that if the\n // element is still connected (aka, still in the DOM).\n if (scrollToElement && scrollToElement.isConnected) {\n scrollToElement.scrollIntoView({ block: 'nearest' })\n scrollToElement = null\n }\n })\n }\n\n return d.dispose\n }, [ownerDocument, enabled])\n}\n\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_DIALOG_TAG = 'div' as const\ninterface DialogRenderPropArg {\n open: boolean\n}\ntype DialogPropsWeControl = 'role' | 'aria-modal' | 'aria-describedby' | 'aria-labelledby'\n\nlet DialogRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet DialogRoot = forwardRefWithAs(function Dialog<\n TTag extends ElementType = typeof DEFAULT_DIALOG_TAG\n>(\n props: Props &\n PropsForFeatures & {\n open?: boolean\n onClose(value: boolean): void\n initialFocus?: MutableRefObject\n __demoMode?: boolean\n },\n ref: Ref\n) {\n let internalId = useId()\n let {\n id = `headlessui-dialog-${internalId}`,\n open,\n onClose,\n initialFocus,\n __demoMode = false,\n ...theirProps\n } = props\n let [nestedDialogCount, setNestedDialogCount] = useState(0)\n\n let usesOpenClosedState = useOpenClosed()\n if (open === undefined && usesOpenClosedState !== null) {\n // Update the `open` prop based on the open closed state\n open = match(usesOpenClosedState, {\n [State.Open]: true,\n [State.Closed]: false,\n })\n }\n\n let containers = useRef>>(new Set())\n let internalDialogRef = useRef(null)\n let dialogRef = useSyncRefs(internalDialogRef, ref)\n\n // Reference to a node in the \"main\" tree, not in the portalled Dialog tree.\n let mainTreeNode = useRef(null)\n\n let ownerDocument = useOwnerDocument(internalDialogRef)\n\n // Validations\n let hasOpen = props.hasOwnProperty('open') || usesOpenClosedState !== null\n let hasOnClose = props.hasOwnProperty('onClose')\n if (!hasOpen && !hasOnClose) {\n throw new Error(\n `You have to provide an \\`open\\` and an \\`onClose\\` prop to the \\`Dialog\\` component.`\n )\n }\n\n if (!hasOpen) {\n throw new Error(\n `You provided an \\`onClose\\` prop to the \\`Dialog\\`, but forgot an \\`open\\` prop.`\n )\n }\n\n if (!hasOnClose) {\n throw new Error(\n `You provided an \\`open\\` prop to the \\`Dialog\\`, but forgot an \\`onClose\\` prop.`\n )\n }\n\n if (typeof open !== 'boolean') {\n throw new Error(\n `You provided an \\`open\\` prop to the \\`Dialog\\`, but the value is not a boolean. Received: ${open}`\n )\n }\n\n if (typeof onClose !== 'function') {\n throw new Error(\n `You provided an \\`onClose\\` prop to the \\`Dialog\\`, but the value is not a function. Received: ${onClose}`\n )\n }\n\n let dialogState = open ? DialogStates.Open : DialogStates.Closed\n\n let [state, dispatch] = useReducer(stateReducer, {\n titleId: null,\n descriptionId: null,\n panelRef: createRef(),\n } as StateDefinition)\n\n let close = useEvent(() => onClose(false))\n\n let setTitleId = useEvent((id: string | null) => dispatch({ type: ActionTypes.SetTitleId, id }))\n\n let ready = useServerHandoffComplete()\n let enabled = ready ? (__demoMode ? false : dialogState === DialogStates.Open) : false\n let hasNestedDialogs = nestedDialogCount > 1 // 1 is the current dialog\n let hasParentDialog = useContext(DialogContext) !== null\n\n // If there are multiple dialogs, then you can be the root, the leaf or one\n // in between. We only care abou whether you are the top most one or not.\n let position = !hasNestedDialogs ? 'leaf' : 'parent'\n\n // Ensure other elements can't be interacted with\n useInertOthers(internalDialogRef, hasNestedDialogs ? enabled : false)\n\n let resolveContainers = useEvent(() => {\n // Third party roots\n let rootContainers = Array.from(\n ownerDocument?.querySelectorAll('body > *, [data-headlessui-portal]') ?? []\n ).filter((container) => {\n if (!(container instanceof HTMLElement)) return false // Skip non-HTMLElements\n if (container.contains(mainTreeNode.current)) return false // Skip if it is the main app\n if (state.panelRef.current && container.contains(state.panelRef.current)) return false\n return true // Keep\n })\n\n return [...rootContainers, state.panelRef.current ?? internalDialogRef.current] as HTMLElement[]\n })\n\n // Close Dialog on outside click\n useOutsideClick(() => resolveContainers(), close, enabled && !hasNestedDialogs)\n\n // Handle `Escape` to close\n useEventListener(ownerDocument?.defaultView, 'keydown', (event) => {\n if (event.defaultPrevented) return\n if (event.key !== Keys.Escape) return\n if (dialogState !== DialogStates.Open) return\n if (hasNestedDialogs) return\n event.preventDefault()\n event.stopPropagation()\n close()\n })\n\n // Scroll lock\n useScrollLock(\n ownerDocument,\n dialogState === DialogStates.Open && !hasParentDialog,\n resolveContainers\n )\n\n // Trigger close when the FocusTrap gets hidden\n useEffect(() => {\n if (dialogState !== DialogStates.Open) return\n if (!internalDialogRef.current) return\n\n let observer = new IntersectionObserver((entries) => {\n for (let entry of entries) {\n if (\n entry.boundingClientRect.x === 0 &&\n entry.boundingClientRect.y === 0 &&\n entry.boundingClientRect.width === 0 &&\n entry.boundingClientRect.height === 0\n ) {\n close()\n }\n }\n })\n\n observer.observe(internalDialogRef.current)\n\n return () => observer.disconnect()\n }, [dialogState, internalDialogRef, close])\n\n let [describedby, DescriptionProvider] = useDescriptions()\n\n let contextBag = useMemo>(\n () => [{ dialogState, close, setTitleId }, state],\n [dialogState, state, close, setTitleId]\n )\n\n let slot = useMemo(\n () => ({ open: dialogState === DialogStates.Open }),\n [dialogState]\n )\n\n let ourProps = {\n ref: dialogRef,\n id,\n role: 'dialog',\n 'aria-modal': dialogState === DialogStates.Open ? true : undefined,\n 'aria-labelledby': state.titleId,\n 'aria-describedby': describedby,\n }\n\n return (\n {\n if (type !== 'Dialog') return\n\n match(message, {\n [StackMessage.Add]() {\n containers.current.add(element)\n setNestedDialogCount((count) => count + 1)\n },\n [StackMessage.Remove]() {\n containers.current.add(element)\n setNestedDialogCount((count) => count - 1)\n },\n })\n })}\n >\n \n \n \n \n \n \n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_DIALOG_TAG,\n features: DialogRenderFeatures,\n visible: dialogState === DialogStates.Open,\n name: 'Dialog',\n })}\n \n \n \n \n \n \n \n \n \n )\n})\n\n// ---\n\nlet DEFAULT_OVERLAY_TAG = 'div' as const\ninterface OverlayRenderPropArg {\n open: boolean\n}\ntype OverlayPropsWeControl = 'aria-hidden' | 'onClick'\n\nlet Overlay = forwardRefWithAs(function Overlay<\n TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG\n>(props: Props, ref: Ref) {\n let internalId = useId()\n let { id = `headlessui-dialog-overlay-${internalId}`, ...theirProps } = props\n let [{ dialogState, close }] = useDialogContext('Dialog.Overlay')\n let overlayRef = useSyncRefs(ref)\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (event.target !== event.currentTarget) return\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n event.preventDefault()\n event.stopPropagation()\n close()\n })\n\n let slot = useMemo(\n () => ({ open: dialogState === DialogStates.Open }),\n [dialogState]\n )\n\n let ourProps = {\n ref: overlayRef,\n id,\n 'aria-hidden': true,\n onClick: handleClick,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OVERLAY_TAG,\n name: 'Dialog.Overlay',\n })\n})\n\n// ---\n\nlet DEFAULT_BACKDROP_TAG = 'div' as const\ninterface BackdropRenderPropArg {\n open: boolean\n}\ntype BackdropPropsWeControl = 'aria-hidden' | 'onClick'\n\nlet Backdrop = forwardRefWithAs(function Backdrop<\n TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG\n>(props: Props, ref: Ref) {\n let internalId = useId()\n let { id = `headlessui-dialog-backdrop-${internalId}`, ...theirProps } = props\n let [{ dialogState }, state] = useDialogContext('Dialog.Backdrop')\n let backdropRef = useSyncRefs(ref)\n\n useEffect(() => {\n if (state.panelRef.current === null) {\n throw new Error(\n `A component is being used, but a component is missing.`\n )\n }\n }, [state.panelRef])\n\n let slot = useMemo(\n () => ({ open: dialogState === DialogStates.Open }),\n [dialogState]\n )\n\n let ourProps = {\n ref: backdropRef,\n id,\n 'aria-hidden': true,\n }\n\n return (\n \n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_BACKDROP_TAG,\n name: 'Dialog.Backdrop',\n })}\n \n \n )\n})\n\n// ---\n\nlet DEFAULT_PANEL_TAG = 'div' as const\ninterface PanelRenderPropArg {\n open: boolean\n}\n\nlet Panel = forwardRefWithAs(function Panel(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-dialog-panel-${internalId}`, ...theirProps } = props\n let [{ dialogState }, state] = useDialogContext('Dialog.Panel')\n let panelRef = useSyncRefs(ref, state.panelRef)\n\n let slot = useMemo(\n () => ({ open: dialogState === DialogStates.Open }),\n [dialogState]\n )\n\n // Prevent the click events inside the Dialog.Panel from bubbling through the React Tree which\n // could submit wrapping
elements even if we portalled the Dialog.\n let handleClick = useEvent((event: ReactMouseEvent) => {\n event.stopPropagation()\n })\n\n let ourProps = {\n ref: panelRef,\n id,\n onClick: handleClick,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_PANEL_TAG,\n name: 'Dialog.Panel',\n })\n})\n\n// ---\n\nlet DEFAULT_TITLE_TAG = 'h2' as const\ninterface TitleRenderPropArg {\n open: boolean\n}\n\nlet Title = forwardRefWithAs(function Title(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-dialog-title-${internalId}`, ...theirProps } = props\n let [{ dialogState, setTitleId }] = useDialogContext('Dialog.Title')\n\n let titleRef = useSyncRefs(ref)\n\n useEffect(() => {\n setTitleId(id)\n return () => setTitleId(null)\n }, [id, setTitleId])\n\n let slot = useMemo(\n () => ({ open: dialogState === DialogStates.Open }),\n [dialogState]\n )\n\n let ourProps = { ref: titleRef, id }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_TITLE_TAG,\n name: 'Dialog.Title',\n })\n})\n\n// ---\n\nexport let Dialog = Object.assign(DialogRoot, { Backdrop, Panel, Overlay, Title, Description })\n", "import React, {\n useEffect,\n useRef,\n\n // Types\n ElementType,\n MutableRefObject,\n Ref,\n FocusEvent as ReactFocusEvent,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { forwardRefWithAs, render } from '../../utils/render'\nimport { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { Features as HiddenFeatures, Hidden } from '../../internal/hidden'\nimport { focusElement, focusIn, Focus, FocusResult } from '../../utils/focus-management'\nimport { match } from '../../utils/match'\nimport { useEvent } from '../../hooks/use-event'\nimport { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab-direction'\nimport { useIsMounted } from '../../hooks/use-is-mounted'\nimport { useOwnerDocument } from '../../hooks/use-owner'\nimport { useEventListener } from '../../hooks/use-event-listener'\nimport { microTask } from '../../utils/micro-task'\nimport { useWatch } from '../../hooks/use-watch'\nimport { useDisposables } from '../../hooks/use-disposables'\n\nlet DEFAULT_FOCUS_TRAP_TAG = 'div' as const\n\nenum Features {\n /** No features enabled for the focus trap. */\n None = 1 << 0,\n\n /** Ensure that we move focus initially into the container. */\n InitialFocus = 1 << 1,\n\n /** Ensure that pressing `Tab` and `Shift+Tab` is trapped within the container. */\n TabLock = 1 << 2,\n\n /** Ensure that programmatically moving focus outside of the container is disallowed. */\n FocusLock = 1 << 3,\n\n /** Ensure that we restore the focus when unmounting the focus trap. */\n RestoreFocus = 1 << 4,\n\n /** Enable all features. */\n All = InitialFocus | TabLock | FocusLock | RestoreFocus,\n}\n\nexport let FocusTrap = Object.assign(\n forwardRefWithAs(function FocusTrap(\n props: Props & {\n initialFocus?: MutableRefObject\n features?: Features\n containers?: MutableRefObject>>\n },\n ref: Ref\n ) {\n let container = useRef(null)\n let focusTrapRef = useSyncRefs(container, ref)\n let { initialFocus, containers, features = Features.All, ...theirProps } = props\n\n if (!useServerHandoffComplete()) {\n features = Features.None\n }\n\n let ownerDocument = useOwnerDocument(container)\n\n useRestoreFocus({ ownerDocument }, Boolean(features & Features.RestoreFocus))\n let previousActiveElement = useInitialFocus(\n { ownerDocument, container, initialFocus },\n Boolean(features & Features.InitialFocus)\n )\n useFocusLock(\n { ownerDocument, container, containers, previousActiveElement },\n Boolean(features & Features.FocusLock)\n )\n\n let direction = useTabDirection()\n let handleFocus = useEvent((e: ReactFocusEvent) => {\n let el = container.current as HTMLElement\n if (!el) return\n\n // TODO: Cleanup once we are using real browser tests\n let wrapper = process.env.NODE_ENV === 'test' ? microTask : (cb: Function) => cb()\n wrapper(() => {\n match(direction.current, {\n [TabDirection.Forwards]: () =>\n focusIn(el, Focus.First, { skipElements: [e.relatedTarget as HTMLElement] }),\n [TabDirection.Backwards]: () =>\n focusIn(el, Focus.Last, { skipElements: [e.relatedTarget as HTMLElement] }),\n })\n })\n })\n\n let d = useDisposables()\n let recentlyUsedTabKey = useRef(false)\n let ourProps = {\n ref: focusTrapRef,\n onKeyDown(e: KeyboardEvent) {\n if (e.key == 'Tab') {\n recentlyUsedTabKey.current = true\n d.requestAnimationFrame(() => {\n recentlyUsedTabKey.current = false\n })\n }\n },\n onBlur(e: ReactFocusEvent) {\n let allContainers = new Set(containers?.current)\n allContainers.add(container)\n\n let relatedTarget = e.relatedTarget as HTMLElement | null\n if (!relatedTarget) return\n\n // Known guards, leave them alone!\n if (relatedTarget.dataset.headlessuiFocusGuard === 'true') {\n return\n }\n\n // Blur is triggered due to focus on relatedTarget, and the relatedTarget is not inside any\n // of the dialog containers. In other words, let's move focus back in!\n if (!contains(allContainers, relatedTarget)) {\n // Was the blur invoke via the keyboard? Redirect to the next in line.\n if (recentlyUsedTabKey.current) {\n focusIn(\n container.current as HTMLElement,\n match(direction.current, {\n [TabDirection.Forwards]: () => Focus.Next,\n [TabDirection.Backwards]: () => Focus.Previous,\n }) | Focus.WrapAround,\n { relativeTo: e.target as HTMLElement }\n )\n }\n\n // It was invoke via something else (e.g.: click, programmatically, ...). Redirect to the\n // previous active item in the FocusTrap\n else if (e.target instanceof HTMLElement) {\n focusElement(e.target)\n }\n }\n },\n }\n\n return (\n <>\n {Boolean(features & Features.TabLock) && (\n \n )}\n {render({\n ourProps,\n theirProps,\n defaultTag: DEFAULT_FOCUS_TRAP_TAG,\n name: 'FocusTrap',\n })}\n {Boolean(features & Features.TabLock) && (\n \n )}\n \n )\n }),\n { features: Features }\n)\n\nfunction useRestoreFocus({ ownerDocument }: { ownerDocument: Document | null }, enabled: boolean) {\n let restoreElement = useRef(null)\n\n // Capture the currently focused element, before we try to move the focus inside the FocusTrap.\n useEventListener(\n ownerDocument?.defaultView,\n 'focusout',\n (event) => {\n if (!enabled) return\n if (restoreElement.current) return\n\n restoreElement.current = event.target as HTMLElement\n },\n true\n )\n\n // Restore the focus to the previous element when `enabled` becomes false again\n useWatch(() => {\n if (enabled) return\n\n if (ownerDocument?.activeElement === ownerDocument?.body) {\n focusElement(restoreElement.current)\n }\n\n restoreElement.current = null\n }, [enabled])\n\n // Restore the focus to the previous element when the component is unmounted\n let trulyUnmounted = useRef(false)\n useEffect(() => {\n trulyUnmounted.current = false\n\n return () => {\n trulyUnmounted.current = true\n microTask(() => {\n if (!trulyUnmounted.current) return\n\n focusElement(restoreElement.current)\n restoreElement.current = null\n })\n }\n }, [])\n}\n\nfunction useInitialFocus(\n {\n ownerDocument,\n container,\n initialFocus,\n }: {\n ownerDocument: Document | null\n container: MutableRefObject\n initialFocus?: MutableRefObject\n },\n enabled: boolean\n) {\n let previousActiveElement = useRef(null)\n\n let mounted = useIsMounted()\n\n // Handle initial focus\n useWatch(() => {\n if (!enabled) return\n let containerElement = container.current\n if (!containerElement) return\n\n // Delaying the focus to the next microtask ensures that a few conditions are true:\n // - The container is rendered\n // - Transitions could be started\n // If we don't do this, then focusing an element will immediately cancel any transitions. This\n // is not ideal because transitions will look broken.\n // There is an additional issue with doing this immediately. The FocusTrap is used inside a\n // Dialog, the Dialog is rendered inside of a Portal and the Portal is rendered at the end of\n // the `document.body`. This means that the moment we call focus, the browser immediately\n // tries to focus the element, which will still be at the bodem resulting in the page to\n // scroll down. Delaying this will prevent the page to scroll down entirely.\n microTask(() => {\n if (!mounted.current) {\n return\n }\n\n let activeElement = ownerDocument?.activeElement as HTMLElement\n\n if (initialFocus?.current) {\n if (initialFocus?.current === activeElement) {\n previousActiveElement.current = activeElement\n return // Initial focus ref is already the active element\n }\n } else if (containerElement!.contains(activeElement)) {\n previousActiveElement.current = activeElement\n return // Already focused within Dialog\n }\n\n // Try to focus the initialFocus ref\n if (initialFocus?.current) {\n focusElement(initialFocus.current)\n } else {\n if (focusIn(containerElement!, Focus.First) === FocusResult.Error) {\n console.warn('There are no focusable elements inside the ')\n }\n }\n\n previousActiveElement.current = ownerDocument?.activeElement as HTMLElement\n })\n }, [enabled])\n\n return previousActiveElement\n}\n\nfunction useFocusLock(\n {\n ownerDocument,\n container,\n containers,\n previousActiveElement,\n }: {\n ownerDocument: Document | null\n container: MutableRefObject\n containers?: MutableRefObject>>\n previousActiveElement: MutableRefObject\n },\n enabled: boolean\n) {\n let mounted = useIsMounted()\n\n // Prevent programmatically escaping the container\n useEventListener(\n ownerDocument?.defaultView,\n 'focus',\n (event) => {\n if (!enabled) return\n if (!mounted.current) return\n\n let allContainers = new Set(containers?.current)\n allContainers.add(container)\n\n let previous = previousActiveElement.current\n if (!previous) return\n\n let toElement = event.target as HTMLElement | null\n\n if (toElement && toElement instanceof HTMLElement) {\n if (!contains(allContainers, toElement)) {\n event.preventDefault()\n event.stopPropagation()\n focusElement(previous)\n } else {\n previousActiveElement.current = toElement\n focusElement(toElement)\n }\n } else {\n focusElement(previousActiveElement.current)\n }\n },\n true\n )\n}\n\nfunction contains(containers: Set>, element: HTMLElement) {\n for (let container of containers) {\n if (container.current?.contains(element)) return true\n }\n\n return false\n}\n", "import { useRef } from 'react'\nimport { useWindowEvent } from './use-window-event'\n\nexport enum Direction {\n Forwards,\n Backwards,\n}\n\nexport function useTabDirection() {\n let direction = useRef(Direction.Forwards)\n\n useWindowEvent(\n 'keydown',\n (event) => {\n if (event.key === 'Tab') {\n direction.current = event.shiftKey ? Direction.Backwards : Direction.Forwards\n }\n },\n true\n )\n\n return direction\n}\n", "import { useEffect } from 'react'\n\nimport { useLatestValue } from './use-latest-value'\n\nexport function useWindowEvent(\n type: TType,\n listener: (ev: WindowEventMap[TType]) => any,\n options?: boolean | AddEventListenerOptions\n) {\n let listenerRef = useLatestValue(listener)\n\n useEffect(() => {\n function handler(event: WindowEventMap[TType]) {\n listenerRef.current(event)\n }\n\n window.addEventListener(type, handler, options)\n return () => window.removeEventListener(type, handler, options)\n }, [type, options])\n}\n", "import { useRef } from 'react'\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\n\nexport function useIsMounted() {\n let mounted = useRef(false)\n\n useIsoMorphicEffect(() => {\n mounted.current = true\n\n return () => {\n mounted.current = false\n }\n }, [])\n\n return mounted\n}\n", "import { useMemo } from 'react'\nimport { getOwnerDocument } from '../utils/owner'\n\nexport function useOwnerDocument(...args: Parameters) {\n return useMemo(() => getOwnerDocument(...args), [...args])\n}\n", "import { useEffect } from 'react'\n\nimport { useLatestValue } from './use-latest-value'\n\nexport function useEventListener(\n element: HTMLElement | Document | Window | EventTarget | null | undefined,\n type: TType,\n listener: (event: WindowEventMap[TType]) => any,\n options?: boolean | AddEventListenerOptions\n) {\n let listenerRef = useLatestValue(listener)\n\n useEffect(() => {\n element = element ?? window\n\n function handler(event: WindowEventMap[TType]) {\n listenerRef.current(event)\n }\n\n element.addEventListener(type, handler as any, options)\n return () => element!.removeEventListener(type, handler as any, options)\n }, [element, type, options])\n}\n", "import { MutableRefObject } from 'react'\nimport { getOwnerDocument } from '../utils/owner'\nimport { useIsoMorphicEffect } from './use-iso-morphic-effect'\n\nlet interactables = new Set()\nlet originals = new Map()\n\nfunction inert(element: HTMLElement) {\n element.setAttribute('aria-hidden', 'true')\n // @ts-expect-error `inert` does not exist on HTMLElement (yet!)\n element.inert = true\n}\n\nfunction restore(element: HTMLElement) {\n let original = originals.get(element)\n if (!original) return\n\n if (original['aria-hidden'] === null) element.removeAttribute('aria-hidden')\n else element.setAttribute('aria-hidden', original['aria-hidden'])\n // @ts-expect-error `inert` does not exist on HTMLElement (yet!)\n element.inert = original.inert\n}\n\nexport function useInertOthers(\n container: MutableRefObject,\n enabled: boolean = true\n) {\n useIsoMorphicEffect(() => {\n if (!enabled) return\n if (!container.current) return\n\n let element = container.current\n let ownerDocument = getOwnerDocument(element)\n if (!ownerDocument) return\n\n // Mark myself as an interactable element\n interactables.add(element)\n\n // Restore elements that now contain an interactable child\n for (let original of originals.keys()) {\n if (original.contains(element)) {\n restore(original)\n originals.delete(original)\n }\n }\n\n // Collect direct children of the body\n ownerDocument.querySelectorAll('body > *').forEach((child) => {\n if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements\n\n // Skip the interactables, and the parents of the interactables\n for (let interactable of interactables) {\n if (child.contains(interactable)) return\n }\n\n // Keep track of the elements\n if (interactables.size === 1) {\n originals.set(child, {\n 'aria-hidden': child.getAttribute('aria-hidden'),\n // @ts-expect-error `inert` does not exist on HTMLElement (yet!)\n inert: child.inert,\n })\n\n // Mutate the element\n inert(child)\n }\n })\n\n return () => {\n // Inert is disabled on the current element\n interactables.delete(element)\n\n // We still have interactable elements, therefore this one and its parent\n // will become inert as well.\n if (interactables.size > 0) {\n // Collect direct children of the body\n ownerDocument!.querySelectorAll('body > *').forEach((child) => {\n if (!(child instanceof HTMLElement)) return // Skip non-HTMLElements\n\n // Skip already inert parents\n if (originals.has(child)) return\n\n // Skip the interactables, and the parents of the interactables\n for (let interactable of interactables) {\n if (child.contains(interactable)) return\n }\n\n originals.set(child, {\n 'aria-hidden': child.getAttribute('aria-hidden'),\n // @ts-expect-error `inert` does not exist on HTMLElement (yet!)\n inert: child.inert,\n })\n\n // Mutate the element\n inert(child)\n })\n } else {\n for (let element of originals.keys()) {\n // Restore\n restore(element)\n\n // Cleanup\n originals.delete(element)\n }\n }\n }\n }, [enabled])\n}\n", "import React, {\n Fragment,\n createContext,\n useContext,\n useEffect,\n useRef,\n useState,\n\n // Types\n ElementType,\n MutableRefObject,\n Ref,\n} from 'react'\nimport { createPortal } from 'react-dom'\n\nimport { Props } from '../../types'\nimport { forwardRefWithAs, render } from '../../utils/render'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { usePortalRoot } from '../../internal/portal-force-root'\nimport { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'\nimport { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useOwnerDocument } from '../../hooks/use-owner'\nimport { microTask } from '../../utils/micro-task'\nimport { isServer } from '../../utils/ssr'\n\nfunction usePortalTarget(ref: MutableRefObject): HTMLElement | null {\n let forceInRoot = usePortalRoot()\n let groupTarget = useContext(PortalGroupContext)\n\n let ownerDocument = useOwnerDocument(ref)\n\n let [target, setTarget] = useState(() => {\n // Group context is used, but still null\n if (!forceInRoot && groupTarget !== null) return null\n\n // No group context is used, let's create a default portal root\n if (isServer) return null\n let existingRoot = ownerDocument?.getElementById('headlessui-portal-root')\n if (existingRoot) return existingRoot\n\n if (ownerDocument === null) return null\n\n let root = ownerDocument.createElement('div')\n root.setAttribute('id', 'headlessui-portal-root')\n return ownerDocument.body.appendChild(root)\n })\n\n // Ensure the portal root is always in the DOM\n useEffect(() => {\n if (target === null) return\n\n if (!ownerDocument?.body.contains(target)) {\n ownerDocument?.body.appendChild(target)\n }\n }, [target, ownerDocument])\n\n useEffect(() => {\n if (forceInRoot) return\n if (groupTarget === null) return\n setTarget(groupTarget.current)\n }, [groupTarget, setTarget, forceInRoot])\n\n return target\n}\n\n// ---\n\nlet DEFAULT_PORTAL_TAG = Fragment\ninterface PortalRenderPropArg {}\n\nlet PortalRoot = forwardRefWithAs(function Portal<\n TTag extends ElementType = typeof DEFAULT_PORTAL_TAG\n>(props: Props, ref: Ref) {\n let theirProps = props\n let internalPortalRootRef = useRef(null)\n let portalRef = useSyncRefs(\n optionalRef((ref) => {\n internalPortalRootRef.current = ref\n }),\n ref\n )\n let ownerDocument = useOwnerDocument(internalPortalRootRef)\n let target = usePortalTarget(internalPortalRootRef)\n let [element] = useState(() =>\n isServer ? null : ownerDocument?.createElement('div') ?? null\n )\n\n let ready = useServerHandoffComplete()\n\n let trulyUnmounted = useRef(false)\n useIsoMorphicEffect(() => {\n trulyUnmounted.current = false\n\n if (!target || !element) return\n\n // Element already exists in target, always calling target.appendChild(element) will cause a\n // brief unmount/remount.\n if (!target.contains(element)) {\n element.setAttribute('data-headlessui-portal', '')\n target.appendChild(element)\n }\n\n return () => {\n trulyUnmounted.current = true\n\n microTask(() => {\n if (!trulyUnmounted.current) return\n if (!target || !element) return\n\n target.removeChild(element)\n\n if (target.childNodes.length <= 0) {\n target.parentElement?.removeChild(target)\n }\n })\n }\n }, [target, element])\n\n if (!ready) return null\n\n let ourProps = { ref: portalRef }\n\n return !target || !element\n ? null\n : createPortal(\n render({\n ourProps,\n theirProps,\n defaultTag: DEFAULT_PORTAL_TAG,\n name: 'Portal',\n }),\n element\n )\n})\n\n// ---\n\nlet DEFAULT_GROUP_TAG = Fragment\ninterface GroupRenderPropArg {}\n\nlet PortalGroupContext = createContext | null>(null)\n\nlet Group = forwardRefWithAs(function Group(\n props: Props & {\n target: MutableRefObject\n },\n ref: Ref\n) {\n let { target, ...theirProps } = props\n let groupRef = useSyncRefs(ref)\n\n let ourProps = { ref: groupRef }\n\n return (\n \n {render({\n ourProps,\n theirProps,\n defaultTag: DEFAULT_GROUP_TAG,\n name: 'Popover.Group',\n })}\n \n )\n})\n\n// ---\n\nexport let Portal = Object.assign(PortalRoot, { Group })\n", "import React, {\n createContext,\n useContext,\n\n // Types\n ReactNode,\n} from 'react'\n\nlet ForcePortalRootContext = createContext(false)\n\nexport function usePortalRoot() {\n return useContext(ForcePortalRootContext)\n}\n\ninterface ForcePortalRootProps {\n force: boolean\n children: ReactNode\n}\n\nexport function ForcePortalRoot(props: ForcePortalRootProps) {\n return (\n \n {props.children}\n \n )\n}\n", "import React, {\n createContext,\n useContext,\n useMemo,\n useState,\n\n // Types\n ElementType,\n ReactNode,\n Ref,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { useId } from '../../hooks/use-id'\nimport { forwardRefWithAs, render } from '../../utils/render'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useEvent } from '../../hooks/use-event'\n\n// ---\n\ninterface SharedData {\n slot?: {}\n name?: string\n props?: {}\n}\n\nlet DescriptionContext = createContext<\n ({ register(value: string): () => void } & SharedData) | null\n>(null)\n\nfunction useDescriptionContext() {\n let context = useContext(DescriptionContext)\n if (context === null) {\n let err = new Error(\n 'You used a component, but it is not inside a relevant parent.'\n )\n if (Error.captureStackTrace) Error.captureStackTrace(err, useDescriptionContext)\n throw err\n }\n return context\n}\n\ninterface DescriptionProviderProps extends SharedData {\n children: ReactNode\n}\n\nexport function useDescriptions(): [\n string | undefined,\n (props: DescriptionProviderProps) => JSX.Element\n] {\n let [descriptionIds, setDescriptionIds] = useState([])\n\n return [\n // The actual id's as string or undefined\n descriptionIds.length > 0 ? descriptionIds.join(' ') : undefined,\n\n // The provider component\n useMemo(() => {\n return function DescriptionProvider(props: DescriptionProviderProps) {\n let register = useEvent((value: string) => {\n setDescriptionIds((existing) => [...existing, value])\n\n return () =>\n setDescriptionIds((existing) => {\n let clone = existing.slice()\n let idx = clone.indexOf(value)\n if (idx !== -1) clone.splice(idx, 1)\n return clone\n })\n })\n\n let contextBag = useMemo(\n () => ({ register, slot: props.slot, name: props.name, props: props.props }),\n [register, props.slot, props.name, props.props]\n )\n\n return (\n \n {props.children}\n \n )\n }\n }, [setDescriptionIds]),\n ]\n}\n\n// ---\n\nlet DEFAULT_DESCRIPTION_TAG = 'p' as const\n\nexport let Description = forwardRefWithAs(function Description<\n TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG\n>(props: Props, ref: Ref) {\n let internalId = useId()\n let { id = `headlessui-description-${internalId}`, ...theirProps } = props\n let context = useDescriptionContext()\n let descriptionRef = useSyncRefs(ref)\n\n useIsoMorphicEffect(() => context.register(id), [id, context.register])\n\n let ourProps = { ref: descriptionRef, ...context.props, id }\n\n return render({\n ourProps,\n theirProps,\n slot: context.slot || {},\n defaultTag: DEFAULT_DESCRIPTION_TAG,\n name: context.name || 'Description',\n })\n})\n", "import React, {\n createContext,\n useContext,\n\n // Types\n MutableRefObject,\n ReactNode,\n} from 'react'\nimport { useIsoMorphicEffect } from '../hooks/use-iso-morphic-effect'\nimport { useEvent } from '../hooks/use-event'\n\ntype OnUpdate = (\n message: StackMessage,\n type: string,\n element: MutableRefObject\n) => void\n\nlet StackContext = createContext(() => {})\nStackContext.displayName = 'StackContext'\n\nexport enum StackMessage {\n Add,\n Remove,\n}\n\nexport function useStackContext() {\n return useContext(StackContext)\n}\n\nexport function StackProvider({\n children,\n onUpdate,\n type,\n element,\n enabled,\n}: {\n children: ReactNode\n onUpdate?: OnUpdate\n type: string\n element: MutableRefObject\n enabled?: boolean\n}) {\n let parentUpdate = useStackContext()\n\n let notify = useEvent((...args: Parameters) => {\n // Notify our layer\n onUpdate?.(...args)\n\n // Notify the parent\n parentUpdate(...args)\n })\n\n useIsoMorphicEffect(() => {\n let shouldNotify = enabled === undefined || enabled === true\n\n shouldNotify && notify(StackMessage.Add, type, element)\n\n return () => {\n shouldNotify && notify(StackMessage.Remove, type, element)\n }\n }, [notify, type, element, enabled])\n\n return {children}\n}\n", "export function isIOS() {\n // TODO: This is not a great way to detect iOS, but it's the best I can do for now.\n // - `window.platform` is deprecated\n // - `window.userAgentData.platform` is still experimental (https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/platform)\n // - `window.userAgent` also doesn't contain the required information\n return (\n // Check if it is an iPhone\n /iPhone/gi.test(window.navigator.platform) ||\n // Check if it is an iPad. iPad reports itself as \"MacIntel\", but we can check if it is a touch\n // screen. Let's hope that Apple doesn't release a touch screen Mac (or maybe this would then\n // work as expected \uD83E\uDD14).\n (/Mac/gi.test(window.navigator.platform) && window.navigator.maxTouchPoints > 0)\n )\n}\n", "// WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#disclosure\nimport React, {\n Fragment,\n createContext,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n\n // Types\n ContextType,\n Dispatch,\n ElementType,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { match } from '../../utils/match'\nimport { forwardRefWithAs, render, Features, PropsForFeatures } from '../../utils/render'\nimport { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useId } from '../../hooks/use-id'\nimport { Keys } from '../keyboard'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'\nimport { useResolveButtonType } from '../../hooks/use-resolve-button-type'\nimport { getOwnerDocument } from '../../utils/owner'\nimport { useEvent } from '../../hooks/use-event'\n\nenum DisclosureStates {\n Open,\n Closed,\n}\n\ninterface StateDefinition {\n disclosureState: DisclosureStates\n\n linkedPanel: boolean\n\n buttonRef: MutableRefObject\n panelRef: MutableRefObject\n\n buttonId: string | null\n panelId: string | null\n}\n\nenum ActionTypes {\n ToggleDisclosure,\n CloseDisclosure,\n\n SetButtonId,\n SetPanelId,\n\n LinkPanel,\n UnlinkPanel,\n}\n\ntype Actions =\n | { type: ActionTypes.ToggleDisclosure }\n | { type: ActionTypes.CloseDisclosure }\n | { type: ActionTypes.SetButtonId; buttonId: string | null }\n | { type: ActionTypes.SetPanelId; panelId: string | null }\n | { type: ActionTypes.LinkPanel }\n | { type: ActionTypes.UnlinkPanel }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract\n ) => StateDefinition\n} = {\n [ActionTypes.ToggleDisclosure]: (state) => ({\n ...state,\n disclosureState: match(state.disclosureState, {\n [DisclosureStates.Open]: DisclosureStates.Closed,\n [DisclosureStates.Closed]: DisclosureStates.Open,\n }),\n }),\n [ActionTypes.CloseDisclosure]: (state) => {\n if (state.disclosureState === DisclosureStates.Closed) return state\n return { ...state, disclosureState: DisclosureStates.Closed }\n },\n [ActionTypes.LinkPanel](state) {\n if (state.linkedPanel === true) return state\n return { ...state, linkedPanel: true }\n },\n [ActionTypes.UnlinkPanel](state) {\n if (state.linkedPanel === false) return state\n return { ...state, linkedPanel: false }\n },\n [ActionTypes.SetButtonId](state, action) {\n if (state.buttonId === action.buttonId) return state\n return { ...state, buttonId: action.buttonId }\n },\n [ActionTypes.SetPanelId](state, action) {\n if (state.panelId === action.panelId) return state\n return { ...state, panelId: action.panelId }\n },\n}\n\nlet DisclosureContext = createContext<[StateDefinition, Dispatch] | null>(null)\nDisclosureContext.displayName = 'DisclosureContext'\n\nfunction useDisclosureContext(component: string) {\n let context = useContext(DisclosureContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useDisclosureContext)\n throw err\n }\n return context\n}\n\nlet DisclosureAPIContext = createContext<{\n close(focusableElement?: HTMLElement | MutableRefObject): void\n} | null>(null)\nDisclosureAPIContext.displayName = 'DisclosureAPIContext'\n\nfunction useDisclosureAPIContext(component: string) {\n let context = useContext(DisclosureAPIContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useDisclosureAPIContext)\n throw err\n }\n return context\n}\n\nlet DisclosurePanelContext = createContext(null)\nDisclosurePanelContext.displayName = 'DisclosurePanelContext'\n\nfunction useDisclosurePanelContext() {\n return useContext(DisclosurePanelContext)\n}\n\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_DISCLOSURE_TAG = Fragment\ninterface DisclosureRenderPropArg {\n open: boolean\n close(focusableElement?: HTMLElement | MutableRefObject): void\n}\n\nlet DisclosureRoot = forwardRefWithAs(function Disclosure<\n TTag extends ElementType = typeof DEFAULT_DISCLOSURE_TAG\n>(\n props: Props & {\n defaultOpen?: boolean\n },\n ref: Ref\n) {\n let { defaultOpen = false, ...theirProps } = props\n let internalDisclosureRef = useRef(null)\n let disclosureRef = useSyncRefs(\n ref,\n optionalRef(\n (ref) => {\n internalDisclosureRef.current = ref as unknown as HTMLElement | null\n },\n props.as === undefined ||\n // @ts-expect-error The `as` prop _can_ be a Fragment\n props.as === Fragment\n )\n )\n\n let panelRef = useRef(null)\n let buttonRef = useRef(null)\n\n let reducerBag = useReducer(stateReducer, {\n disclosureState: defaultOpen ? DisclosureStates.Open : DisclosureStates.Closed,\n linkedPanel: false,\n buttonRef,\n panelRef,\n buttonId: null,\n panelId: null,\n } as StateDefinition)\n let [{ disclosureState, buttonId }, dispatch] = reducerBag\n\n let close = useEvent((focusableElement?: HTMLElement | MutableRefObject) => {\n dispatch({ type: ActionTypes.CloseDisclosure })\n let ownerDocument = getOwnerDocument(internalDisclosureRef)\n if (!ownerDocument) return\n if (!buttonId) return\n\n let restoreElement = (() => {\n if (!focusableElement) return ownerDocument.getElementById(buttonId)\n if (focusableElement instanceof HTMLElement) return focusableElement\n if (focusableElement.current instanceof HTMLElement) return focusableElement.current\n\n return ownerDocument.getElementById(buttonId)\n })()\n\n restoreElement?.focus()\n })\n\n let api = useMemo>(() => ({ close }), [close])\n\n let slot = useMemo(\n () => ({ open: disclosureState === DisclosureStates.Open, close }),\n [disclosureState, close]\n )\n\n let ourProps = {\n ref: disclosureRef,\n }\n\n return (\n \n \n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_DISCLOSURE_TAG,\n name: 'Disclosure',\n })}\n \n \n \n )\n})\n\n// ---\n\nlet DEFAULT_BUTTON_TAG = 'button' as const\ninterface ButtonRenderPropArg {\n open: boolean\n}\ntype ButtonPropsWeControl = 'type' | 'aria-expanded' | 'aria-controls' | 'onKeyDown' | 'onClick'\n\nlet Button = forwardRefWithAs(function Button(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-disclosure-button-${internalId}`, ...theirProps } = props\n let [state, dispatch] = useDisclosureContext('Disclosure.Button')\n let panelContext = useDisclosurePanelContext()\n let isWithinPanel = panelContext === null ? false : panelContext === state.panelId\n\n let internalButtonRef = useRef(null)\n let buttonRef = useSyncRefs(internalButtonRef, ref, !isWithinPanel ? state.buttonRef : null)\n\n useEffect(() => {\n if (isWithinPanel) return\n\n dispatch({ type: ActionTypes.SetButtonId, buttonId: id })\n return () => {\n dispatch({ type: ActionTypes.SetButtonId, buttonId: null })\n }\n }, [id, dispatch, isWithinPanel])\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n if (isWithinPanel) {\n if (state.disclosureState === DisclosureStates.Closed) return\n\n switch (event.key) {\n case Keys.Space:\n case Keys.Enter:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.ToggleDisclosure })\n state.buttonRef.current?.focus()\n break\n }\n } else {\n switch (event.key) {\n case Keys.Space:\n case Keys.Enter:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.ToggleDisclosure })\n break\n }\n }\n })\n\n let handleKeyUp = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n case Keys.Space:\n // Required for firefox, event.preventDefault() in handleKeyDown for\n // the Space key doesn't cancel the handleKeyUp, which in turn\n // triggers a *click*.\n event.preventDefault()\n break\n }\n })\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return\n if (props.disabled) return\n\n if (isWithinPanel) {\n dispatch({ type: ActionTypes.ToggleDisclosure })\n state.buttonRef.current?.focus()\n } else {\n dispatch({ type: ActionTypes.ToggleDisclosure })\n }\n })\n\n let slot = useMemo(\n () => ({ open: state.disclosureState === DisclosureStates.Open }),\n [state]\n )\n\n let type = useResolveButtonType(props, internalButtonRef)\n let ourProps = isWithinPanel\n ? { ref: buttonRef, type, onKeyDown: handleKeyDown, onClick: handleClick }\n : {\n ref: buttonRef,\n id,\n type,\n 'aria-expanded': props.disabled\n ? undefined\n : state.disclosureState === DisclosureStates.Open,\n 'aria-controls': state.linkedPanel ? state.panelId : undefined,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n onClick: handleClick,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_BUTTON_TAG,\n name: 'Disclosure.Button',\n })\n})\n\n// ---\n\nlet DEFAULT_PANEL_TAG = 'div' as const\ninterface PanelRenderPropArg {\n open: boolean\n close: (focusableElement?: HTMLElement | MutableRefObject) => void\n}\n\nlet PanelRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet Panel = forwardRefWithAs(function Panel(\n props: Props & PropsForFeatures,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-disclosure-panel-${internalId}`, ...theirProps } = props\n let [state, dispatch] = useDisclosureContext('Disclosure.Panel')\n let { close } = useDisclosureAPIContext('Disclosure.Panel')\n\n let panelRef = useSyncRefs(ref, state.panelRef, (el) => {\n dispatch({ type: el ? ActionTypes.LinkPanel : ActionTypes.UnlinkPanel })\n })\n\n useEffect(() => {\n dispatch({ type: ActionTypes.SetPanelId, panelId: id })\n return () => {\n dispatch({ type: ActionTypes.SetPanelId, panelId: null })\n }\n }, [id, dispatch])\n\n let usesOpenClosedState = useOpenClosed()\n let visible = (() => {\n if (usesOpenClosedState !== null) {\n return usesOpenClosedState === State.Open\n }\n\n return state.disclosureState === DisclosureStates.Open\n })()\n\n let slot = useMemo(\n () => ({ open: state.disclosureState === DisclosureStates.Open, close }),\n [state, close]\n )\n\n let ourProps = {\n ref: panelRef,\n id,\n }\n\n return (\n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_PANEL_TAG,\n features: PanelRenderFeatures,\n visible,\n name: 'Disclosure.Panel',\n })}\n \n )\n})\n\n// ---\n\nexport let Disclosure = Object.assign(DisclosureRoot, { Button, Panel })\n", "import React, {\n Fragment,\n createContext,\n createRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n\n // Types\n ElementType,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n} from 'react'\n\nimport { useDisposables } from '../../hooks/use-disposables'\nimport { useId } from '../../hooks/use-id'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { useComputed } from '../../hooks/use-computed'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { EnsureArray, Props } from '../../types'\nimport { Features, forwardRefWithAs, PropsForFeatures, render, compact } from '../../utils/render'\nimport { match } from '../../utils/match'\nimport { disposables } from '../../utils/disposables'\nimport { Keys } from '../keyboard'\nimport { Focus, calculateActiveIndex } from '../../utils/calculate-active-index'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport { isFocusableElement, FocusableMode, sortByDomNode } from '../../utils/focus-management'\nimport { useOpenClosed, State, OpenClosedProvider } from '../../internal/open-closed'\nimport { useResolveButtonType } from '../../hooks/use-resolve-button-type'\nimport { useOutsideClick } from '../../hooks/use-outside-click'\nimport { Hidden, Features as HiddenFeatures } from '../../internal/hidden'\nimport { objectToFormEntries } from '../../utils/form'\nimport { getOwnerDocument } from '../../utils/owner'\nimport { useEvent } from '../../hooks/use-event'\nimport { useControllable } from '../../hooks/use-controllable'\nimport { useLatestValue } from '../../hooks/use-latest-value'\nimport { useTrackedPointer } from '../../hooks/use-tracked-pointer'\n\nenum ListboxStates {\n Open,\n Closed,\n}\n\nenum ValueMode {\n Single,\n Multi,\n}\n\nenum ActivationTrigger {\n Pointer,\n Other,\n}\n\ntype ListboxOptionDataRef = MutableRefObject<{\n textValue?: string\n disabled: boolean\n value: T\n domRef: MutableRefObject\n}>\n\ninterface StateDefinition {\n dataRef: MutableRefObject<_Data>\n labelId: string | null\n\n listboxState: ListboxStates\n\n options: { id: string; dataRef: ListboxOptionDataRef }[]\n searchQuery: string\n activeOptionIndex: number | null\n activationTrigger: ActivationTrigger\n}\n\nenum ActionTypes {\n OpenListbox,\n CloseListbox,\n\n GoToOption,\n Search,\n ClearSearch,\n\n RegisterOption,\n UnregisterOption,\n\n RegisterLabel,\n}\n\nfunction adjustOrderedState(\n state: StateDefinition,\n adjustment: (options: StateDefinition['options']) => StateDefinition['options'] = (i) => i\n) {\n let currentActiveOption =\n state.activeOptionIndex !== null ? state.options[state.activeOptionIndex] : null\n\n let sortedOptions = sortByDomNode(\n adjustment(state.options.slice()),\n (option) => option.dataRef.current.domRef.current\n )\n\n // If we inserted an option before the current active option then the active option index\n // would be wrong. To fix this, we will re-lookup the correct index.\n let adjustedActiveOptionIndex = currentActiveOption\n ? sortedOptions.indexOf(currentActiveOption)\n : null\n\n // Reset to `null` in case the currentActiveOption was removed.\n if (adjustedActiveOptionIndex === -1) {\n adjustedActiveOptionIndex = null\n }\n\n return {\n options: sortedOptions,\n activeOptionIndex: adjustedActiveOptionIndex,\n }\n}\n\ntype Actions =\n | { type: ActionTypes.CloseListbox }\n | { type: ActionTypes.OpenListbox }\n | { type: ActionTypes.GoToOption; focus: Focus.Specific; id: string; trigger?: ActivationTrigger }\n | {\n type: ActionTypes.GoToOption\n focus: Exclude\n trigger?: ActivationTrigger\n }\n | { type: ActionTypes.Search; value: string }\n | { type: ActionTypes.ClearSearch }\n | { type: ActionTypes.RegisterOption; id: string; dataRef: ListboxOptionDataRef }\n | { type: ActionTypes.RegisterLabel; id: string | null }\n | { type: ActionTypes.UnregisterOption; id: string }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract, { type: P }>\n ) => StateDefinition\n} = {\n [ActionTypes.CloseListbox](state) {\n if (state.dataRef.current.disabled) return state\n if (state.listboxState === ListboxStates.Closed) return state\n return { ...state, activeOptionIndex: null, listboxState: ListboxStates.Closed }\n },\n [ActionTypes.OpenListbox](state) {\n if (state.dataRef.current.disabled) return state\n if (state.listboxState === ListboxStates.Open) return state\n\n // Check if we have a selected value that we can make active\n let activeOptionIndex = state.activeOptionIndex\n let { isSelected } = state.dataRef.current\n let optionIdx = state.options.findIndex((option) => isSelected(option.dataRef.current.value))\n\n if (optionIdx !== -1) {\n activeOptionIndex = optionIdx\n }\n\n return { ...state, listboxState: ListboxStates.Open, activeOptionIndex }\n },\n [ActionTypes.GoToOption](state, action) {\n if (state.dataRef.current.disabled) return state\n if (state.listboxState === ListboxStates.Closed) return state\n\n let adjustedState = adjustOrderedState(state)\n let activeOptionIndex = calculateActiveIndex(action, {\n resolveItems: () => adjustedState.options,\n resolveActiveIndex: () => adjustedState.activeOptionIndex,\n resolveId: (option) => option.id,\n resolveDisabled: (option) => option.dataRef.current.disabled,\n })\n\n return {\n ...state,\n ...adjustedState,\n searchQuery: '',\n activeOptionIndex,\n activationTrigger: action.trigger ?? ActivationTrigger.Other,\n }\n },\n [ActionTypes.Search]: (state, action) => {\n if (state.dataRef.current.disabled) return state\n if (state.listboxState === ListboxStates.Closed) return state\n\n let wasAlreadySearching = state.searchQuery !== ''\n let offset = wasAlreadySearching ? 0 : 1\n\n let searchQuery = state.searchQuery + action.value.toLowerCase()\n\n let reOrderedOptions =\n state.activeOptionIndex !== null\n ? state.options\n .slice(state.activeOptionIndex + offset)\n .concat(state.options.slice(0, state.activeOptionIndex + offset))\n : state.options\n\n let matchingOption = reOrderedOptions.find(\n (option) =>\n !option.dataRef.current.disabled &&\n option.dataRef.current.textValue?.startsWith(searchQuery)\n )\n\n let matchIdx = matchingOption ? state.options.indexOf(matchingOption) : -1\n\n if (matchIdx === -1 || matchIdx === state.activeOptionIndex) return { ...state, searchQuery }\n return {\n ...state,\n searchQuery,\n activeOptionIndex: matchIdx,\n activationTrigger: ActivationTrigger.Other,\n }\n },\n [ActionTypes.ClearSearch](state) {\n if (state.dataRef.current.disabled) return state\n if (state.listboxState === ListboxStates.Closed) return state\n if (state.searchQuery === '') return state\n return { ...state, searchQuery: '' }\n },\n [ActionTypes.RegisterOption]: (state, action) => {\n let option = { id: action.id, dataRef: action.dataRef }\n let adjustedState = adjustOrderedState(state, (options) => [...options, option])\n\n // Check if we need to make the newly registered option active.\n if (state.activeOptionIndex === null) {\n if (state.dataRef.current.isSelected(action.dataRef.current.value)) {\n adjustedState.activeOptionIndex = adjustedState.options.indexOf(option)\n }\n }\n\n return { ...state, ...adjustedState }\n },\n [ActionTypes.UnregisterOption]: (state, action) => {\n let adjustedState = adjustOrderedState(state, (options) => {\n let idx = options.findIndex((a) => a.id === action.id)\n if (idx !== -1) options.splice(idx, 1)\n return options\n })\n\n return {\n ...state,\n ...adjustedState,\n activationTrigger: ActivationTrigger.Other,\n }\n },\n [ActionTypes.RegisterLabel]: (state, action) => {\n return {\n ...state,\n labelId: action.id,\n }\n },\n}\n\nlet ListboxActionsContext = createContext<{\n openListbox(): void\n closeListbox(): void\n registerOption(id: string, dataRef: ListboxOptionDataRef): () => void\n registerLabel(id: string): () => void\n goToOption(focus: Focus.Specific, id: string, trigger?: ActivationTrigger): void\n goToOption(focus: Focus, id?: string, trigger?: ActivationTrigger): void\n selectOption(id: string): void\n selectActiveOption(): void\n onChange(value: unknown): void\n search(query: string): void\n clearSearch(): void\n} | null>(null)\nListboxActionsContext.displayName = 'ListboxActionsContext'\n\nfunction useActions(component: string) {\n let context = useContext(ListboxActionsContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useActions)\n throw err\n }\n return context\n}\ntype _Actions = ReturnType\n\nlet ListboxDataContext = createContext<\n | ({\n value: unknown\n disabled: boolean\n mode: ValueMode\n orientation: 'horizontal' | 'vertical'\n activeOptionIndex: number | null\n compare(a: unknown, z: unknown): boolean\n isSelected(value: unknown): boolean\n\n optionsPropsRef: MutableRefObject<{\n static: boolean\n hold: boolean\n }>\n\n labelRef: MutableRefObject\n buttonRef: MutableRefObject\n optionsRef: MutableRefObject\n } & Omit, 'dataRef'>)\n | null\n>(null)\nListboxDataContext.displayName = 'ListboxDataContext'\n\nfunction useData(component: string) {\n let context = useContext(ListboxDataContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useData)\n throw err\n }\n return context\n}\ntype _Data = ReturnType\n\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_LISTBOX_TAG = Fragment\ninterface ListboxRenderPropArg {\n open: boolean\n disabled: boolean\n value: T\n}\n\nlet ListboxRoot = forwardRefWithAs(function Listbox<\n TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG,\n TType = string,\n TActualType = TType extends (infer U)[] ? U : TType\n>(\n props: Props<\n TTag,\n ListboxRenderPropArg,\n 'value' | 'defaultValue' | 'onChange' | 'by' | 'disabled' | 'horizontal' | 'name' | 'multiple'\n > & {\n value?: TType\n defaultValue?: TType\n onChange?(value: TType): void\n by?: (keyof TActualType & string) | ((a: TActualType, z: TActualType) => boolean)\n disabled?: boolean\n horizontal?: boolean\n name?: string\n multiple?: boolean\n },\n ref: Ref\n) {\n let {\n value: controlledValue,\n defaultValue,\n name,\n onChange: controlledOnChange,\n by = (a, z) => a === z,\n disabled = false,\n horizontal = false,\n multiple = false,\n ...theirProps\n } = props\n const orientation = horizontal ? 'horizontal' : 'vertical'\n let listboxRef = useSyncRefs(ref)\n\n let [value = multiple ? [] : undefined, theirOnChange] = useControllable(\n controlledValue,\n controlledOnChange,\n defaultValue\n )\n\n let [state, dispatch] = useReducer(stateReducer, {\n dataRef: createRef(),\n listboxState: ListboxStates.Closed,\n options: [],\n searchQuery: '',\n labelId: null,\n activeOptionIndex: null,\n activationTrigger: ActivationTrigger.Other,\n } as StateDefinition)\n\n let optionsPropsRef = useRef<_Data['optionsPropsRef']['current']>({ static: false, hold: false })\n\n let labelRef = useRef<_Data['labelRef']['current']>(null)\n let buttonRef = useRef<_Data['buttonRef']['current']>(null)\n let optionsRef = useRef<_Data['optionsRef']['current']>(null)\n\n let compare = useEvent(\n typeof by === 'string'\n ? (a, z) => {\n let property = by as unknown as keyof TActualType\n return a?.[property] === z?.[property]\n }\n : by\n )\n\n let isSelected: (value: unknown) => boolean = useCallback(\n (compareValue) =>\n match(data.mode, {\n [ValueMode.Multi]: () =>\n (value as unknown as EnsureArray).some((option) => compare(option, compareValue)),\n [ValueMode.Single]: () => compare(value as TType, compareValue),\n }),\n [value]\n )\n\n let data = useMemo<_Data>(\n () => ({\n ...state,\n value,\n disabled,\n mode: multiple ? ValueMode.Multi : ValueMode.Single,\n orientation,\n compare,\n isSelected,\n optionsPropsRef,\n labelRef,\n buttonRef,\n optionsRef,\n }),\n [value, disabled, multiple, state]\n )\n\n useIsoMorphicEffect(() => {\n state.dataRef.current = data\n }, [data])\n\n // Handle outside click\n useOutsideClick(\n [data.buttonRef, data.optionsRef],\n (event, target) => {\n dispatch({ type: ActionTypes.CloseListbox })\n\n if (!isFocusableElement(target, FocusableMode.Loose)) {\n event.preventDefault()\n data.buttonRef.current?.focus()\n }\n },\n data.listboxState === ListboxStates.Open\n )\n\n let slot = useMemo>(\n () => ({ open: data.listboxState === ListboxStates.Open, disabled, value }),\n [data, disabled, value]\n )\n\n let selectOption = useEvent((id: string) => {\n let option = data.options.find((item) => item.id === id)\n if (!option) return\n\n onChange(option.dataRef.current.value)\n })\n\n let selectActiveOption = useEvent(() => {\n if (data.activeOptionIndex !== null) {\n let { dataRef, id } = data.options[data.activeOptionIndex]\n onChange(dataRef.current.value)\n\n // It could happen that the `activeOptionIndex` stored in state is actually null,\n // but we are getting the fallback active option back instead.\n dispatch({ type: ActionTypes.GoToOption, focus: Focus.Specific, id })\n }\n })\n\n let openListbox = useEvent(() => dispatch({ type: ActionTypes.OpenListbox }))\n let closeListbox = useEvent(() => dispatch({ type: ActionTypes.CloseListbox }))\n\n let goToOption = useEvent((focus, id, trigger) => {\n if (focus === Focus.Specific) {\n return dispatch({ type: ActionTypes.GoToOption, focus: Focus.Specific, id: id!, trigger })\n }\n\n return dispatch({ type: ActionTypes.GoToOption, focus, trigger })\n })\n\n let registerOption = useEvent((id, dataRef) => {\n dispatch({ type: ActionTypes.RegisterOption, id, dataRef })\n return () => dispatch({ type: ActionTypes.UnregisterOption, id })\n })\n\n let registerLabel = useEvent((id) => {\n dispatch({ type: ActionTypes.RegisterLabel, id })\n return () => dispatch({ type: ActionTypes.RegisterLabel, id: null })\n })\n\n let onChange = useEvent((value: unknown) => {\n return match(data.mode, {\n [ValueMode.Single]() {\n return theirOnChange?.(value as TType)\n },\n [ValueMode.Multi]() {\n let copy = (data.value as TActualType[]).slice()\n\n let idx = copy.findIndex((item) => compare(item, value as TActualType))\n if (idx === -1) {\n copy.push(value as TActualType)\n } else {\n copy.splice(idx, 1)\n }\n\n return theirOnChange?.(copy as unknown as TType[])\n },\n })\n })\n\n let search = useEvent((value: string) => dispatch({ type: ActionTypes.Search, value }))\n let clearSearch = useEvent(() => dispatch({ type: ActionTypes.ClearSearch }))\n\n let actions = useMemo<_Actions>(\n () => ({\n onChange,\n registerOption,\n registerLabel,\n goToOption,\n closeListbox,\n openListbox,\n selectActiveOption,\n selectOption,\n search,\n clearSearch,\n }),\n []\n )\n\n let ourProps = { ref: listboxRef }\n\n let form = useRef(null)\n let d = useDisposables()\n useEffect(() => {\n if (!form.current) return\n if (defaultValue === undefined) return\n\n d.addEventListener(form.current, 'reset', () => {\n onChange(defaultValue)\n })\n }, [form, onChange /* Explicitly ignoring `defaultValue` */])\n\n return (\n \n \n \n {name != null &&\n value != null &&\n objectToFormEntries({ [name]: value }).map(([name, value], idx) => (\n {\n form.current = element?.closest('form') ?? null\n }\n : undefined\n }\n {...compact({\n key: name,\n as: 'input',\n type: 'hidden',\n hidden: true,\n readOnly: true,\n name,\n value,\n })}\n />\n ))}\n {render({ ourProps, theirProps, slot, defaultTag: DEFAULT_LISTBOX_TAG, name: 'Listbox' })}\n \n \n \n )\n})\n\n// ---\n\nlet DEFAULT_BUTTON_TAG = 'button' as const\ninterface ButtonRenderPropArg {\n open: boolean\n disabled: boolean\n value: any\n}\ntype ButtonPropsWeControl =\n | 'type'\n | 'aria-haspopup'\n | 'aria-controls'\n | 'aria-expanded'\n | 'aria-labelledby'\n | 'disabled'\n | 'onKeyDown'\n | 'onClick'\n\nlet Button = forwardRefWithAs(function Button(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-listbox-button-${internalId}`, ...theirProps } = props\n let data = useData('Listbox.Button')\n let actions = useActions('Listbox.Button')\n let buttonRef = useSyncRefs(data.buttonRef, ref)\n\n let d = useDisposables()\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-13\n\n case Keys.Space:\n case Keys.Enter:\n case Keys.ArrowDown:\n event.preventDefault()\n actions.openListbox()\n d.nextFrame(() => {\n if (!data.value) actions.goToOption(Focus.First)\n })\n break\n\n case Keys.ArrowUp:\n event.preventDefault()\n actions.openListbox()\n d.nextFrame(() => {\n if (!data.value) actions.goToOption(Focus.Last)\n })\n break\n }\n })\n\n let handleKeyUp = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n case Keys.Space:\n // Required for firefox, event.preventDefault() in handleKeyDown for\n // the Space key doesn't cancel the handleKeyUp, which in turn\n // triggers a *click*.\n event.preventDefault()\n break\n }\n })\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n if (data.listboxState === ListboxStates.Open) {\n actions.closeListbox()\n d.nextFrame(() => data.buttonRef.current?.focus({ preventScroll: true }))\n } else {\n event.preventDefault()\n actions.openListbox()\n }\n })\n\n let labelledby = useComputed(() => {\n if (!data.labelId) return undefined\n return [data.labelId, id].join(' ')\n }, [data.labelId, id])\n\n let slot = useMemo(\n () => ({\n open: data.listboxState === ListboxStates.Open,\n disabled: data.disabled,\n value: data.value,\n }),\n [data]\n )\n\n let ourProps = {\n ref: buttonRef,\n id,\n type: useResolveButtonType(props, data.buttonRef),\n 'aria-haspopup': 'listbox',\n 'aria-controls': data.optionsRef.current?.id,\n 'aria-expanded': data.disabled ? undefined : data.listboxState === ListboxStates.Open,\n 'aria-labelledby': labelledby,\n disabled: data.disabled,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n onClick: handleClick,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_BUTTON_TAG,\n name: 'Listbox.Button',\n })\n})\n\n// ---\n\nlet DEFAULT_LABEL_TAG = 'label' as const\ninterface LabelRenderPropArg {\n open: boolean\n disabled: boolean\n}\ntype LabelPropsWeControl = 'ref' | 'onClick'\n\nlet Label = forwardRefWithAs(function Label(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-listbox-label-${internalId}`, ...theirProps } = props\n let data = useData('Listbox.Label')\n let actions = useActions('Listbox.Label')\n let labelRef = useSyncRefs(data.labelRef, ref)\n\n useIsoMorphicEffect(() => actions.registerLabel(id), [id])\n\n let handleClick = useEvent(() => data.buttonRef.current?.focus({ preventScroll: true }))\n\n let slot = useMemo(\n () => ({ open: data.listboxState === ListboxStates.Open, disabled: data.disabled }),\n [data]\n )\n let ourProps = { ref: labelRef, id, onClick: handleClick }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_LABEL_TAG,\n name: 'Listbox.Label',\n })\n})\n\n// ---\n\nlet DEFAULT_OPTIONS_TAG = 'ul' as const\ninterface OptionsRenderPropArg {\n open: boolean\n}\ntype OptionsPropsWeControl =\n | 'aria-activedescendant'\n | 'aria-labelledby'\n | 'aria-orientation'\n | 'onKeyDown'\n | 'role'\n | 'tabIndex'\n\nlet OptionsRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet Options = forwardRefWithAs(function Options<\n TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG\n>(\n props: Props &\n PropsForFeatures,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-listbox-options-${internalId}`, ...theirProps } = props\n let data = useData('Listbox.Options')\n let actions = useActions('Listbox.Options')\n let optionsRef = useSyncRefs(data.optionsRef, ref)\n\n let d = useDisposables()\n let searchDisposables = useDisposables()\n\n let usesOpenClosedState = useOpenClosed()\n let visible = (() => {\n if (usesOpenClosedState !== null) {\n return usesOpenClosedState === State.Open\n }\n\n return data.listboxState === ListboxStates.Open\n })()\n\n useEffect(() => {\n let container = data.optionsRef.current\n if (!container) return\n if (data.listboxState !== ListboxStates.Open) return\n if (container === getOwnerDocument(container)?.activeElement) return\n\n container.focus({ preventScroll: true })\n }, [data.listboxState, data.optionsRef])\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n searchDisposables.dispose()\n\n switch (event.key) {\n // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-12\n\n // @ts-expect-error Fallthrough is expected here\n case Keys.Space:\n if (data.searchQuery !== '') {\n event.preventDefault()\n event.stopPropagation()\n return actions.search(event.key)\n }\n // When in type ahead mode, fallthrough\n case Keys.Enter:\n event.preventDefault()\n event.stopPropagation()\n\n if (data.activeOptionIndex !== null) {\n let { dataRef } = data.options[data.activeOptionIndex]\n actions.onChange(dataRef.current.value)\n }\n if (data.mode === ValueMode.Single) {\n actions.closeListbox()\n disposables().nextFrame(() => data.buttonRef.current?.focus({ preventScroll: true }))\n }\n break\n\n case match(data.orientation, { vertical: Keys.ArrowDown, horizontal: Keys.ArrowRight }):\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.Next)\n\n case match(data.orientation, { vertical: Keys.ArrowUp, horizontal: Keys.ArrowLeft }):\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.Previous)\n\n case Keys.Home:\n case Keys.PageUp:\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.First)\n\n case Keys.End:\n case Keys.PageDown:\n event.preventDefault()\n event.stopPropagation()\n return actions.goToOption(Focus.Last)\n\n case Keys.Escape:\n event.preventDefault()\n event.stopPropagation()\n actions.closeListbox()\n return d.nextFrame(() => data.buttonRef.current?.focus({ preventScroll: true }))\n\n case Keys.Tab:\n event.preventDefault()\n event.stopPropagation()\n break\n\n default:\n if (event.key.length === 1) {\n actions.search(event.key)\n searchDisposables.setTimeout(() => actions.clearSearch(), 350)\n }\n break\n }\n })\n\n let labelledby = useComputed(\n () => data.labelRef.current?.id ?? data.buttonRef.current?.id,\n [data.labelRef.current, data.buttonRef.current]\n )\n\n let slot = useMemo(\n () => ({ open: data.listboxState === ListboxStates.Open }),\n [data]\n )\n\n let ourProps = {\n 'aria-activedescendant':\n data.activeOptionIndex === null ? undefined : data.options[data.activeOptionIndex]?.id,\n 'aria-multiselectable': data.mode === ValueMode.Multi ? true : undefined,\n 'aria-labelledby': labelledby,\n 'aria-orientation': data.orientation,\n id,\n onKeyDown: handleKeyDown,\n role: 'listbox',\n tabIndex: 0,\n ref: optionsRef,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OPTIONS_TAG,\n features: OptionsRenderFeatures,\n visible,\n name: 'Listbox.Options',\n })\n})\n\n// ---\n\nlet DEFAULT_OPTION_TAG = 'li' as const\ninterface OptionRenderPropArg {\n active: boolean\n selected: boolean\n disabled: boolean\n}\ntype ListboxOptionPropsWeControl =\n | 'role'\n | 'tabIndex'\n | 'aria-disabled'\n | 'aria-selected'\n | 'onPointerLeave'\n | 'onMouseLeave'\n | 'onPointerMove'\n | 'onMouseMove'\n | 'onFocus'\n\nlet Option = forwardRefWithAs(function Option<\n TTag extends ElementType = typeof DEFAULT_OPTION_TAG,\n // TODO: One day we will be able to infer this type from the generic in Listbox itself.\n // But today is not that day..\n TType = Parameters[0]['value']\n>(\n props: Props & {\n disabled?: boolean\n value: TType\n },\n ref: Ref\n) {\n let internalId = useId()\n let {\n id = `headlessui-listbox-option-${internalId}`,\n disabled = false,\n value,\n ...theirProps\n } = props\n let data = useData('Listbox.Option')\n let actions = useActions('Listbox.Option')\n\n let active =\n data.activeOptionIndex !== null ? data.options[data.activeOptionIndex].id === id : false\n\n let selected = data.isSelected(value)\n let internalOptionRef = useRef(null)\n let bag = useLatestValue['current']>({\n disabled,\n value,\n domRef: internalOptionRef,\n get textValue() {\n return internalOptionRef.current?.textContent?.toLowerCase()\n },\n })\n let optionRef = useSyncRefs(ref, internalOptionRef)\n\n useIsoMorphicEffect(() => {\n if (data.listboxState !== ListboxStates.Open) return\n if (!active) return\n if (data.activationTrigger === ActivationTrigger.Pointer) return\n let d = disposables()\n d.requestAnimationFrame(() => {\n internalOptionRef.current?.scrollIntoView?.({ block: 'nearest' })\n })\n return d.dispose\n }, [internalOptionRef, active, data.listboxState, data.activationTrigger, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ data.activeOptionIndex])\n\n useIsoMorphicEffect(() => actions.registerOption(id, bag), [bag, id])\n\n let handleClick = useEvent((event: { preventDefault: Function }) => {\n if (disabled) return event.preventDefault()\n actions.onChange(value)\n if (data.mode === ValueMode.Single) {\n actions.closeListbox()\n disposables().nextFrame(() => data.buttonRef.current?.focus({ preventScroll: true }))\n }\n })\n\n let handleFocus = useEvent(() => {\n if (disabled) return actions.goToOption(Focus.Nothing)\n actions.goToOption(Focus.Specific, id)\n })\n\n let pointer = useTrackedPointer()\n\n let handleEnter = useEvent((evt) => pointer.update(evt))\n\n let handleMove = useEvent((evt) => {\n if (!pointer.wasMoved(evt)) return\n if (disabled) return\n if (active) return\n actions.goToOption(Focus.Specific, id, ActivationTrigger.Pointer)\n })\n\n let handleLeave = useEvent((evt) => {\n if (!pointer.wasMoved(evt)) return\n if (disabled) return\n if (!active) return\n actions.goToOption(Focus.Nothing)\n })\n\n let slot = useMemo(\n () => ({ active, selected, disabled }),\n [active, selected, disabled]\n )\n let ourProps = {\n id,\n ref: optionRef,\n role: 'option',\n tabIndex: disabled === true ? undefined : -1,\n 'aria-disabled': disabled === true ? true : undefined,\n // According to the WAI-ARIA best practices, we should use aria-checked for\n // multi-select,but Voice-Over disagrees. So we use aria-checked instead for\n // both single and multi-select.\n 'aria-selected': selected,\n disabled: undefined, // Never forward the `disabled` prop\n onClick: handleClick,\n onFocus: handleFocus,\n onPointerEnter: handleEnter,\n onMouseEnter: handleEnter,\n onPointerMove: handleMove,\n onMouseMove: handleMove,\n onPointerLeave: handleLeave,\n onMouseLeave: handleLeave,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OPTION_TAG,\n name: 'Listbox.Option',\n })\n})\n\n// ---\n\nexport let Listbox = Object.assign(ListboxRoot, { Button, Label, Options, Option })\n", "// WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#menubutton\nimport React, {\n Fragment,\n createContext,\n createRef,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n\n // Types\n Dispatch,\n ElementType,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { match } from '../../utils/match'\nimport { forwardRefWithAs, render, Features, PropsForFeatures } from '../../utils/render'\nimport { disposables } from '../../utils/disposables'\nimport { useDisposables } from '../../hooks/use-disposables'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useId } from '../../hooks/use-id'\nimport { Keys } from '../keyboard'\nimport { Focus, calculateActiveIndex } from '../../utils/calculate-active-index'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport {\n isFocusableElement,\n FocusableMode,\n sortByDomNode,\n Focus as FocusManagementFocus,\n focusFrom,\n restoreFocusIfNecessary,\n} from '../../utils/focus-management'\nimport { useOutsideClick } from '../../hooks/use-outside-click'\nimport { useTreeWalker } from '../../hooks/use-tree-walker'\nimport { useOpenClosed, State, OpenClosedProvider } from '../../internal/open-closed'\nimport { useResolveButtonType } from '../../hooks/use-resolve-button-type'\nimport { useOwnerDocument } from '../../hooks/use-owner'\nimport { useEvent } from '../../hooks/use-event'\nimport { useTrackedPointer } from '../../hooks/use-tracked-pointer'\n\nenum MenuStates {\n Open,\n Closed,\n}\n\nenum ActivationTrigger {\n Pointer,\n Other,\n}\n\ntype MenuItemDataRef = MutableRefObject<{\n textValue?: string\n disabled: boolean\n domRef: MutableRefObject\n}>\n\ninterface StateDefinition {\n menuState: MenuStates\n buttonRef: MutableRefObject\n itemsRef: MutableRefObject\n items: { id: string; dataRef: MenuItemDataRef }[]\n searchQuery: string\n activeItemIndex: number | null\n activationTrigger: ActivationTrigger\n}\n\nenum ActionTypes {\n OpenMenu,\n CloseMenu,\n\n GoToItem,\n Search,\n ClearSearch,\n RegisterItem,\n UnregisterItem,\n}\n\nfunction adjustOrderedState(\n state: StateDefinition,\n adjustment: (items: StateDefinition['items']) => StateDefinition['items'] = (i) => i\n) {\n let currentActiveItem = state.activeItemIndex !== null ? state.items[state.activeItemIndex] : null\n\n let sortedItems = sortByDomNode(\n adjustment(state.items.slice()),\n (item) => item.dataRef.current.domRef.current\n )\n\n // If we inserted an item before the current active item then the active item index\n // would be wrong. To fix this, we will re-lookup the correct index.\n let adjustedActiveItemIndex = currentActiveItem ? sortedItems.indexOf(currentActiveItem) : null\n\n // Reset to `null` in case the currentActiveItem was removed.\n if (adjustedActiveItemIndex === -1) {\n adjustedActiveItemIndex = null\n }\n\n return {\n items: sortedItems,\n activeItemIndex: adjustedActiveItemIndex,\n }\n}\n\ntype Actions =\n | { type: ActionTypes.CloseMenu }\n | { type: ActionTypes.OpenMenu }\n | { type: ActionTypes.GoToItem; focus: Focus.Specific; id: string; trigger?: ActivationTrigger }\n | {\n type: ActionTypes.GoToItem\n focus: Exclude\n trigger?: ActivationTrigger\n }\n | { type: ActionTypes.Search; value: string }\n | { type: ActionTypes.ClearSearch }\n | { type: ActionTypes.RegisterItem; id: string; dataRef: MenuItemDataRef }\n | { type: ActionTypes.UnregisterItem; id: string }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract\n ) => StateDefinition\n} = {\n [ActionTypes.CloseMenu](state) {\n if (state.menuState === MenuStates.Closed) return state\n return { ...state, activeItemIndex: null, menuState: MenuStates.Closed }\n },\n [ActionTypes.OpenMenu](state) {\n if (state.menuState === MenuStates.Open) return state\n return { ...state, menuState: MenuStates.Open }\n },\n [ActionTypes.GoToItem]: (state, action) => {\n let adjustedState = adjustOrderedState(state)\n let activeItemIndex = calculateActiveIndex(action, {\n resolveItems: () => adjustedState.items,\n resolveActiveIndex: () => adjustedState.activeItemIndex,\n resolveId: (item) => item.id,\n resolveDisabled: (item) => item.dataRef.current.disabled,\n })\n\n return {\n ...state,\n ...adjustedState,\n searchQuery: '',\n activeItemIndex,\n activationTrigger: action.trigger ?? ActivationTrigger.Other,\n }\n },\n [ActionTypes.Search]: (state, action) => {\n let wasAlreadySearching = state.searchQuery !== ''\n let offset = wasAlreadySearching ? 0 : 1\n let searchQuery = state.searchQuery + action.value.toLowerCase()\n\n let reOrderedItems =\n state.activeItemIndex !== null\n ? state.items\n .slice(state.activeItemIndex + offset)\n .concat(state.items.slice(0, state.activeItemIndex + offset))\n : state.items\n\n let matchingItem = reOrderedItems.find(\n (item) =>\n item.dataRef.current.textValue?.startsWith(searchQuery) && !item.dataRef.current.disabled\n )\n\n let matchIdx = matchingItem ? state.items.indexOf(matchingItem) : -1\n if (matchIdx === -1 || matchIdx === state.activeItemIndex) return { ...state, searchQuery }\n return {\n ...state,\n searchQuery,\n activeItemIndex: matchIdx,\n activationTrigger: ActivationTrigger.Other,\n }\n },\n [ActionTypes.ClearSearch](state) {\n if (state.searchQuery === '') return state\n return { ...state, searchQuery: '', searchActiveItemIndex: null }\n },\n [ActionTypes.RegisterItem]: (state, action) => {\n let adjustedState = adjustOrderedState(state, (items) => [\n ...items,\n { id: action.id, dataRef: action.dataRef },\n ])\n\n return { ...state, ...adjustedState }\n },\n [ActionTypes.UnregisterItem]: (state, action) => {\n let adjustedState = adjustOrderedState(state, (items) => {\n let idx = items.findIndex((a) => a.id === action.id)\n if (idx !== -1) items.splice(idx, 1)\n return items\n })\n\n return {\n ...state,\n ...adjustedState,\n activationTrigger: ActivationTrigger.Other,\n }\n },\n}\n\nlet MenuContext = createContext<[StateDefinition, Dispatch] | null>(null)\nMenuContext.displayName = 'MenuContext'\n\nfunction useMenuContext(component: string) {\n let context = useContext(MenuContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useMenuContext)\n throw err\n }\n return context\n}\n\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_MENU_TAG = Fragment\ninterface MenuRenderPropArg {\n open: boolean\n close: () => void\n}\n\nlet MenuRoot = forwardRefWithAs(function Menu(\n props: Props,\n ref: Ref\n) {\n let reducerBag = useReducer(stateReducer, {\n menuState: MenuStates.Closed,\n buttonRef: createRef(),\n itemsRef: createRef(),\n items: [],\n searchQuery: '',\n activeItemIndex: null,\n activationTrigger: ActivationTrigger.Other,\n } as StateDefinition)\n let [{ menuState, itemsRef, buttonRef }, dispatch] = reducerBag\n let menuRef = useSyncRefs(ref)\n\n // Handle outside click\n useOutsideClick(\n [buttonRef, itemsRef],\n (event, target) => {\n dispatch({ type: ActionTypes.CloseMenu })\n\n if (!isFocusableElement(target, FocusableMode.Loose)) {\n event.preventDefault()\n buttonRef.current?.focus()\n }\n },\n menuState === MenuStates.Open\n )\n\n let close = useEvent(() => {\n dispatch({ type: ActionTypes.CloseMenu })\n })\n\n let slot = useMemo(\n () => ({ open: menuState === MenuStates.Open, close }),\n [menuState, close]\n )\n\n let theirProps = props\n let ourProps = { ref: menuRef }\n\n return (\n \n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_MENU_TAG,\n name: 'Menu',\n })}\n \n \n )\n})\n\n// ---\n\nlet DEFAULT_BUTTON_TAG = 'button' as const\ninterface ButtonRenderPropArg {\n open: boolean\n}\ntype ButtonPropsWeControl =\n | 'type'\n | 'aria-haspopup'\n | 'aria-controls'\n | 'aria-expanded'\n | 'onKeyDown'\n | 'onClick'\n\nlet Button = forwardRefWithAs(function Button(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-menu-button-${internalId}`, ...theirProps } = props\n let [state, dispatch] = useMenuContext('Menu.Button')\n let buttonRef = useSyncRefs(state.buttonRef, ref)\n\n let d = useDisposables()\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-13\n\n case Keys.Space:\n case Keys.Enter:\n case Keys.ArrowDown:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.OpenMenu })\n d.nextFrame(() => dispatch({ type: ActionTypes.GoToItem, focus: Focus.First }))\n break\n\n case Keys.ArrowUp:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.OpenMenu })\n d.nextFrame(() => dispatch({ type: ActionTypes.GoToItem, focus: Focus.Last }))\n break\n }\n })\n\n let handleKeyUp = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n case Keys.Space:\n // Required for firefox, event.preventDefault() in handleKeyDown for\n // the Space key doesn't cancel the handleKeyUp, which in turn\n // triggers a *click*.\n event.preventDefault()\n break\n }\n })\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n if (props.disabled) return\n if (state.menuState === MenuStates.Open) {\n dispatch({ type: ActionTypes.CloseMenu })\n d.nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))\n } else {\n event.preventDefault()\n dispatch({ type: ActionTypes.OpenMenu })\n }\n })\n\n let slot = useMemo(\n () => ({ open: state.menuState === MenuStates.Open }),\n [state]\n )\n let ourProps = {\n ref: buttonRef,\n id,\n type: useResolveButtonType(props, state.buttonRef),\n 'aria-haspopup': 'menu',\n 'aria-controls': state.itemsRef.current?.id,\n 'aria-expanded': props.disabled ? undefined : state.menuState === MenuStates.Open,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n onClick: handleClick,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_BUTTON_TAG,\n name: 'Menu.Button',\n })\n})\n\n// ---\n\nlet DEFAULT_ITEMS_TAG = 'div' as const\ninterface ItemsRenderPropArg {\n open: boolean\n}\ntype ItemsPropsWeControl =\n | 'aria-activedescendant'\n | 'aria-labelledby'\n | 'onKeyDown'\n | 'role'\n | 'tabIndex'\n\nlet ItemsRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet Items = forwardRefWithAs(function Items(\n props: Props &\n PropsForFeatures,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-menu-items-${internalId}`, ...theirProps } = props\n let [state, dispatch] = useMenuContext('Menu.Items')\n let itemsRef = useSyncRefs(state.itemsRef, ref)\n let ownerDocument = useOwnerDocument(state.itemsRef)\n\n let searchDisposables = useDisposables()\n\n let usesOpenClosedState = useOpenClosed()\n let visible = (() => {\n if (usesOpenClosedState !== null) {\n return usesOpenClosedState === State.Open\n }\n\n return state.menuState === MenuStates.Open\n })()\n\n useEffect(() => {\n let container = state.itemsRef.current\n if (!container) return\n if (state.menuState !== MenuStates.Open) return\n if (container === ownerDocument?.activeElement) return\n\n container.focus({ preventScroll: true })\n }, [state.menuState, state.itemsRef, ownerDocument])\n\n useTreeWalker({\n container: state.itemsRef.current,\n enabled: state.menuState === MenuStates.Open,\n accept(node) {\n if (node.getAttribute('role') === 'menuitem') return NodeFilter.FILTER_REJECT\n if (node.hasAttribute('role')) return NodeFilter.FILTER_SKIP\n return NodeFilter.FILTER_ACCEPT\n },\n walk(node) {\n node.setAttribute('role', 'none')\n },\n })\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n searchDisposables.dispose()\n\n switch (event.key) {\n // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-12\n\n // @ts-expect-error Fallthrough is expected here\n case Keys.Space:\n if (state.searchQuery !== '') {\n event.preventDefault()\n event.stopPropagation()\n return dispatch({ type: ActionTypes.Search, value: event.key })\n }\n // When in type ahead mode, fallthrough\n case Keys.Enter:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.CloseMenu })\n if (state.activeItemIndex !== null) {\n let { dataRef } = state.items[state.activeItemIndex]\n dataRef.current?.domRef.current?.click()\n }\n restoreFocusIfNecessary(state.buttonRef.current)\n break\n\n case Keys.ArrowDown:\n event.preventDefault()\n event.stopPropagation()\n return dispatch({ type: ActionTypes.GoToItem, focus: Focus.Next })\n\n case Keys.ArrowUp:\n event.preventDefault()\n event.stopPropagation()\n return dispatch({ type: ActionTypes.GoToItem, focus: Focus.Previous })\n\n case Keys.Home:\n case Keys.PageUp:\n event.preventDefault()\n event.stopPropagation()\n return dispatch({ type: ActionTypes.GoToItem, focus: Focus.First })\n\n case Keys.End:\n case Keys.PageDown:\n event.preventDefault()\n event.stopPropagation()\n return dispatch({ type: ActionTypes.GoToItem, focus: Focus.Last })\n\n case Keys.Escape:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.CloseMenu })\n disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))\n break\n\n case Keys.Tab:\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.CloseMenu })\n disposables().nextFrame(() => {\n focusFrom(\n state.buttonRef.current!,\n event.shiftKey ? FocusManagementFocus.Previous : FocusManagementFocus.Next\n )\n })\n break\n\n default:\n if (event.key.length === 1) {\n dispatch({ type: ActionTypes.Search, value: event.key })\n searchDisposables.setTimeout(() => dispatch({ type: ActionTypes.ClearSearch }), 350)\n }\n break\n }\n })\n\n let handleKeyUp = useEvent((event: ReactKeyboardEvent) => {\n switch (event.key) {\n case Keys.Space:\n // Required for firefox, event.preventDefault() in handleKeyDown for\n // the Space key doesn't cancel the handleKeyUp, which in turn\n // triggers a *click*.\n event.preventDefault()\n break\n }\n })\n\n let slot = useMemo(\n () => ({ open: state.menuState === MenuStates.Open }),\n [state]\n )\n\n let ourProps = {\n 'aria-activedescendant':\n state.activeItemIndex === null ? undefined : state.items[state.activeItemIndex]?.id,\n 'aria-labelledby': state.buttonRef.current?.id,\n id,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n role: 'menu',\n tabIndex: 0,\n ref: itemsRef,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_ITEMS_TAG,\n features: ItemsRenderFeatures,\n visible,\n name: 'Menu.Items',\n })\n})\n\n// ---\n\nlet DEFAULT_ITEM_TAG = Fragment\ninterface ItemRenderPropArg {\n active: boolean\n disabled: boolean\n close: () => void\n}\ntype MenuItemPropsWeControl =\n | 'role'\n | 'tabIndex'\n | 'aria-disabled'\n | 'onPointerLeave'\n | 'onPointerMove'\n | 'onMouseLeave'\n | 'onMouseMove'\n | 'onFocus'\n\nlet Item = forwardRefWithAs(function Item(\n props: Props & {\n disabled?: boolean\n },\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-menu-item-${internalId}`, disabled = false, ...theirProps } = props\n let [state, dispatch] = useMenuContext('Menu.Item')\n let active = state.activeItemIndex !== null ? state.items[state.activeItemIndex].id === id : false\n let internalItemRef = useRef(null)\n let itemRef = useSyncRefs(ref, internalItemRef)\n\n useIsoMorphicEffect(() => {\n if (state.menuState !== MenuStates.Open) return\n if (!active) return\n if (state.activationTrigger === ActivationTrigger.Pointer) return\n let d = disposables()\n d.requestAnimationFrame(() => {\n internalItemRef.current?.scrollIntoView?.({ block: 'nearest' })\n })\n return d.dispose\n }, [internalItemRef, active, state.menuState, state.activationTrigger, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeItemIndex])\n\n let bag = useRef({ disabled, domRef: internalItemRef })\n\n useIsoMorphicEffect(() => {\n bag.current.disabled = disabled\n }, [bag, disabled])\n useIsoMorphicEffect(() => {\n bag.current.textValue = internalItemRef.current?.textContent?.toLowerCase()\n }, [bag, internalItemRef])\n\n useIsoMorphicEffect(() => {\n dispatch({ type: ActionTypes.RegisterItem, id, dataRef: bag })\n return () => dispatch({ type: ActionTypes.UnregisterItem, id })\n }, [bag, id])\n\n let close = useEvent(() => {\n dispatch({ type: ActionTypes.CloseMenu })\n })\n\n let handleClick = useEvent((event: MouseEvent) => {\n if (disabled) return event.preventDefault()\n dispatch({ type: ActionTypes.CloseMenu })\n restoreFocusIfNecessary(state.buttonRef.current)\n })\n\n let handleFocus = useEvent(() => {\n if (disabled) return dispatch({ type: ActionTypes.GoToItem, focus: Focus.Nothing })\n dispatch({ type: ActionTypes.GoToItem, focus: Focus.Specific, id })\n })\n\n let pointer = useTrackedPointer()\n\n let handleEnter = useEvent((evt) => pointer.update(evt))\n\n let handleMove = useEvent((evt) => {\n if (!pointer.wasMoved(evt)) return\n if (disabled) return\n if (active) return\n dispatch({\n type: ActionTypes.GoToItem,\n focus: Focus.Specific,\n id,\n trigger: ActivationTrigger.Pointer,\n })\n })\n\n let handleLeave = useEvent((evt) => {\n if (!pointer.wasMoved(evt)) return\n if (disabled) return\n if (!active) return\n dispatch({ type: ActionTypes.GoToItem, focus: Focus.Nothing })\n })\n\n let slot = useMemo(\n () => ({ active, disabled, close }),\n [active, disabled, close]\n )\n let ourProps = {\n id,\n ref: itemRef,\n role: 'menuitem',\n tabIndex: disabled === true ? undefined : -1,\n 'aria-disabled': disabled === true ? true : undefined,\n disabled: undefined, // Never forward the `disabled` prop\n onClick: handleClick,\n onFocus: handleFocus,\n onPointerEnter: handleEnter,\n onMouseEnter: handleEnter,\n onPointerMove: handleMove,\n onMouseMove: handleMove,\n onPointerLeave: handleLeave,\n onMouseLeave: handleLeave,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_ITEM_TAG,\n name: 'Menu.Item',\n })\n})\n\n// ---\n\nexport let Menu = Object.assign(MenuRoot, { Button, Items, Item })\n", "import React, {\n createContext,\n createRef,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n\n // Types\n ContextType,\n Dispatch,\n ElementType,\n FocusEvent as ReactFocusEvent,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n MouseEventHandler,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { match } from '../../utils/match'\nimport { forwardRefWithAs, render, Features, PropsForFeatures } from '../../utils/render'\nimport { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useId } from '../../hooks/use-id'\nimport { Keys } from '../keyboard'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport {\n getFocusableElements,\n Focus,\n focusIn,\n isFocusableElement,\n FocusableMode,\n} from '../../utils/focus-management'\nimport { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'\nimport { useResolveButtonType } from '../../hooks/use-resolve-button-type'\nimport { useOutsideClick } from '../../hooks/use-outside-click'\nimport { getOwnerDocument } from '../../utils/owner'\nimport { useOwnerDocument } from '../../hooks/use-owner'\nimport { useEventListener } from '../../hooks/use-event-listener'\nimport { Hidden, Features as HiddenFeatures } from '../../internal/hidden'\nimport { useEvent } from '../../hooks/use-event'\nimport { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab-direction'\nimport { microTask } from '../../utils/micro-task'\nimport { useLatestValue } from '../../hooks/use-latest-value'\n\ntype MouseEvent = Parameters>[0]\n\nenum PopoverStates {\n Open,\n Closed,\n}\n\ninterface StateDefinition {\n popoverState: PopoverStates\n\n buttons: string[]\n\n button: HTMLElement | null\n buttonId: string | null\n panel: HTMLElement | null\n panelId: string | null\n\n beforePanelSentinel: MutableRefObject\n afterPanelSentinel: MutableRefObject\n}\n\nenum ActionTypes {\n TogglePopover,\n ClosePopover,\n\n SetButton,\n SetButtonId,\n SetPanel,\n SetPanelId,\n}\n\ntype Actions =\n | { type: ActionTypes.TogglePopover }\n | { type: ActionTypes.ClosePopover }\n | { type: ActionTypes.SetButton; button: HTMLElement | null }\n | { type: ActionTypes.SetButtonId; buttonId: string | null }\n | { type: ActionTypes.SetPanel; panel: HTMLElement | null }\n | { type: ActionTypes.SetPanelId; panelId: string | null }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract\n ) => StateDefinition\n} = {\n [ActionTypes.TogglePopover]: (state) => ({\n ...state,\n popoverState: match(state.popoverState, {\n [PopoverStates.Open]: PopoverStates.Closed,\n [PopoverStates.Closed]: PopoverStates.Open,\n }),\n }),\n [ActionTypes.ClosePopover](state) {\n if (state.popoverState === PopoverStates.Closed) return state\n return { ...state, popoverState: PopoverStates.Closed }\n },\n [ActionTypes.SetButton](state, action) {\n if (state.button === action.button) return state\n return { ...state, button: action.button }\n },\n [ActionTypes.SetButtonId](state, action) {\n if (state.buttonId === action.buttonId) return state\n return { ...state, buttonId: action.buttonId }\n },\n [ActionTypes.SetPanel](state, action) {\n if (state.panel === action.panel) return state\n return { ...state, panel: action.panel }\n },\n [ActionTypes.SetPanelId](state, action) {\n if (state.panelId === action.panelId) return state\n return { ...state, panelId: action.panelId }\n },\n}\n\nlet PopoverContext = createContext<[StateDefinition, Dispatch] | null>(null)\nPopoverContext.displayName = 'PopoverContext'\n\nfunction usePopoverContext(component: string) {\n let context = useContext(PopoverContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, usePopoverContext)\n throw err\n }\n return context\n}\n\nlet PopoverAPIContext = createContext<{\n close(\n focusableElement?: HTMLElement | MutableRefObject | MouseEvent\n ): void\n isPortalled: boolean\n} | null>(null)\nPopoverAPIContext.displayName = 'PopoverAPIContext'\n\nfunction usePopoverAPIContext(component: string) {\n let context = useContext(PopoverAPIContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, usePopoverAPIContext)\n throw err\n }\n return context\n}\n\nlet PopoverGroupContext = createContext<{\n registerPopover(registerbag: PopoverRegisterBag): void\n unregisterPopover(registerbag: PopoverRegisterBag): void\n isFocusWithinPopoverGroup(): boolean\n closeOthers(buttonId: string): void\n} | null>(null)\nPopoverGroupContext.displayName = 'PopoverGroupContext'\n\nfunction usePopoverGroupContext() {\n return useContext(PopoverGroupContext)\n}\n\nlet PopoverPanelContext = createContext(null)\nPopoverPanelContext.displayName = 'PopoverPanelContext'\n\nfunction usePopoverPanelContext() {\n return useContext(PopoverPanelContext)\n}\n\ninterface PopoverRegisterBag {\n buttonId: MutableRefObject\n panelId: MutableRefObject\n close(): void\n}\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_POPOVER_TAG = 'div' as const\ninterface PopoverRenderPropArg {\n open: boolean\n close(\n focusableElement?: HTMLElement | MutableRefObject | MouseEvent\n ): void\n}\n\nlet PopoverRoot = forwardRefWithAs(function Popover<\n TTag extends ElementType = typeof DEFAULT_POPOVER_TAG\n>(props: Props, ref: Ref) {\n let internalPopoverRef = useRef(null)\n let popoverRef = useSyncRefs(\n ref,\n optionalRef((ref) => {\n internalPopoverRef.current = ref\n })\n )\n\n let reducerBag = useReducer(stateReducer, {\n popoverState: PopoverStates.Closed,\n buttons: [],\n button: null,\n buttonId: null,\n panel: null,\n panelId: null,\n beforePanelSentinel: createRef(),\n afterPanelSentinel: createRef(),\n } as StateDefinition)\n let [\n { popoverState, button, buttonId, panel, panelId, beforePanelSentinel, afterPanelSentinel },\n dispatch,\n ] = reducerBag\n\n let ownerDocument = useOwnerDocument(internalPopoverRef.current ?? button)\n\n let isPortalled = useMemo(() => {\n if (!button) return false\n if (!panel) return false\n\n // We are part of a different \"root\" tree, so therefore we can consider it portalled. This is a\n // heuristic because 3rd party tools could use some form of portal, typically rendered at the\n // end of the body but we don't have an actual reference to that.\n for (let root of document.querySelectorAll('body > *')) {\n if (Number(root?.contains(button)) ^ Number(root?.contains(panel))) {\n return true\n }\n }\n\n // Use another heuristic to try and calculate wether or not the focusable elements are near\n // eachother (aka, following the default focus/tab order from the browser). If they are then it\n // doesn't really matter if they are portalled or not because we can follow the default tab\n // order. But if they are not, then we can consider it being portalled so that we can ensure\n // that tab and shift+tab (hopefully) go to the correct spot.\n let elements = getFocusableElements()\n let buttonIdx = elements.indexOf(button)\n\n let beforeIdx = (buttonIdx + elements.length - 1) % elements.length\n let afterIdx = (buttonIdx + 1) % elements.length\n\n let beforeElement = elements[beforeIdx]\n let afterElement = elements[afterIdx]\n\n if (!panel.contains(beforeElement) && !panel.contains(afterElement)) {\n return true\n }\n\n // It may or may not be portalled, but we don't really know.\n return false\n }, [button, panel])\n\n let buttonIdRef = useLatestValue(buttonId)\n let panelIdRef = useLatestValue(panelId)\n\n let registerBag = useMemo(\n () => ({\n buttonId: buttonIdRef,\n panelId: panelIdRef,\n close: () => dispatch({ type: ActionTypes.ClosePopover }),\n }),\n [buttonIdRef, panelIdRef, dispatch]\n )\n\n let groupContext = usePopoverGroupContext()\n let registerPopover = groupContext?.registerPopover\n let isFocusWithinPopoverGroup = useEvent(() => {\n return (\n groupContext?.isFocusWithinPopoverGroup() ??\n (ownerDocument?.activeElement &&\n (button?.contains(ownerDocument.activeElement) ||\n panel?.contains(ownerDocument.activeElement)))\n )\n })\n\n useEffect(() => registerPopover?.(registerBag), [registerPopover, registerBag])\n\n // Handle focus out\n useEventListener(\n ownerDocument?.defaultView,\n 'focus',\n (event) => {\n if (popoverState !== PopoverStates.Open) return\n if (isFocusWithinPopoverGroup()) return\n if (!button) return\n if (!panel) return\n if (event.target === window) return\n if (beforePanelSentinel.current?.contains?.(event.target as HTMLElement)) return\n if (afterPanelSentinel.current?.contains?.(event.target as HTMLElement)) return\n\n dispatch({ type: ActionTypes.ClosePopover })\n },\n true\n )\n\n // Handle outside click\n useOutsideClick(\n [button, panel],\n (event, target) => {\n dispatch({ type: ActionTypes.ClosePopover })\n\n if (!isFocusableElement(target, FocusableMode.Loose)) {\n event.preventDefault()\n button?.focus()\n }\n },\n popoverState === PopoverStates.Open\n )\n\n let close = useEvent(\n (\n focusableElement?:\n | HTMLElement\n | MutableRefObject\n | MouseEvent\n ) => {\n dispatch({ type: ActionTypes.ClosePopover })\n\n let restoreElement = (() => {\n if (!focusableElement) return button\n if (focusableElement instanceof HTMLElement) return focusableElement\n if ('current' in focusableElement && focusableElement.current instanceof HTMLElement)\n return focusableElement.current\n\n return button\n })()\n\n restoreElement?.focus()\n }\n )\n\n let api = useMemo>(\n () => ({ close, isPortalled }),\n [close, isPortalled]\n )\n\n let slot = useMemo(\n () => ({ open: popoverState === PopoverStates.Open, close }),\n [popoverState, close]\n )\n\n let theirProps = props\n let ourProps = { ref: popoverRef }\n\n return (\n \n \n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_POPOVER_TAG,\n name: 'Popover',\n })}\n \n \n \n )\n})\n\n// ---\n\nlet DEFAULT_BUTTON_TAG = 'button' as const\ninterface ButtonRenderPropArg {\n open: boolean\n}\ntype ButtonPropsWeControl = 'type' | 'aria-expanded' | 'aria-controls' | 'onKeyDown' | 'onClick'\n\nlet Button = forwardRefWithAs(function Button(\n props: Props,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-popover-button-${internalId}`, ...theirProps } = props\n let [state, dispatch] = usePopoverContext('Popover.Button')\n let { isPortalled } = usePopoverAPIContext('Popover.Button')\n let internalButtonRef = useRef(null)\n\n let sentinelId = `headlessui-focus-sentinel-${useId()}`\n\n let groupContext = usePopoverGroupContext()\n let closeOthers = groupContext?.closeOthers\n\n let panelContext = usePopoverPanelContext()\n let isWithinPanel = panelContext === null ? false : panelContext === state.panelId\n\n useEffect(() => {\n if (isWithinPanel) return\n dispatch({ type: ActionTypes.SetButtonId, buttonId: id })\n return () => {\n dispatch({ type: ActionTypes.SetButtonId, buttonId: null })\n }\n }, [id, dispatch])\n\n let buttonRef = useSyncRefs(\n internalButtonRef,\n ref,\n isWithinPanel\n ? null\n : (button) => {\n if (button) {\n state.buttons.push(id)\n } else {\n let idx = state.buttons.indexOf(id)\n if (idx !== -1) state.buttons.splice(idx, 1)\n }\n\n if (state.buttons.length > 1) {\n console.warn(\n 'You are already using a but only 1 is supported.'\n )\n }\n\n button && dispatch({ type: ActionTypes.SetButton, button })\n }\n )\n let withinPanelButtonRef = useSyncRefs(internalButtonRef, ref)\n let ownerDocument = useOwnerDocument(internalButtonRef)\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n if (isWithinPanel) {\n if (state.popoverState === PopoverStates.Closed) return\n switch (event.key) {\n case Keys.Space:\n case Keys.Enter:\n event.preventDefault() // Prevent triggering a *click* event\n // @ts-expect-error\n event.target.click?.()\n dispatch({ type: ActionTypes.ClosePopover })\n state.button?.focus() // Re-focus the original opening Button\n break\n }\n } else {\n switch (event.key) {\n case Keys.Space:\n case Keys.Enter:\n event.preventDefault() // Prevent triggering a *click* event\n event.stopPropagation()\n if (state.popoverState === PopoverStates.Closed) closeOthers?.(state.buttonId!)\n dispatch({ type: ActionTypes.TogglePopover })\n break\n\n case Keys.Escape:\n if (state.popoverState !== PopoverStates.Open) return closeOthers?.(state.buttonId!)\n if (!internalButtonRef.current) return\n if (\n ownerDocument?.activeElement &&\n !internalButtonRef.current.contains(ownerDocument.activeElement)\n ) {\n return\n }\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.ClosePopover })\n break\n }\n }\n })\n\n let handleKeyUp = useEvent((event: ReactKeyboardEvent) => {\n if (isWithinPanel) return\n if (event.key === Keys.Space) {\n // Required for firefox, event.preventDefault() in handleKeyDown for\n // the Space key doesn't cancel the handleKeyUp, which in turn\n // triggers a *click*.\n event.preventDefault()\n }\n })\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return\n if (props.disabled) return\n if (isWithinPanel) {\n dispatch({ type: ActionTypes.ClosePopover })\n state.button?.focus() // Re-focus the original opening Button\n } else {\n event.preventDefault()\n event.stopPropagation()\n if (state.popoverState === PopoverStates.Closed) closeOthers?.(state.buttonId!)\n dispatch({ type: ActionTypes.TogglePopover })\n state.button?.focus()\n }\n })\n\n let handleMouseDown = useEvent((event: ReactMouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n })\n\n let visible = state.popoverState === PopoverStates.Open\n let slot = useMemo(() => ({ open: visible }), [visible])\n\n let type = useResolveButtonType(props, internalButtonRef)\n let ourProps = isWithinPanel\n ? {\n ref: withinPanelButtonRef,\n type,\n onKeyDown: handleKeyDown,\n onClick: handleClick,\n }\n : {\n ref: buttonRef,\n id: state.buttonId,\n type,\n 'aria-expanded': props.disabled ? undefined : state.popoverState === PopoverStates.Open,\n 'aria-controls': state.panel ? state.panelId : undefined,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n onClick: handleClick,\n onMouseDown: handleMouseDown,\n }\n\n let direction = useTabDirection()\n let handleFocus = useEvent(() => {\n let el = state.panel as HTMLElement\n if (!el) return\n\n function run() {\n match(direction.current, {\n [TabDirection.Forwards]: () => focusIn(el, Focus.First),\n [TabDirection.Backwards]: () => focusIn(el, Focus.Last),\n })\n }\n\n // TODO: Cleanup once we are using real browser tests\n if (process.env.NODE_ENV === 'test') {\n microTask(run)\n } else {\n run()\n }\n })\n\n return (\n <>\n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_BUTTON_TAG,\n name: 'Popover.Button',\n })}\n {visible && !isWithinPanel && isPortalled && (\n \n )}\n \n )\n})\n\n// ---\n\nlet DEFAULT_OVERLAY_TAG = 'div' as const\ninterface OverlayRenderPropArg {\n open: boolean\n}\ntype OverlayPropsWeControl = 'aria-hidden' | 'onClick'\n\nlet OverlayRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet Overlay = forwardRefWithAs(function Overlay<\n TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG\n>(\n props: Props &\n PropsForFeatures,\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-popover-overlay-${internalId}`, ...theirProps } = props\n let [{ popoverState }, dispatch] = usePopoverContext('Popover.Overlay')\n let overlayRef = useSyncRefs(ref)\n\n let usesOpenClosedState = useOpenClosed()\n let visible = (() => {\n if (usesOpenClosedState !== null) {\n return usesOpenClosedState === State.Open\n }\n\n return popoverState === PopoverStates.Open\n })()\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n dispatch({ type: ActionTypes.ClosePopover })\n })\n\n let slot = useMemo(\n () => ({ open: popoverState === PopoverStates.Open }),\n [popoverState]\n )\n\n let ourProps = {\n ref: overlayRef,\n id,\n 'aria-hidden': true,\n onClick: handleClick,\n }\n\n return render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OVERLAY_TAG,\n features: OverlayRenderFeatures,\n visible,\n name: 'Popover.Overlay',\n })\n})\n\n// ---\n\nlet DEFAULT_PANEL_TAG = 'div' as const\ninterface PanelRenderPropArg {\n open: boolean\n close: (focusableElement?: HTMLElement | MutableRefObject) => void\n}\ntype PanelPropsWeControl = 'onKeyDown'\n\nlet PanelRenderFeatures = Features.RenderStrategy | Features.Static\n\nlet Panel = forwardRefWithAs(function Panel(\n props: Props &\n PropsForFeatures & {\n focus?: boolean\n },\n ref: Ref\n) {\n let internalId = useId()\n let { id = `headlessui-popover-panel-${internalId}`, focus = false, ...theirProps } = props\n\n let [state, dispatch] = usePopoverContext('Popover.Panel')\n let { close, isPortalled } = usePopoverAPIContext('Popover.Panel')\n\n let beforePanelSentinelId = `headlessui-focus-sentinel-before-${useId()}`\n let afterPanelSentinelId = `headlessui-focus-sentinel-after-${useId()}`\n\n let internalPanelRef = useRef(null)\n let panelRef = useSyncRefs(internalPanelRef, ref, (panel) => {\n dispatch({ type: ActionTypes.SetPanel, panel })\n })\n let ownerDocument = useOwnerDocument(internalPanelRef)\n\n useEffect(() => {\n dispatch({ type: ActionTypes.SetPanelId, panelId: id })\n return () => {\n dispatch({ type: ActionTypes.SetPanelId, panelId: null })\n }\n }, [id, dispatch])\n\n let usesOpenClosedState = useOpenClosed()\n let visible = (() => {\n if (usesOpenClosedState !== null) {\n return usesOpenClosedState === State.Open\n }\n\n return state.popoverState === PopoverStates.Open\n })()\n\n let handleKeyDown = useEvent((event: KeyboardEvent) => {\n switch (event.key) {\n case Keys.Escape:\n if (state.popoverState !== PopoverStates.Open) return\n if (!internalPanelRef.current) return\n if (\n ownerDocument?.activeElement &&\n !internalPanelRef.current.contains(ownerDocument.activeElement)\n ) {\n return\n }\n event.preventDefault()\n event.stopPropagation()\n dispatch({ type: ActionTypes.ClosePopover })\n state.button?.focus()\n break\n }\n })\n\n // Unlink on \"unmount\" children\n useEffect(() => {\n if (props.static) return\n\n if (state.popoverState === PopoverStates.Closed && (props.unmount ?? true)) {\n dispatch({ type: ActionTypes.SetPanel, panel: null })\n }\n }, [state.popoverState, props.unmount, props.static, dispatch])\n\n // Move focus within panel\n useEffect(() => {\n if (!focus) return\n if (state.popoverState !== PopoverStates.Open) return\n if (!internalPanelRef.current) return\n\n let activeElement = ownerDocument?.activeElement as HTMLElement\n if (internalPanelRef.current.contains(activeElement)) return // Already focused within Dialog\n\n focusIn(internalPanelRef.current, Focus.First)\n }, [focus, internalPanelRef, state.popoverState])\n\n let slot = useMemo(\n () => ({ open: state.popoverState === PopoverStates.Open, close }),\n [state, close]\n )\n\n let ourProps = {\n ref: panelRef,\n id: state.panelId,\n onKeyDown: handleKeyDown,\n onBlur:\n focus && state.popoverState === PopoverStates.Open\n ? (event: ReactFocusEvent) => {\n let el = event.relatedTarget as HTMLElement\n if (!el) return\n if (!internalPanelRef.current) return\n if (internalPanelRef.current?.contains(el)) return\n\n dispatch({ type: ActionTypes.ClosePopover })\n\n if (\n state.beforePanelSentinel.current?.contains?.(el) ||\n state.afterPanelSentinel.current?.contains?.(el)\n ) {\n el.focus({ preventScroll: true })\n }\n }\n : undefined,\n tabIndex: -1,\n }\n\n let direction = useTabDirection()\n let handleBeforeFocus = useEvent(() => {\n let el = internalPanelRef.current as HTMLElement\n if (!el) return\n\n function run() {\n match(direction.current, {\n [TabDirection.Forwards]: () => {\n focusIn(el, Focus.First)\n },\n [TabDirection.Backwards]: () => {\n // Coming from the Popover.Panel (which is portalled to somewhere else). Let's redirect\n // the focus to the Popover.Button again.\n state.button?.focus({ preventScroll: true })\n },\n })\n }\n\n // TODO: Cleanup once we are using real browser tests\n if (process.env.NODE_ENV === 'test') {\n microTask(run)\n } else {\n run()\n }\n })\n\n let handleAfterFocus = useEvent(() => {\n let el = internalPanelRef.current as HTMLElement\n if (!el) return\n\n function run() {\n match(direction.current, {\n [TabDirection.Forwards]: () => {\n if (!state.button) return\n\n let elements = getFocusableElements()\n\n let idx = elements.indexOf(state.button)\n let before = elements.slice(0, idx + 1)\n let after = elements.slice(idx + 1)\n\n let combined = [...after, ...before]\n\n // Ignore sentinel buttons and items inside the panel\n for (let element of combined.slice()) {\n if (\n element?.id?.startsWith?.('headlessui-focus-sentinel-') ||\n state.panel?.contains(element)\n ) {\n let idx = combined.indexOf(element)\n if (idx !== -1) combined.splice(idx, 1)\n }\n }\n\n focusIn(combined, Focus.First, { sorted: false })\n },\n [TabDirection.Backwards]: () => focusIn(el, Focus.Last),\n })\n }\n\n // TODO: Cleanup once we are using real browser tests\n if (process.env.NODE_ENV === 'test') {\n microTask(run)\n } else {\n run()\n }\n })\n\n return (\n \n {visible && isPortalled && (\n \n )}\n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_PANEL_TAG,\n features: PanelRenderFeatures,\n visible,\n name: 'Popover.Panel',\n })}\n {visible && isPortalled && (\n \n )}\n \n )\n})\n\n// ---\n\nlet DEFAULT_GROUP_TAG = 'div' as const\ninterface GroupRenderPropArg {}\n\nlet Group = forwardRefWithAs(function Group(\n props: Props,\n ref: Ref\n) {\n let internalGroupRef = useRef(null)\n let groupRef = useSyncRefs(internalGroupRef, ref)\n let [popovers, setPopovers] = useState([])\n\n let unregisterPopover = useEvent((registerbag: PopoverRegisterBag) => {\n setPopovers((existing) => {\n let idx = existing.indexOf(registerbag)\n if (idx !== -1) {\n let clone = existing.slice()\n clone.splice(idx, 1)\n return clone\n }\n return existing\n })\n })\n\n let registerPopover = useEvent((registerbag: PopoverRegisterBag) => {\n setPopovers((existing) => [...existing, registerbag])\n return () => unregisterPopover(registerbag)\n })\n\n let isFocusWithinPopoverGroup = useEvent(() => {\n let ownerDocument = getOwnerDocument(internalGroupRef)\n if (!ownerDocument) return false\n let element = ownerDocument.activeElement\n\n if (internalGroupRef.current?.contains(element)) return true\n\n // Check if the focus is in one of the button or panel elements. This is important in case you are rendering inside a Portal.\n return popovers.some((bag) => {\n return (\n ownerDocument!.getElementById(bag.buttonId.current!)?.contains(element) ||\n ownerDocument!.getElementById(bag.panelId.current!)?.contains(element)\n )\n })\n })\n\n let closeOthers = useEvent((buttonId: string) => {\n for (let popover of popovers) {\n if (popover.buttonId.current !== buttonId) popover.close()\n }\n })\n\n let contextBag = useMemo>(\n () => ({\n registerPopover: registerPopover,\n unregisterPopover: unregisterPopover,\n isFocusWithinPopoverGroup,\n closeOthers,\n }),\n [registerPopover, unregisterPopover, isFocusWithinPopoverGroup, closeOthers]\n )\n\n let slot = useMemo(() => ({}), [])\n\n let theirProps = props\n let ourProps = { ref: groupRef }\n\n return (\n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_GROUP_TAG,\n name: 'Popover.Group',\n })}\n \n )\n})\n\n// ---\n\nexport let Popover = Object.assign(PopoverRoot, { Button, Overlay, Panel, Group })\n", "import React, {\n createContext,\n useContext,\n useMemo,\n useReducer,\n useRef,\n\n // Types\n ElementType,\n FocusEvent as ReactFocusEvent,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n MutableRefObject,\n Ref,\n useEffect,\n} from 'react'\n\nimport { Props, Expand } from '../../types'\nimport { forwardRefWithAs, render, compact } from '../../utils/render'\nimport { useId } from '../../hooks/use-id'\nimport { match } from '../../utils/match'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { Keys } from '../../components/keyboard'\nimport { focusIn, Focus, FocusResult, sortByDomNode } from '../../utils/focus-management'\nimport { useFlags } from '../../hooks/use-flags'\nimport { Label, useLabels } from '../../components/label/label'\nimport { Description, useDescriptions } from '../../components/description/description'\nimport { useTreeWalker } from '../../hooks/use-tree-walker'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { Hidden, Features as HiddenFeatures } from '../../internal/hidden'\nimport { attemptSubmit, objectToFormEntries } from '../../utils/form'\nimport { getOwnerDocument } from '../../utils/owner'\nimport { useEvent } from '../../hooks/use-event'\nimport { useControllable } from '../../hooks/use-controllable'\nimport { isDisabledReactIssue7711 } from '../../utils/bugs'\nimport { useLatestValue } from '../../hooks/use-latest-value'\nimport { useDisposables } from '../../hooks/use-disposables'\n\ninterface Option {\n id: string\n element: MutableRefObject\n propsRef: MutableRefObject<{ value: T; disabled: boolean }>\n}\n\ninterface StateDefinition {\n options: Option[]\n}\n\nenum ActionTypes {\n RegisterOption,\n UnregisterOption,\n}\n\ntype Actions =\n | Expand<{ type: ActionTypes.RegisterOption } & Option>\n | { type: ActionTypes.UnregisterOption; id: Option['id'] }\n\nlet reducers: {\n [P in ActionTypes]: (\n state: StateDefinition,\n action: Extract\n ) => StateDefinition\n} = {\n [ActionTypes.RegisterOption](state, action) {\n let nextOptions = [\n ...state.options,\n { id: action.id, element: action.element, propsRef: action.propsRef },\n ]\n\n return {\n ...state,\n options: sortByDomNode(nextOptions, (option) => option.element.current),\n }\n },\n [ActionTypes.UnregisterOption](state, action) {\n let options = state.options.slice()\n let idx = state.options.findIndex((radio) => radio.id === action.id)\n if (idx === -1) return state\n options.splice(idx, 1)\n return { ...state, options }\n },\n}\n\nlet RadioGroupDataContext = createContext<\n | ({\n value: unknown\n firstOption?: Option\n containsCheckedOption: boolean\n disabled: boolean\n compare(a: unknown, z: unknown): boolean\n } & StateDefinition)\n | null\n>(null)\nRadioGroupDataContext.displayName = 'RadioGroupDataContext'\n\nfunction useData(component: string) {\n let context = useContext(RadioGroupDataContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useData)\n throw err\n }\n return context\n}\ntype _Data = ReturnType\n\nlet RadioGroupActionsContext = createContext<{\n registerOption(option: Option): () => void\n change(value: unknown): boolean\n} | null>(null)\nRadioGroupActionsContext.displayName = 'RadioGroupActionsContext'\n\nfunction useActions(component: string) {\n let context = useContext(RadioGroupActionsContext)\n if (context === null) {\n let err = new Error(`<${component} /> is missing a parent component.`)\n if (Error.captureStackTrace) Error.captureStackTrace(err, useActions)\n throw err\n }\n return context\n}\ntype _Actions = ReturnType\n\nfunction stateReducer(state: StateDefinition, action: Actions) {\n return match(action.type, reducers, state, action)\n}\n\n// ---\n\nlet DEFAULT_RADIO_GROUP_TAG = 'div' as const\ninterface RadioGroupRenderPropArg {\n value: TType\n}\ntype RadioGroupPropsWeControl = 'role' | 'aria-labelledby' | 'aria-describedby'\n\nlet RadioGroupRoot = forwardRefWithAs(function RadioGroup<\n TTag extends ElementType = typeof DEFAULT_RADIO_GROUP_TAG,\n TType = string\n>(\n props: Props<\n TTag,\n RadioGroupRenderPropArg,\n RadioGroupPropsWeControl | 'value' | 'defaultValue' | 'onChange' | 'disabled' | 'name' | 'by'\n > & {\n value?: TType\n defaultValue?: TType\n onChange?(value: TType): void\n by?: (keyof TType & string) | ((a: TType, z: TType) => boolean)\n disabled?: boolean\n name?: string\n },\n ref: Ref\n) {\n let internalId = useId()\n let {\n id = `headlessui-radiogroup-${internalId}`,\n value: controlledValue,\n defaultValue,\n name,\n onChange: controlledOnChange,\n by = (a, z) => a === z,\n disabled = false,\n ...theirProps\n } = props\n let compare = useEvent(\n typeof by === 'string'\n ? (a: TType, z: TType) => {\n let property = by as unknown as keyof TType\n return a?.[property] === z?.[property]\n }\n : by\n )\n let [state, dispatch] = useReducer(stateReducer, { options: [] } as StateDefinition)\n let options = state.options as unknown as Option[]\n let [labelledby, LabelProvider] = useLabels()\n let [describedby, DescriptionProvider] = useDescriptions()\n let internalRadioGroupRef = useRef(null)\n let radioGroupRef = useSyncRefs(internalRadioGroupRef, ref)\n\n let [value, onChange] = useControllable(controlledValue, controlledOnChange, defaultValue)\n\n let firstOption = useMemo(\n () =>\n options.find((option) => {\n if (option.propsRef.current.disabled) return false\n return true\n }),\n [options]\n )\n let containsCheckedOption = useMemo(\n () => options.some((option) => compare(option.propsRef.current.value as TType, value)),\n [options, value]\n )\n\n let triggerChange = useEvent((nextValue: TType) => {\n if (disabled) return false\n if (compare(nextValue, value)) return false\n let nextOption = options.find((option) =>\n compare(option.propsRef.current.value as TType, nextValue)\n )?.propsRef.current\n if (nextOption?.disabled) return false\n\n onChange?.(nextValue)\n\n return true\n })\n\n useTreeWalker({\n container: internalRadioGroupRef.current,\n accept(node) {\n if (node.getAttribute('role') === 'radio') return NodeFilter.FILTER_REJECT\n if (node.hasAttribute('role')) return NodeFilter.FILTER_SKIP\n return NodeFilter.FILTER_ACCEPT\n },\n walk(node) {\n node.setAttribute('role', 'none')\n },\n })\n\n let handleKeyDown = useEvent((event: ReactKeyboardEvent) => {\n let container = internalRadioGroupRef.current\n if (!container) return\n\n let ownerDocument = getOwnerDocument(container)\n\n let all = options\n .filter((option) => option.propsRef.current.disabled === false)\n .map((radio) => radio.element.current) as HTMLElement[]\n\n switch (event.key) {\n case Keys.Enter:\n attemptSubmit(event.currentTarget)\n break\n case Keys.ArrowLeft:\n case Keys.ArrowUp:\n {\n event.preventDefault()\n event.stopPropagation()\n\n let result = focusIn(all, Focus.Previous | Focus.WrapAround)\n\n if (result === FocusResult.Success) {\n let activeOption = options.find(\n (option) => option.element.current === ownerDocument?.activeElement\n )\n if (activeOption) triggerChange(activeOption.propsRef.current.value)\n }\n }\n break\n\n case Keys.ArrowRight:\n case Keys.ArrowDown:\n {\n event.preventDefault()\n event.stopPropagation()\n\n let result = focusIn(all, Focus.Next | Focus.WrapAround)\n\n if (result === FocusResult.Success) {\n let activeOption = options.find(\n (option) => option.element.current === ownerDocument?.activeElement\n )\n if (activeOption) triggerChange(activeOption.propsRef.current.value)\n }\n }\n break\n\n case Keys.Space:\n {\n event.preventDefault()\n event.stopPropagation()\n\n let activeOption = options.find(\n (option) => option.element.current === ownerDocument?.activeElement\n )\n if (activeOption) triggerChange(activeOption.propsRef.current.value)\n }\n break\n }\n })\n\n let registerOption = useEvent((option: Option) => {\n dispatch({ type: ActionTypes.RegisterOption, ...option })\n return () => dispatch({ type: ActionTypes.UnregisterOption, id: option.id })\n })\n\n let radioGroupData = useMemo<_Data>(\n () => ({ value, firstOption, containsCheckedOption, disabled, compare, ...state }),\n [value, firstOption, containsCheckedOption, disabled, compare, state]\n )\n let radioGroupActions = useMemo<_Actions>(\n () => ({ registerOption, change: triggerChange }),\n [registerOption, triggerChange]\n )\n\n let ourProps = {\n ref: radioGroupRef,\n id,\n role: 'radiogroup',\n 'aria-labelledby': labelledby,\n 'aria-describedby': describedby,\n onKeyDown: handleKeyDown,\n }\n\n let slot = useMemo>(() => ({ value }), [value])\n\n let form = useRef(null)\n let d = useDisposables()\n useEffect(() => {\n if (!form.current) return\n if (defaultValue === undefined) return\n\n d.addEventListener(form.current, 'reset', () => {\n triggerChange(defaultValue!)\n })\n }, [form, triggerChange /* Explicitly ignoring `defaultValue` */])\n\n return (\n \n \n \n \n {name != null &&\n value != null &&\n objectToFormEntries({ [name]: value }).map(([name, value], idx) => (\n {\n form.current = element?.closest('form') ?? null\n }\n : undefined\n }\n {...compact({\n key: name,\n as: 'input',\n type: 'radio',\n checked: value != null,\n hidden: true,\n readOnly: true,\n name,\n value,\n })}\n />\n ))}\n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_RADIO_GROUP_TAG,\n name: 'RadioGroup',\n })}\n \n \n \n \n )\n})\n\n// ---\n\nenum OptionState {\n Empty = 1 << 0,\n Active = 1 << 1,\n}\n\nlet DEFAULT_OPTION_TAG = 'div' as const\ninterface OptionRenderPropArg {\n checked: boolean\n active: boolean\n disabled: boolean\n}\ntype RadioPropsWeControl =\n | 'aria-checked'\n | 'onBlur'\n | 'onClick'\n | 'onFocus'\n | 'ref'\n | 'role'\n | 'tabIndex'\n\nlet Option = forwardRefWithAs(function Option<\n TTag extends ElementType = typeof DEFAULT_OPTION_TAG,\n // TODO: One day we will be able to infer this type from the generic in RadioGroup itself.\n // But today is not that day..\n TType = Parameters[0]['value']\n>(\n props: Props & {\n value: TType\n disabled?: boolean\n },\n ref: Ref\n) {\n let internalId = useId()\n let {\n id = `headlessui-radiogroup-option-${internalId}`,\n value,\n disabled = false,\n ...theirProps\n } = props\n let internalOptionRef = useRef(null)\n let optionRef = useSyncRefs(internalOptionRef, ref)\n\n let [labelledby, LabelProvider] = useLabels()\n let [describedby, DescriptionProvider] = useDescriptions()\n let { addFlag, removeFlag, hasFlag } = useFlags(OptionState.Empty)\n\n let propsRef = useLatestValue({ value, disabled })\n\n let data = useData('RadioGroup.Option')\n let actions = useActions('RadioGroup.Option')\n\n useIsoMorphicEffect(\n () => actions.registerOption({ id, element: internalOptionRef, propsRef }),\n [id, actions, internalOptionRef, props]\n )\n\n let handleClick = useEvent((event: ReactMouseEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n if (!actions.change(value)) return\n\n addFlag(OptionState.Active)\n internalOptionRef.current?.focus()\n })\n\n let handleFocus = useEvent((event: ReactFocusEvent) => {\n if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()\n addFlag(OptionState.Active)\n })\n\n let handleBlur = useEvent(() => removeFlag(OptionState.Active))\n\n let isFirstOption = data.firstOption?.id === id\n let isDisabled = data.disabled || disabled\n\n let checked = data.compare(data.value as TType, value)\n let ourProps = {\n ref: optionRef,\n id,\n role: 'radio',\n 'aria-checked': checked ? 'true' : 'false',\n 'aria-labelledby': labelledby,\n 'aria-describedby': describedby,\n 'aria-disabled': isDisabled ? true : undefined,\n tabIndex: (() => {\n if (isDisabled) return -1\n if (checked) return 0\n if (!data.containsCheckedOption && isFirstOption) return 0\n return -1\n })(),\n onClick: isDisabled ? undefined : handleClick,\n onFocus: isDisabled ? undefined : handleFocus,\n onBlur: isDisabled ? undefined : handleBlur,\n }\n let slot = useMemo(\n () => ({ checked, disabled: isDisabled, active: hasFlag(OptionState.Active) }),\n [checked, isDisabled, hasFlag]\n )\n\n return (\n \n \n {render({\n ourProps,\n theirProps,\n slot,\n defaultTag: DEFAULT_OPTION_TAG,\n name: 'RadioGroup.Option',\n })}\n \n \n )\n})\n\n// ---\n\nexport let RadioGroup = Object.assign(RadioGroupRoot, { Option, Label, Description })\n", "import { useState, useCallback } from 'react'\n\nexport function useFlags(initialFlags = 0) {\n let [flags, setFlags] = useState(initialFlags)\n\n let addFlag = useCallback((flag: number) => setFlags((flags) => flags | flag), [flags])\n let hasFlag = useCallback((flag: number) => Boolean(flags & flag), [flags])\n let removeFlag = useCallback((flag: number) => setFlags((flags) => flags & ~flag), [setFlags])\n let toggleFlag = useCallback((flag: number) => setFlags((flags) => flags ^ flag), [setFlags])\n\n return { addFlag, hasFlag, removeFlag, toggleFlag }\n}\n", "import React, {\n createContext,\n useContext,\n useMemo,\n useState,\n\n // Types\n ElementType,\n ReactNode,\n Ref,\n} from 'react'\n\nimport { Props } from '../../types'\nimport { useId } from '../../hooks/use-id'\nimport { forwardRefWithAs, render } from '../../utils/render'\nimport { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'\nimport { useSyncRefs } from '../../hooks/use-sync-refs'\nimport { useEvent } from '../../hooks/use-event'\n\n// ---\n\ninterface SharedData {\n slot?: {}\n name?: string\n props?: {}\n}\n\nlet LabelContext = createContext<({ register(value: string): () => void } & SharedData) | null>(\n null\n)\n\nfunction useLabelContext() {\n let context = useContext(LabelContext)\n if (context === null) {\n let err = new Error('You used a