Skip to content

Commit 1ee4cfd

Browse files
authored
[internal] Move enabled parameter in hooks to first argument (#3245)
* move `enabled` parameter in hooks to front Whenever a hook requires an `enabled` state, the `enabled` parameter is moved to the front. Initially this was the last argument and enabled by default but everywhere that we use these hooks we have to pass a dedicated boolean anyway. This makes sure these hooks follow a similar pattern. Bonus points because Prettier can now improve formatting the usage of these hooks. The reason why is because there is no additional argument after the potential last callback. Before: ```ts let enabled = data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open useInertOthers( { allowed: useEvent(() => [ data.inputRef.current, data.buttonRef.current, data.optionsRef.current, ]), }, enabled ) ``` After: ```ts let enabled = data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open useInertOthers(enabled, { allowed: useEvent(() => [ data.inputRef.current, data.buttonRef.current, data.optionsRef.current, ]), }) ``` Much better! * inline variables
1 parent 7be23e5 commit 1ee4cfd

File tree

13 files changed

+120
-135
lines changed

13 files changed

+120
-135
lines changed

packages/@headlessui-react/src/components/combobox/combobox.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -770,10 +770,9 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
770770
}, [data])
771771

772772
// Handle outside click
773-
useOutsideClick(
774-
[data.buttonRef, data.inputRef, data.optionsRef],
775-
() => actions.closeCombobox(),
776-
data.comboboxState === ComboboxState.Open
773+
let outsideClickEnabled = data.comboboxState === ComboboxState.Open
774+
useOutsideClick(outsideClickEnabled, [data.buttonRef, data.inputRef, data.optionsRef], () =>
775+
actions.closeCombobox()
777776
)
778777

779778
let slot = useMemo(() => {
@@ -1623,25 +1622,25 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
16231622
})()
16241623

16251624
// Ensure we close the combobox as soon as the input becomes hidden
1626-
useOnDisappear(data.inputRef, actions.closeCombobox, visible)
1625+
useOnDisappear(visible, data.inputRef, actions.closeCombobox)
16271626

16281627
// Enable scroll locking when the combobox is visible, and `modal` is enabled
1629-
useScrollLock(
1630-
ownerDocument,
1631-
data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
1632-
)
1628+
let scrollLockEnabled = data.__demoMode
1629+
? false
1630+
: modal && data.comboboxState === ComboboxState.Open
1631+
useScrollLock(scrollLockEnabled, ownerDocument)
16331632

16341633
// Mark other elements as inert when the combobox is visible, and `modal` is enabled
1635-
useInertOthers(
1636-
{
1637-
allowed: useEvent(() => [
1638-
data.inputRef.current,
1639-
data.buttonRef.current,
1640-
data.optionsRef.current,
1641-
]),
1642-
},
1643-
data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
1644-
)
1634+
let inertOthersEnabled = data.__demoMode
1635+
? false
1636+
: modal && data.comboboxState === ComboboxState.Open
1637+
useInertOthers(inertOthersEnabled, {
1638+
allowed: useEvent(() => [
1639+
data.inputRef.current,
1640+
data.buttonRef.current,
1641+
data.optionsRef.current,
1642+
]),
1643+
})
16451644

16461645
useIsoMorphicEffect(() => {
16471646
data.optionsPropsRef.current.static = props.static ?? false
@@ -1650,9 +1649,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
16501649
data.optionsPropsRef.current.hold = hold
16511650
}, [data.optionsPropsRef, hold])
16521651

1653-
useTreeWalker({
1652+
useTreeWalker(data.comboboxState === ComboboxState.Open, {
16541653
container: data.optionsRef.current,
1655-
enabled: data.comboboxState === ComboboxState.Open,
16561654
accept(node) {
16571655
if (node.getAttribute('role') === 'option') return NodeFilter.FILTER_REJECT
16581656
if (node.hasAttribute('role')) return NodeFilter.FILTER_SKIP

packages/@headlessui-react/src/components/dialog/dialog.tsx

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -260,43 +260,36 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
260260
usesOpenClosedState !== null ? (usesOpenClosedState & State.Closing) === State.Closing : false
261261

262262
// Ensure other elements can't be interacted with
263-
let inertEnabled = (() => {
263+
let inertOthersEnabled = (() => {
264+
if (__demoMode) return false
264265
// Only the top-most dialog should be allowed, all others should be inert
265266
if (hasNestedDialogs) return false
266267
if (isClosing) return false
267268
return enabled
268269
})()
269-
270-
useInertOthers(
271-
{
272-
allowed: useEvent(() => [
273-
// Allow the headlessui-portal of the Dialog to be interactive. This
274-
// contains the current dialog and the necessary focus guard elements.
275-
internalDialogRef.current?.closest<HTMLElement>('[data-headlessui-portal]') ?? null,
276-
]),
277-
disallowed: useEvent(() => [
278-
// Disallow the "main" tree root node
279-
mainTreeNodeRef.current?.closest<HTMLElement>('body > *:not(#headlessui-portal-root)') ??
280-
null,
281-
]),
282-
},
283-
__demoMode ? false : inertEnabled
284-
)
270+
useInertOthers(inertOthersEnabled, {
271+
allowed: useEvent(() => [
272+
// Allow the headlessui-portal of the Dialog to be interactive. This
273+
// contains the current dialog and the necessary focus guard elements.
274+
internalDialogRef.current?.closest<HTMLElement>('[data-headlessui-portal]') ?? null,
275+
]),
276+
disallowed: useEvent(() => [
277+
// Disallow the "main" tree root node
278+
mainTreeNodeRef.current?.closest<HTMLElement>('body > *:not(#headlessui-portal-root)') ??
279+
null,
280+
]),
281+
})
285282

286283
// Close Dialog on outside click
287284
let outsideClickEnabled = (() => {
288285
if (!enabled) return false
289286
if (hasNestedDialogs) return false
290287
return true
291288
})()
292-
useOutsideClick(
293-
resolveRootContainers,
294-
(event) => {
295-
event.preventDefault()
296-
close()
297-
},
298-
outsideClickEnabled
299-
)
289+
useOutsideClick(outsideClickEnabled, resolveRootContainers, (event) => {
290+
event.preventDefault()
291+
close()
292+
})
300293

301294
// Handle `Escape` to close
302295
let escapeToCloseEnabled = (() => {
@@ -335,10 +328,10 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
335328
if (hasParentDialog) return false
336329
return true
337330
})()
338-
useScrollLock(ownerDocument, __demoMode ? false : scrollLockEnabled, resolveRootContainers)
331+
useScrollLock(scrollLockEnabled, ownerDocument, resolveRootContainers)
339332

340333
// Ensure we close the dialog as soon as the dialog itself becomes hidden
341-
useOnDisappear(internalDialogRef, close, dialogState === DialogStates.Open)
334+
useOnDisappear(enabled, internalDialogRef, close)
342335

343336
let [describedby, DescriptionProvider] = useDescriptions()
344337

packages/@headlessui-react/src/components/listbox/listbox.tsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -560,18 +560,15 @@ function ListboxFn<
560560
}, [data])
561561

562562
// Handle outside click
563-
useOutsideClick(
564-
[data.buttonRef, data.optionsRef],
565-
(event, target) => {
566-
dispatch({ type: ActionTypes.CloseListbox })
563+
let outsideClickEnabled = data.listboxState === ListboxStates.Open
564+
useOutsideClick(outsideClickEnabled, [data.buttonRef, data.optionsRef], (event, target) => {
565+
dispatch({ type: ActionTypes.CloseListbox })
567566

568-
if (!isFocusableElement(target, FocusableMode.Loose)) {
569-
event.preventDefault()
570-
data.buttonRef.current?.focus()
571-
}
572-
},
573-
data.listboxState === ListboxStates.Open
574-
)
567+
if (!isFocusableElement(target, FocusableMode.Loose)) {
568+
event.preventDefault()
569+
data.buttonRef.current?.focus()
570+
}
571+
})
575572

576573
let slot = useMemo(() => {
577574
return {
@@ -927,19 +924,21 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
927924
})()
928925

929926
// Ensure we close the listbox as soon as the button becomes hidden
930-
useOnDisappear(data.buttonRef, actions.closeListbox, visible)
927+
useOnDisappear(visible, data.buttonRef, actions.closeListbox)
931928

932929
// Enable scroll locking when the listbox is visible, and `modal` is enabled
933-
useScrollLock(
934-
ownerDocument,
935-
data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open
936-
)
930+
let scrollLockEnabled = data.__demoMode
931+
? false
932+
: modal && data.listboxState === ListboxStates.Open
933+
useScrollLock(scrollLockEnabled, ownerDocument)
937934

938935
// Mark other elements as inert when the listbox is visible, and `modal` is enabled
939-
useInertOthers(
940-
{ allowed: useEvent(() => [data.buttonRef.current, data.optionsRef.current]) },
941-
data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open
942-
)
936+
let inertOthersEnabled = data.__demoMode
937+
? false
938+
: modal && data.listboxState === ListboxStates.Open
939+
useInertOthers(inertOthersEnabled, {
940+
allowed: useEvent(() => [data.buttonRef.current, data.optionsRef.current]),
941+
})
943942

944943
let initialOption = useRef<number | null>(null)
945944

@@ -970,7 +969,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
970969
//
971970
// This can be solved by only transitioning the `opacity` instead of everything, but if you _do_
972971
// want to transition the y-axis for example you will run into the same issue again.
973-
let didButtonMove = useDidElementMove(data.buttonRef, data.listboxState !== ListboxStates.Open)
972+
let didElementMoveEnabled = data.listboxState !== ListboxStates.Open
973+
let didButtonMove = useDidElementMove(didElementMoveEnabled, data.buttonRef)
974974

975975
// Now that we know that the button did move or not, we can either disable the panel and all of
976976
// its transitions, or rely on the `visible` state to hide the panel whenever necessary.

packages/@headlessui-react/src/components/menu/menu.tsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -388,18 +388,15 @@ function MenuFn<TTag extends ElementType = typeof DEFAULT_MENU_TAG>(
388388
let menuRef = useSyncRefs(ref)
389389

390390
// Handle outside click
391-
useOutsideClick(
392-
[buttonRef, itemsRef],
393-
(event, target) => {
394-
dispatch({ type: ActionTypes.CloseMenu })
391+
let outsideClickEnabled = menuState === MenuStates.Open
392+
useOutsideClick(outsideClickEnabled, [buttonRef, itemsRef], (event, target) => {
393+
dispatch({ type: ActionTypes.CloseMenu })
395394

396-
if (!isFocusableElement(target, FocusableMode.Loose)) {
397-
event.preventDefault()
398-
buttonRef.current?.focus()
399-
}
400-
},
401-
menuState === MenuStates.Open
402-
)
395+
if (!isFocusableElement(target, FocusableMode.Loose)) {
396+
event.preventDefault()
397+
buttonRef.current?.focus()
398+
}
399+
})
403400

404401
let close = useEvent(() => {
405402
dispatch({ type: ActionTypes.CloseMenu })
@@ -624,19 +621,19 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
624621
})()
625622

626623
// Ensure we close the menu as soon as the button becomes hidden
627-
useOnDisappear(state.buttonRef, () => dispatch({ type: ActionTypes.CloseMenu }), visible)
624+
useOnDisappear(visible, state.buttonRef, () => {
625+
dispatch({ type: ActionTypes.CloseMenu })
626+
})
628627

629628
// Enable scroll locking when the menu is visible, and `modal` is enabled
630-
useScrollLock(
631-
ownerDocument,
632-
state.__demoMode ? false : modal && state.menuState === MenuStates.Open
633-
)
629+
let scrollLockEnabled = state.__demoMode ? false : modal && state.menuState === MenuStates.Open
630+
useScrollLock(scrollLockEnabled, ownerDocument)
634631

635632
// Mark other elements as inert when the menu is visible, and `modal` is enabled
636-
useInertOthers(
637-
{ allowed: useEvent(() => [state.buttonRef.current, state.itemsRef.current]) },
638-
state.__demoMode ? false : modal && state.menuState === MenuStates.Open
639-
)
633+
let inertOthersEnabled = state.__demoMode ? false : modal && state.menuState === MenuStates.Open
634+
useInertOthers(inertOthersEnabled, {
635+
allowed: useEvent(() => [state.buttonRef.current, state.itemsRef.current]),
636+
})
640637

641638
// We keep track whether the button moved or not, we only check this when the menu state becomes
642639
// closed. If the button moved, then we want to cancel pending transitions to prevent that the
@@ -647,7 +644,8 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
647644
//
648645
// This can be solved by only transitioning the `opacity` instead of everything, but if you _do_
649646
// want to transition the y-axis for example you will run into the same issue again.
650-
let didButtonMove = useDidElementMove(state.buttonRef, state.menuState !== MenuStates.Open)
647+
let didButtonMoveEnabled = state.menuState !== MenuStates.Open
648+
let didButtonMove = useDidElementMove(didButtonMoveEnabled, state.buttonRef)
651649

652650
// Now that we know that the button did move or not, we can either disable the panel and all of
653651
// its transitions, or rely on the `visible` state to hide the panel whenever necessary.
@@ -662,9 +660,8 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
662660
container.focus({ preventScroll: true })
663661
}, [state.menuState, state.itemsRef, ownerDocument, state.itemsRef.current])
664662

665-
useTreeWalker({
663+
useTreeWalker(state.menuState === MenuStates.Open, {
666664
container: state.itemsRef.current,
667-
enabled: state.menuState === MenuStates.Open,
668665
accept(node) {
669666
if (node.getAttribute('role') === 'menuitem') return NodeFilter.FILTER_REJECT
670667
if (node.hasAttribute('role')) return NodeFilter.FILTER_SKIP

packages/@headlessui-react/src/components/popover/popover.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -365,18 +365,15 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
365365
)
366366

367367
// Handle outside click
368-
useOutsideClick(
369-
root.resolveContainers,
370-
(event, target) => {
371-
dispatch({ type: ActionTypes.ClosePopover })
368+
let outsideClickEnabled = popoverState === PopoverStates.Open
369+
useOutsideClick(outsideClickEnabled, root.resolveContainers, (event, target) => {
370+
dispatch({ type: ActionTypes.ClosePopover })
372371

373-
if (!isFocusableElement(target, FocusableMode.Loose)) {
374-
event.preventDefault()
375-
button?.focus()
376-
}
377-
},
378-
popoverState === PopoverStates.Open
379-
)
372+
if (!isFocusableElement(target, FocusableMode.Loose)) {
373+
event.preventDefault()
374+
button?.focus()
375+
}
376+
})
380377

381378
let close = useEvent(
382379
(
@@ -868,10 +865,13 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
868865
})()
869866

870867
// Ensure we close the popover as soon as the button becomes hidden
871-
useOnDisappear(state.button, () => dispatch({ type: ActionTypes.ClosePopover }), visible)
868+
useOnDisappear(visible, state.button, () => {
869+
dispatch({ type: ActionTypes.ClosePopover })
870+
})
872871

873872
// Enable scroll locking when the popover is visible, and `modal` is enabled
874-
useScrollLock(ownerDocument, state.__demoMode ? false : modal && visible)
873+
let scrollLockEnabled = state.__demoMode ? false : modal && visible
874+
useScrollLock(scrollLockEnabled, ownerDocument)
875875

876876
let handleKeyDown = useEvent((event: ReactKeyboardEvent<HTMLButtonElement>) => {
877877
switch (event.key) {

packages/@headlessui-react/src/components/transition/transition.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ function TransitionRootFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_C
586586
)
587587

588588
// Ensure we change the tree state to hidden once the transition becomes hidden
589-
useOnDisappear(internalTransitionRef, () => setState(TreeStates.Hidden))
589+
useOnDisappear(show, internalTransitionRef, () => setState(TreeStates.Hidden))
590590

591591
useIsoMorphicEffect(() => {
592592
if (show) {

packages/@headlessui-react/src/hooks/use-did-element-move.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { useRef, type MutableRefObject } from 'react'
22
import { useIsoMorphicEffect } from './use-iso-morphic-effect'
33

4-
export function useDidElementMove(
5-
element: MutableRefObject<HTMLElement | null>,
6-
enabled: boolean = true
7-
) {
4+
export function useDidElementMove(enabled: boolean, element: MutableRefObject<HTMLElement | null>) {
85
let elementPosition = useRef({ left: 0, top: 0 })
96
useIsoMorphicEffect(() => {
107
let el = element.current

packages/@headlessui-react/src/hooks/use-inert-others.test.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ it('should be possible to inert an element', async () => {
1313
function Example() {
1414
let ref = useRef(null)
1515
let [enabled, setEnabled] = useState(true)
16-
useInertOthers({ disallowed: () => [ref.current] }, enabled)
16+
useInertOthers(enabled, { disallowed: () => [ref.current] })
1717

1818
return (
1919
<div ref={ref} id="main">
@@ -59,7 +59,7 @@ it('should not mark an element as inert when the hook is disabled', async () =>
5959
function Example() {
6060
let ref = useRef(null)
6161
let [enabled, setEnabled] = useState(false)
62-
useInertOthers({ disallowed: () => [ref.current] }, enabled)
62+
useInertOthers(enabled, { disallowed: () => [ref.current] })
6363

6464
return (
6565
<div ref={ref} id="main">
@@ -95,7 +95,7 @@ it('should mark the element as not inert anymore, once all references are gone',
9595
let ref = useRef<HTMLDivElement | null>(null)
9696

9797
let [enabled, setEnabled] = useState(false)
98-
useInertOthers({ disallowed: () => [ref.current?.parentElement ?? null] }, enabled)
98+
useInertOthers(enabled, { disallowed: () => [ref.current?.parentElement ?? null] })
9999

100100
return (
101101
<div ref={ref}>
@@ -143,10 +143,9 @@ it('should mark the element as not inert anymore, once all references are gone',
143143
it('should be possible to mark everything but allowed containers as inert', async () => {
144144
function Example({ children }: { children: ReactNode }) {
145145
let [enabled, setEnabled] = useState(false)
146-
useInertOthers(
147-
{ allowed: () => [document.getElementById('a-a-b')!, document.getElementById('a-a-c')!] },
148-
enabled
149-
)
146+
useInertOthers(enabled, {
147+
allowed: () => [document.getElementById('a-a-b')!, document.getElementById('a-a-c')!],
148+
})
150149

151150
return (
152151
<div>

0 commit comments

Comments
 (0)