Skip to content

Commit f144666

Browse files
authored
Remove transition state from render props (#3312)
* add function to map transition data to data attributes * use transition data attributes in props Instead of in the `slot` because this would also expose this information as render props but we just want to set it as props without exposing it as render props. * rename `slot` to `transitionData` for consistency * update changelog
1 parent 1f1e290 commit f144666

File tree

10 files changed

+47
-39
lines changed

10 files changed

+47
-39
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Add ability to render multiple `Dialog` components at once (without nesting them) ([#3242](https://github.com/tailwindlabs/headlessui/pull/3242))
13-
- Add new data-attribute-based transition API ([#3273](https://github.com/tailwindlabs/headlessui/pull/3273), [#3285](https://github.com/tailwindlabs/headlessui/pull/3285), [#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3309](https://github.com/tailwindlabs/headlessui/pull/3309))
13+
- Add new data-attribute-based transition API ([#3273](https://github.com/tailwindlabs/headlessui/pull/3273), [#3285](https://github.com/tailwindlabs/headlessui/pull/3285), [#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3309](https://github.com/tailwindlabs/headlessui/pull/3309), [#3312](https://github.com/tailwindlabs/headlessui/pull/3312))
1414
- Add `DialogBackdrop` component ([#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3310](https://github.com/tailwindlabs/headlessui/pull/3310))
1515
- Add `PopoverBackdrop` component to replace `PopoverOverlay` ([#3308](https://github.com/tailwindlabs/headlessui/pull/3308))
1616

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
4141
import { useScrollLock } from '../../hooks/use-scroll-lock'
4242
import { useSyncRefs } from '../../hooks/use-sync-refs'
4343
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
44-
import { useTransition, type TransitionData } from '../../hooks/use-transition'
44+
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
4545
import { useTreeWalker } from '../../hooks/use-tree-walker'
4646
import { useWatch } from '../../hooks/use-watch'
4747
import { useDisabled } from '../../internal/disabled'
@@ -1564,7 +1564,7 @@ let DEFAULT_OPTIONS_TAG = 'div' as const
15641564
type OptionsRenderPropArg = {
15651565
open: boolean
15661566
option: unknown
1567-
} & TransitionData
1567+
}
15681568
type OptionsPropsWeControl = 'aria-labelledby' | 'aria-multiselectable' | 'role' | 'tabIndex'
15691569

15701570
let OptionsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
@@ -1665,9 +1665,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
16651665
return {
16661666
open: data.comboboxState === ComboboxState.Open,
16671667
option: undefined,
1668-
...transitionData,
16691668
} satisfies OptionsRenderPropArg
1670-
}, [data.comboboxState, transitionData])
1669+
}, [data.comboboxState])
16711670

16721671
// When the user scrolls **using the mouse** (so scroll event isn't appropriate)
16731672
// we want to make sure that the current activation trigger is set to pointer.
@@ -1706,6 +1705,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
17061705
} as CSSProperties,
17071706
onWheel: data.activationTrigger === ActivationTrigger.Pointer ? undefined : handleWheel,
17081707
onMouseDown: handleMouseDown,
1708+
...transitionDataAttributes(transitionData),
17091709
})
17101710

17111711
// We should freeze when the combobox is visible but "closed". This means that

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { useEvent } from '../../hooks/use-event'
2424
import { useId } from '../../hooks/use-id'
2525
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
2626
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
27-
import { useTransition, type TransitionData } from '../../hooks/use-transition'
27+
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
2828
import { CloseProvider } from '../../internal/close-provider'
2929
import {
3030
OpenClosedProvider,
@@ -425,7 +425,7 @@ let DEFAULT_PANEL_TAG = 'div' as const
425425
type PanelRenderPropArg = {
426426
open: boolean
427427
close: (focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => void
428-
} & TransitionData
428+
}
429429
type DisclosurePanelPropsWeControl = never
430430

431431
let PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
@@ -475,13 +475,13 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
475475
return {
476476
open: state.disclosureState === DisclosureStates.Open,
477477
close,
478-
...transitionData,
479478
} satisfies PanelRenderPropArg
480-
}, [state.disclosureState, close, transitionData])
479+
}, [state.disclosureState, close])
481480

482481
let ourProps = {
483482
ref: panelRef,
484483
id,
484+
...transitionDataAttributes(transitionData),
485485
}
486486

487487
return (

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
4141
import { useSyncRefs } from '../../hooks/use-sync-refs'
4242
import { useTextValue } from '../../hooks/use-text-value'
4343
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
44-
import { useTransition, type TransitionData } from '../../hooks/use-transition'
44+
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
4545
import { useDisabled } from '../../internal/disabled'
4646
import {
4747
FloatingProvider,
@@ -870,7 +870,7 @@ let SelectedOptionContext = createContext(false)
870870
let DEFAULT_OPTIONS_TAG = 'div' as const
871871
type OptionsRenderPropArg = {
872872
open: boolean
873-
} & TransitionData
873+
}
874874
type OptionsPropsWeControl =
875875
| 'aria-activedescendant'
876876
| 'aria-labelledby'
@@ -1090,9 +1090,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
10901090
let slot = useMemo(() => {
10911091
return {
10921092
open: data.listboxState === ListboxStates.Open,
1093-
...transitionData,
10941093
} satisfies OptionsRenderPropArg
1095-
}, [data.listboxState, transitionData])
1094+
}, [data.listboxState])
10961095

10971096
let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
10981097
id,
@@ -1113,6 +1112,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
11131112
...style,
11141113
'--button-width': useElementSize(data.buttonRef, true).width,
11151114
} as CSSProperties,
1115+
...transitionDataAttributes(transitionData),
11161116
})
11171117

11181118
// We should freeze when the listbox is visible but "closed". This means that

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
3737
import { useSyncRefs } from '../../hooks/use-sync-refs'
3838
import { useTextValue } from '../../hooks/use-text-value'
3939
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
40-
import { useTransition, type TransitionData } from '../../hooks/use-transition'
40+
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
4141
import { useTreeWalker } from '../../hooks/use-tree-walker'
4242
import {
4343
FloatingProvider,
@@ -565,7 +565,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
565565
let DEFAULT_ITEMS_TAG = 'div' as const
566566
type ItemsRenderPropArg = {
567567
open: boolean
568-
} & TransitionData
568+
}
569569
type ItemsPropsWeControl = 'aria-activedescendant' | 'aria-labelledby' | 'role' | 'tabIndex'
570570

571571
let ItemsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
@@ -760,9 +760,8 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
760760
let slot = useMemo(() => {
761761
return {
762762
open: state.menuState === MenuStates.Open,
763-
...transitionData,
764763
} satisfies ItemsRenderPropArg
765-
}, [state.menuState, transitionData])
764+
}, [state.menuState])
766765

767766
let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
768767
'aria-activedescendant':
@@ -782,6 +781,7 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
782781
...style,
783782
'--button-width': useElementSize(state.buttonRef, true).width,
784783
} as CSSProperties,
784+
...transitionDataAttributes(transitionData),
785785
})
786786

787787
return (

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-contain
3636
import { useScrollLock } from '../../hooks/use-scroll-lock'
3737
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
3838
import { Direction as TabDirection, useTabDirection } from '../../hooks/use-tab-direction'
39-
import { useTransition, type TransitionData } from '../../hooks/use-transition'
39+
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
4040
import { CloseProvider } from '../../internal/close-provider'
4141
import {
4242
FloatingProvider,
@@ -732,7 +732,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
732732
let DEFAULT_BACKDROP_TAG = 'div' as const
733733
type BackdropRenderPropArg = {
734734
open: boolean
735-
} & TransitionData
735+
}
736736
type BackdropPropsWeControl = 'aria-hidden'
737737

738738
let BackdropRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
@@ -778,15 +778,15 @@ function BackdropFn<TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG>(
778778
let slot = useMemo(() => {
779779
return {
780780
open: popoverState === PopoverStates.Open,
781-
...transitionData,
782781
} satisfies BackdropRenderPropArg
783-
}, [popoverState, transitionData])
782+
}, [popoverState])
784783

785784
let ourProps = {
786785
ref: backdropRef,
787786
id,
788787
'aria-hidden': true,
789788
onClick: handleClick,
789+
...transitionDataAttributes(transitionData),
790790
}
791791

792792
return render({
@@ -806,7 +806,7 @@ let DEFAULT_PANEL_TAG = 'div' as const
806806
type PanelRenderPropArg = {
807807
open: boolean
808808
close: (focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => void
809-
} & TransitionData
809+
}
810810

811811
let PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
812812

@@ -936,9 +936,8 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
936936
return {
937937
open: state.popoverState === PopoverStates.Open,
938938
close,
939-
...transitionData,
940939
} satisfies PanelRenderPropArg
941-
}, [state.popoverState, close, transitionData])
940+
}, [state.popoverState, close])
942941

943942
let ourProps: Record<string, any> = mergeProps(anchor ? getFloatingPanelProps() : {}, {
944943
ref: panelRef,
@@ -968,6 +967,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
968967
...style,
969968
'--button-width': useElementSize(state.button, true).width,
970969
} as React.CSSProperties,
970+
...transitionDataAttributes(transitionData),
971971
})
972972

973973
let direction = useTabDirection()

packages/@headlessui-react/src/components/transition/__snapshots__/transition.test.tsx.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ exports[`Setup API transition classes should be possible to passthrough the tran
132132
class="enter enter-from"
133133
data-closed=""
134134
data-enter=""
135-
data-headlessui-state="closed enter transition"
136135
data-transition=""
137136
style=""
138137
>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,6 @@ describe('Setup API', () => {
374374
<div
375375
class="foo1
376376
foo2 leave"
377-
data-headlessui-state="leave transition"
378377
data-leave=""
379378
data-transition=""
380379
style=""

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { useLatestValue } from '../../hooks/use-latest-value'
2020
import { useOnDisappear } from '../../hooks/use-on-disappear'
2121
import { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'
2222
import { useSyncRefs } from '../../hooks/use-sync-refs'
23-
import { useTransition } from '../../hooks/use-transition'
23+
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
2424
import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
2525
import type { Props, ReactTag } from '../../types'
2626
import { classNames } from '../../utils/class-names'
@@ -437,7 +437,7 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
437437
// a leave transition on the `<Transition>` is done, but there is still a
438438
// child `<TransitionChild>` busy, then `visible` would be `false`, while
439439
// `state` would still be `TreeStates.Visible`.
440-
let [, slot] = useTransition(enabled, container, show, { start, end })
440+
let [, transitionData] = useTransition(enabled, container, show, { start, end })
441441

442442
let ourProps = compact({
443443
ref: transitionRef,
@@ -451,33 +451,33 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
451451
immediate && enterFrom,
452452

453453
// Map data attributes to `enter`, `enterFrom` and `enterTo` classes
454-
slot.enter && enter,
455-
slot.enter && slot.closed && enterFrom,
456-
slot.enter && !slot.closed && enterTo,
454+
transitionData.enter && enter,
455+
transitionData.enter && transitionData.closed && enterFrom,
456+
transitionData.enter && !transitionData.closed && enterTo,
457457

458458
// Map data attributes to `leave`, `leaveFrom` and `leaveTo` classes
459-
slot.leave && leave,
460-
slot.leave && !slot.closed && leaveFrom,
461-
slot.leave && slot.closed && leaveTo,
459+
transitionData.leave && leave,
460+
transitionData.leave && !transitionData.closed && leaveFrom,
461+
transitionData.leave && transitionData.closed && leaveTo,
462462

463463
// Map data attributes to `entered` class (backwards compatibility)
464-
!slot.transition && show && entered
464+
!transitionData.transition && show && entered
465465
)?.trim() || undefined, // If `className` is an empty string, we can omit it
466+
...transitionDataAttributes(transitionData),
466467
})
467468

468469
let openClosedState = 0
469470
if (state === TreeStates.Visible) openClosedState |= State.Open
470471
if (state === TreeStates.Hidden) openClosedState |= State.Closed
471-
if (slot.enter) openClosedState |= State.Opening
472-
if (slot.leave) openClosedState |= State.Closing
472+
if (transitionData.enter) openClosedState |= State.Opening
473+
if (transitionData.leave) openClosedState |= State.Closing
473474

474475
return (
475476
<NestingContext.Provider value={nesting}>
476477
<OpenClosedProvider value={openClosedState}>
477478
{render({
478479
ourProps,
479480
theirProps,
480-
slot,
481481
defaultTag: DEFAULT_TRANSITION_CHILD_TAG,
482482
features: TransitionChildRenderFeatures,
483483
visible: state === TreeStates.Visible,

packages/@headlessui-react/src/hooks/use-transition.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,23 @@ enum TransitionState {
3232
Leave = 1 << 2,
3333
}
3434

35-
export type TransitionData = {
35+
type TransitionData = {
3636
closed?: boolean
3737
enter?: boolean
3838
leave?: boolean
3939
transition?: boolean
4040
}
4141

42+
export function transitionDataAttributes(data: TransitionData) {
43+
let attributes: Record<string, string> = {}
44+
for (let key in data) {
45+
if (data[key as keyof TransitionData] === true) {
46+
attributes[`data-${key}`] = ''
47+
}
48+
}
49+
return attributes
50+
}
51+
4252
export function useTransition(
4353
enabled: boolean,
4454
elementRef: MutableRefObject<HTMLElement | null>,

0 commit comments

Comments
 (0)