diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 118d8dfaf..8b68ced4c 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -110,6 +110,8 @@ interface StateDefinition { options: { id: string; dataRef: ComboboxOptionDataRef }[] activeOptionIndex: number | null activationTrigger: ActivationTrigger + + __demoMode: boolean } enum ActionTypes { @@ -190,7 +192,12 @@ let reducers: { if (state.dataRef.current?.disabled) return state if (state.comboboxState === ComboboxState.Closed) return state - return { ...state, activeOptionIndex: null, comboboxState: ComboboxState.Closed } + return { + ...state, + activeOptionIndex: null, + comboboxState: ComboboxState.Closed, + __demoMode: false, + } }, [ActionTypes.OpenCombobox](state) { if (state.dataRef.current?.disabled) return state @@ -204,11 +211,12 @@ let reducers: { ...state, activeOptionIndex: idx, comboboxState: ComboboxState.Open, + __demoMode: false, } } } - return { ...state, comboboxState: ComboboxState.Open } + return { ...state, comboboxState: ComboboxState.Open, __demoMode: false } }, [ActionTypes.GoToOption](state, action) { if (state.dataRef.current?.disabled) return state @@ -249,6 +257,7 @@ let reducers: { ...state, activeOptionIndex, activationTrigger, + __demoMode: false, } } @@ -290,6 +299,7 @@ let reducers: { ...adjustedState, activeOptionIndex, activationTrigger, + __demoMode: false, } }, [ActionTypes.RegisterOption]: (state, action) => { @@ -635,6 +645,7 @@ function ComboboxFn) let defaultToFirstOption = useRef(false) @@ -1583,7 +1594,10 @@ function OptionsFn( useOnDisappear(data.inputRef, actions.closeCombobox, visible) // Enable scroll locking when the combobox is visible, and `modal` is enabled - useScrollLock(ownerDocument, modal && data.comboboxState === ComboboxState.Open) + useScrollLock( + ownerDocument, + data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open + ) // Mark other elements as inert when the combobox is visible, and `modal` is enabled useInertOthers( @@ -1594,7 +1608,7 @@ function OptionsFn( data.optionsRef.current, ]), }, - modal && data.comboboxState === ComboboxState.Open + data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open ) useIsoMorphicEffect(() => { @@ -1753,13 +1767,11 @@ function OptionFn< let enableScrollIntoView = useRef(data.virtual || data.__demoMode ? false : true) useIsoMorphicEffect(() => { - if (!data.virtual) return - if (!data.__demoMode) return - let d = disposables() - d.requestAnimationFrame(() => { + if (data.virtual) return + if (data.__demoMode) return + return disposables().requestAnimationFrame(() => { enableScrollIntoView.current = true }) - return d.dispose }, [data.virtual, data.__demoMode]) useIsoMorphicEffect(() => { @@ -1767,11 +1779,9 @@ function OptionFn< if (data.comboboxState !== ComboboxState.Open) return if (!active) return if (data.activationTrigger === ActivationTrigger.Pointer) return - let d = disposables() - d.requestAnimationFrame(() => { + return disposables().requestAnimationFrame(() => { internalOptionRef.current?.scrollIntoView?.({ block: 'nearest' }) }) - return d.dispose }, [ internalOptionRef, active, diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index bc7c9e31d..824a64eb9 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -224,7 +224,7 @@ function DialogFn( let setTitleId = useEvent((id: string | null) => dispatch({ type: ActionTypes.SetTitleId, id })) let ready = useServerHandoffComplete() - let enabled = ready ? (__demoMode ? false : dialogState === DialogStates.Open) : false + let enabled = ready ? dialogState === DialogStates.Open : false let hasNestedDialogs = nestedDialogCount > 1 // 1 is the current dialog let hasParentDialog = useContext(DialogContext) !== null let [portals, PortalWrapper] = useNestedPortals() @@ -281,7 +281,7 @@ function DialogFn( null, ]), }, - inertEnabled + __demoMode ? false : inertEnabled ) // Close Dialog on outside click @@ -321,7 +321,7 @@ function DialogFn( if (hasParentDialog) return false return true })() - useScrollLock(ownerDocument, scrollLockEnabled, resolveRootContainers) + useScrollLock(ownerDocument, __demoMode ? false : scrollLockEnabled, resolveRootContainers) // Ensure we close the dialog as soon as the dialog itself becomes hidden useOnDisappear(internalDialogRef, close, dialogState === DialogStates.Open) @@ -367,6 +367,10 @@ function DialogFn( focusTrapFeatures &= ~FocusTrapFeatures.InitialFocus } + if (__demoMode) { + focusTrapFeatures = FocusTrapFeatures.None + } + return ( { searchQuery: string activeOptionIndex: number | null activationTrigger: ActivationTrigger + + __demoMode: boolean } enum ActionTypes { @@ -172,7 +174,12 @@ let reducers: { [ActionTypes.CloseListbox](state) { if (state.dataRef.current.disabled) return state if (state.listboxState === ListboxStates.Closed) return state - return { ...state, activeOptionIndex: null, listboxState: ListboxStates.Closed } + return { + ...state, + activeOptionIndex: null, + listboxState: ListboxStates.Closed, + __demoMode: false, + } }, [ActionTypes.OpenListbox](state) { if (state.dataRef.current.disabled) return state @@ -187,7 +194,7 @@ let reducers: { activeOptionIndex = optionIdx } - return { ...state, listboxState: ListboxStates.Open, activeOptionIndex } + return { ...state, listboxState: ListboxStates.Open, activeOptionIndex, __demoMode: false } }, [ActionTypes.GoToOption](state, action) { if (state.dataRef.current.disabled) return state @@ -197,6 +204,7 @@ let reducers: { ...state, searchQuery: '', activationTrigger: action.trigger ?? ActivationTrigger.Other, + __demoMode: false, } // Optimization: @@ -460,6 +468,8 @@ export type ListboxProps< form?: string name?: string multiple?: boolean + + __demoMode?: boolean } > @@ -480,6 +490,7 @@ function ListboxFn< disabled = providedDisabled || false, horizontal = false, multiple = false, + __demoMode = false, ...theirProps } = props const orientation = horizontal ? 'horizontal' : 'vertical' @@ -493,12 +504,13 @@ function ListboxFn< let [state, dispatch] = useReducer(stateReducer, { dataRef: createRef(), - listboxState: ListboxStates.Closed, + listboxState: __demoMode ? ListboxStates.Open : ListboxStates.Closed, options: [], searchQuery: '', activeOptionIndex: null, activationTrigger: ActivationTrigger.Other, optionsVisible: false, + __demoMode, } as StateDefinition) let optionsPropsRef = useRef<_Data['optionsPropsRef']['current']>({ static: false, hold: false }) @@ -914,12 +926,15 @@ function OptionsFn( useOnDisappear(data.buttonRef, actions.closeListbox, visible) // Enable scroll locking when the listbox is visible, and `modal` is enabled - useScrollLock(ownerDocument, modal && data.listboxState === ListboxStates.Open) + useScrollLock( + ownerDocument, + data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open + ) // Mark other elements as inert when the listbox is visible, and `modal` is enabled useInertOthers( { allowed: useEvent(() => [data.buttonRef.current, data.optionsRef.current]) }, - modal && data.listboxState === ListboxStates.Open + data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open ) let initialOption = useRef(null) @@ -1181,6 +1196,7 @@ function OptionFn< }) useIsoMorphicEffect(() => { + if (data.__demoMode) return if (data.listboxState !== ListboxStates.Open) return if (!active) return if (data.activationTrigger === ActivationTrigger.Pointer) return @@ -1192,6 +1208,7 @@ function OptionFn< }, [ internalOptionRef, active, + data.__demoMode, 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, diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index df70af8d8..2c616806d 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -177,6 +177,7 @@ let reducers: { ...state, searchQuery: '', activationTrigger: action.trigger ?? ActivationTrigger.Other, + __demoMode: false, } // Optimization: @@ -626,12 +627,15 @@ function ItemsFn( useOnDisappear(state.buttonRef, () => dispatch({ type: ActionTypes.CloseMenu }), visible) // Enable scroll locking when the menu is visible, and `modal` is enabled - useScrollLock(ownerDocument, modal && state.menuState === MenuStates.Open) + useScrollLock( + ownerDocument, + state.__demoMode ? false : modal && state.menuState === MenuStates.Open + ) // Mark other elements as inert when the menu is visible, and `modal` is enabled useInertOthers( { allowed: useEvent(() => [state.buttonRef.current, state.itemsRef.current]) }, - modal && state.menuState === MenuStates.Open + state.__demoMode ? false : modal && state.menuState === MenuStates.Open ) // We keep track whether the button moved or not, we only check this when the menu state becomes diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index 2822b042f..b8ff4cdea 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -81,7 +81,6 @@ enum PopoverStates { } interface StateDefinition { - __demoMode: boolean popoverState: PopoverStates buttons: MutableRefObject @@ -93,6 +92,8 @@ interface StateDefinition { beforePanelSentinel: MutableRefObject afterPanelSentinel: MutableRefObject + + __demoMode: boolean } enum ActionTypes { @@ -120,24 +121,18 @@ let reducers: { ) => StateDefinition } = { [ActionTypes.TogglePopover]: (state) => { - let nextState = { + return { ...state, popoverState: match(state.popoverState, { [PopoverStates.Open]: PopoverStates.Closed, [PopoverStates.Closed]: PopoverStates.Open, }), + __demoMode: false, } - - /* We can turn off demo mode once we re-open the `Popover` */ - if (nextState.popoverState === PopoverStates.Open) { - nextState.__demoMode = false - } - - return nextState }, [ActionTypes.ClosePopover](state) { if (state.popoverState === PopoverStates.Closed) return state - return { ...state, popoverState: PopoverStates.Closed } + return { ...state, popoverState: PopoverStates.Closed, __demoMode: false } }, [ActionTypes.SetButton](state, action) { if (state.button === action.button) return state @@ -863,7 +858,7 @@ function PanelFn( useOnDisappear(state.button, () => dispatch({ type: ActionTypes.ClosePopover }), visible) // Enable scroll locking when the popover is visible, and `modal` is enabled - useScrollLock(ownerDocument, modal && visible) + useScrollLock(ownerDocument, state.__demoMode ? false : modal && visible) let handleKeyDown = useEvent((event: ReactKeyboardEvent) => { switch (event.key) {