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
This commit is contained in:
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Add ability to render multiple `Dialog` components at once (without nesting them) ([#3242](https://github.com/tailwindlabs/headlessui/pull/3242))
|
||||
- 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))
|
||||
- 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))
|
||||
- Add `DialogBackdrop` component ([#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3310](https://github.com/tailwindlabs/headlessui/pull/3310))
|
||||
- Add `PopoverBackdrop` component to replace `PopoverOverlay` ([#3308](https://github.com/tailwindlabs/headlessui/pull/3308))
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
|
||||
import { useScrollLock } from '../../hooks/use-scroll-lock'
|
||||
import { useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
|
||||
import { useTransition, type TransitionData } from '../../hooks/use-transition'
|
||||
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
|
||||
import { useTreeWalker } from '../../hooks/use-tree-walker'
|
||||
import { useWatch } from '../../hooks/use-watch'
|
||||
import { useDisabled } from '../../internal/disabled'
|
||||
@@ -1564,7 +1564,7 @@ let DEFAULT_OPTIONS_TAG = 'div' as const
|
||||
type OptionsRenderPropArg = {
|
||||
open: boolean
|
||||
option: unknown
|
||||
} & TransitionData
|
||||
}
|
||||
type OptionsPropsWeControl = 'aria-labelledby' | 'aria-multiselectable' | 'role' | 'tabIndex'
|
||||
|
||||
let OptionsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
||||
@@ -1665,9 +1665,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
return {
|
||||
open: data.comboboxState === ComboboxState.Open,
|
||||
option: undefined,
|
||||
...transitionData,
|
||||
} satisfies OptionsRenderPropArg
|
||||
}, [data.comboboxState, transitionData])
|
||||
}, [data.comboboxState])
|
||||
|
||||
// When the user scrolls **using the mouse** (so scroll event isn't appropriate)
|
||||
// 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>(
|
||||
} as CSSProperties,
|
||||
onWheel: data.activationTrigger === ActivationTrigger.Pointer ? undefined : handleWheel,
|
||||
onMouseDown: handleMouseDown,
|
||||
...transitionDataAttributes(transitionData),
|
||||
})
|
||||
|
||||
// We should freeze when the combobox is visible but "closed". This means that
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useEvent } from '../../hooks/use-event'
|
||||
import { useId } from '../../hooks/use-id'
|
||||
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
|
||||
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { useTransition, type TransitionData } from '../../hooks/use-transition'
|
||||
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
|
||||
import { CloseProvider } from '../../internal/close-provider'
|
||||
import {
|
||||
OpenClosedProvider,
|
||||
@@ -425,7 +425,7 @@ let DEFAULT_PANEL_TAG = 'div' as const
|
||||
type PanelRenderPropArg = {
|
||||
open: boolean
|
||||
close: (focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => void
|
||||
} & TransitionData
|
||||
}
|
||||
type DisclosurePanelPropsWeControl = never
|
||||
|
||||
let PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
||||
@@ -475,13 +475,13 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
return {
|
||||
open: state.disclosureState === DisclosureStates.Open,
|
||||
close,
|
||||
...transitionData,
|
||||
} satisfies PanelRenderPropArg
|
||||
}, [state.disclosureState, close, transitionData])
|
||||
}, [state.disclosureState, close])
|
||||
|
||||
let ourProps = {
|
||||
ref: panelRef,
|
||||
id,
|
||||
...transitionDataAttributes(transitionData),
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -41,7 +41,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
|
||||
import { useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { useTextValue } from '../../hooks/use-text-value'
|
||||
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
|
||||
import { useTransition, type TransitionData } from '../../hooks/use-transition'
|
||||
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
|
||||
import { useDisabled } from '../../internal/disabled'
|
||||
import {
|
||||
FloatingProvider,
|
||||
@@ -870,7 +870,7 @@ let SelectedOptionContext = createContext(false)
|
||||
let DEFAULT_OPTIONS_TAG = 'div' as const
|
||||
type OptionsRenderPropArg = {
|
||||
open: boolean
|
||||
} & TransitionData
|
||||
}
|
||||
type OptionsPropsWeControl =
|
||||
| 'aria-activedescendant'
|
||||
| 'aria-labelledby'
|
||||
@@ -1090,9 +1090,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
let slot = useMemo(() => {
|
||||
return {
|
||||
open: data.listboxState === ListboxStates.Open,
|
||||
...transitionData,
|
||||
} satisfies OptionsRenderPropArg
|
||||
}, [data.listboxState, transitionData])
|
||||
}, [data.listboxState])
|
||||
|
||||
let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
|
||||
id,
|
||||
@@ -1113,6 +1112,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
...style,
|
||||
'--button-width': useElementSize(data.buttonRef, true).width,
|
||||
} as CSSProperties,
|
||||
...transitionDataAttributes(transitionData),
|
||||
})
|
||||
|
||||
// We should freeze when the listbox is visible but "closed". This means that
|
||||
|
||||
@@ -37,7 +37,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
|
||||
import { useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { useTextValue } from '../../hooks/use-text-value'
|
||||
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
|
||||
import { useTransition, type TransitionData } from '../../hooks/use-transition'
|
||||
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
|
||||
import { useTreeWalker } from '../../hooks/use-tree-walker'
|
||||
import {
|
||||
FloatingProvider,
|
||||
@@ -565,7 +565,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
|
||||
let DEFAULT_ITEMS_TAG = 'div' as const
|
||||
type ItemsRenderPropArg = {
|
||||
open: boolean
|
||||
} & TransitionData
|
||||
}
|
||||
type ItemsPropsWeControl = 'aria-activedescendant' | 'aria-labelledby' | 'role' | 'tabIndex'
|
||||
|
||||
let ItemsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
||||
@@ -760,9 +760,8 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
|
||||
let slot = useMemo(() => {
|
||||
return {
|
||||
open: state.menuState === MenuStates.Open,
|
||||
...transitionData,
|
||||
} satisfies ItemsRenderPropArg
|
||||
}, [state.menuState, transitionData])
|
||||
}, [state.menuState])
|
||||
|
||||
let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
|
||||
'aria-activedescendant':
|
||||
@@ -782,6 +781,7 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
|
||||
...style,
|
||||
'--button-width': useElementSize(state.buttonRef, true).width,
|
||||
} as CSSProperties,
|
||||
...transitionDataAttributes(transitionData),
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -36,7 +36,7 @@ import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-contain
|
||||
import { useScrollLock } from '../../hooks/use-scroll-lock'
|
||||
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { Direction as TabDirection, useTabDirection } from '../../hooks/use-tab-direction'
|
||||
import { useTransition, type TransitionData } from '../../hooks/use-transition'
|
||||
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
|
||||
import { CloseProvider } from '../../internal/close-provider'
|
||||
import {
|
||||
FloatingProvider,
|
||||
@@ -732,7 +732,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
|
||||
let DEFAULT_BACKDROP_TAG = 'div' as const
|
||||
type BackdropRenderPropArg = {
|
||||
open: boolean
|
||||
} & TransitionData
|
||||
}
|
||||
type BackdropPropsWeControl = 'aria-hidden'
|
||||
|
||||
let BackdropRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
||||
@@ -778,15 +778,15 @@ function BackdropFn<TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG>(
|
||||
let slot = useMemo(() => {
|
||||
return {
|
||||
open: popoverState === PopoverStates.Open,
|
||||
...transitionData,
|
||||
} satisfies BackdropRenderPropArg
|
||||
}, [popoverState, transitionData])
|
||||
}, [popoverState])
|
||||
|
||||
let ourProps = {
|
||||
ref: backdropRef,
|
||||
id,
|
||||
'aria-hidden': true,
|
||||
onClick: handleClick,
|
||||
...transitionDataAttributes(transitionData),
|
||||
}
|
||||
|
||||
return render({
|
||||
@@ -806,7 +806,7 @@ let DEFAULT_PANEL_TAG = 'div' as const
|
||||
type PanelRenderPropArg = {
|
||||
open: boolean
|
||||
close: (focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => void
|
||||
} & TransitionData
|
||||
}
|
||||
|
||||
let PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
||||
|
||||
@@ -936,9 +936,8 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
return {
|
||||
open: state.popoverState === PopoverStates.Open,
|
||||
close,
|
||||
...transitionData,
|
||||
} satisfies PanelRenderPropArg
|
||||
}, [state.popoverState, close, transitionData])
|
||||
}, [state.popoverState, close])
|
||||
|
||||
let ourProps: Record<string, any> = mergeProps(anchor ? getFloatingPanelProps() : {}, {
|
||||
ref: panelRef,
|
||||
@@ -968,6 +967,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
...style,
|
||||
'--button-width': useElementSize(state.button, true).width,
|
||||
} as React.CSSProperties,
|
||||
...transitionDataAttributes(transitionData),
|
||||
})
|
||||
|
||||
let direction = useTabDirection()
|
||||
|
||||
-1
@@ -132,7 +132,6 @@ exports[`Setup API transition classes should be possible to passthrough the tran
|
||||
class="enter enter-from"
|
||||
data-closed=""
|
||||
data-enter=""
|
||||
data-headlessui-state="closed enter transition"
|
||||
data-transition=""
|
||||
style=""
|
||||
>
|
||||
|
||||
@@ -374,7 +374,6 @@ describe('Setup API', () => {
|
||||
<div
|
||||
class="foo1
|
||||
foo2 leave"
|
||||
data-headlessui-state="leave transition"
|
||||
data-leave=""
|
||||
data-transition=""
|
||||
style=""
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useLatestValue } from '../../hooks/use-latest-value'
|
||||
import { useOnDisappear } from '../../hooks/use-on-disappear'
|
||||
import { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'
|
||||
import { useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { useTransition } from '../../hooks/use-transition'
|
||||
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
|
||||
import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
|
||||
import type { Props, ReactTag } from '../../types'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
@@ -437,7 +437,7 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
|
||||
// a leave transition on the `<Transition>` is done, but there is still a
|
||||
// child `<TransitionChild>` busy, then `visible` would be `false`, while
|
||||
// `state` would still be `TreeStates.Visible`.
|
||||
let [, slot] = useTransition(enabled, container, show, { start, end })
|
||||
let [, transitionData] = useTransition(enabled, container, show, { start, end })
|
||||
|
||||
let ourProps = compact({
|
||||
ref: transitionRef,
|
||||
@@ -451,25 +451,26 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
|
||||
immediate && enterFrom,
|
||||
|
||||
// Map data attributes to `enter`, `enterFrom` and `enterTo` classes
|
||||
slot.enter && enter,
|
||||
slot.enter && slot.closed && enterFrom,
|
||||
slot.enter && !slot.closed && enterTo,
|
||||
transitionData.enter && enter,
|
||||
transitionData.enter && transitionData.closed && enterFrom,
|
||||
transitionData.enter && !transitionData.closed && enterTo,
|
||||
|
||||
// Map data attributes to `leave`, `leaveFrom` and `leaveTo` classes
|
||||
slot.leave && leave,
|
||||
slot.leave && !slot.closed && leaveFrom,
|
||||
slot.leave && slot.closed && leaveTo,
|
||||
transitionData.leave && leave,
|
||||
transitionData.leave && !transitionData.closed && leaveFrom,
|
||||
transitionData.leave && transitionData.closed && leaveTo,
|
||||
|
||||
// Map data attributes to `entered` class (backwards compatibility)
|
||||
!slot.transition && show && entered
|
||||
!transitionData.transition && show && entered
|
||||
)?.trim() || undefined, // If `className` is an empty string, we can omit it
|
||||
...transitionDataAttributes(transitionData),
|
||||
})
|
||||
|
||||
let openClosedState = 0
|
||||
if (state === TreeStates.Visible) openClosedState |= State.Open
|
||||
if (state === TreeStates.Hidden) openClosedState |= State.Closed
|
||||
if (slot.enter) openClosedState |= State.Opening
|
||||
if (slot.leave) openClosedState |= State.Closing
|
||||
if (transitionData.enter) openClosedState |= State.Opening
|
||||
if (transitionData.leave) openClosedState |= State.Closing
|
||||
|
||||
return (
|
||||
<NestingContext.Provider value={nesting}>
|
||||
@@ -477,7 +478,6 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_TRANSITION_CHILD_TAG,
|
||||
features: TransitionChildRenderFeatures,
|
||||
visible: state === TreeStates.Visible,
|
||||
|
||||
@@ -32,13 +32,23 @@ enum TransitionState {
|
||||
Leave = 1 << 2,
|
||||
}
|
||||
|
||||
export type TransitionData = {
|
||||
type TransitionData = {
|
||||
closed?: boolean
|
||||
enter?: boolean
|
||||
leave?: boolean
|
||||
transition?: boolean
|
||||
}
|
||||
|
||||
export function transitionDataAttributes(data: TransitionData) {
|
||||
let attributes: Record<string, string> = {}
|
||||
for (let key in data) {
|
||||
if (data[key as keyof TransitionData] === true) {
|
||||
attributes[`data-${key}`] = ''
|
||||
}
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
export function useTransition(
|
||||
enabled: boolean,
|
||||
elementRef: MutableRefObject<HTMLElement | null>,
|
||||
|
||||
Reference in New Issue
Block a user