Properly merge incoming props (#1265)

* rename inconsistent `passThroughProps` and `passthroughProps` to more
concise `incomingProps`

This is going to make a bit more sense in the next commits of this
branch, hold on!

* split props into `propsWeControl` and `propsTheyControl`

This will allow us to merge the props with a bit more control. Instead
of overriding every prop from the user' props with our props, we can now
merge event listeners.

* update `render` API to accept `propsWeControl` and `propsTheyControl`

* improve the merge logic

This will essentially do the exact same thing we were doing before:
```js
let props = { ...propsTheyControl, ...propsWeControl }
```

But instead of overriding everything, we will merge the event listener
related props like `onClick`, `onKeyDown`, ...

* fix typo in tests

* simplify naming

- Rename `propsWeControl` to `ourProps`
- Rename `propsTheyControl` to `theirProps`

* update changelog
This commit is contained in:
Robin Malfait
2022-03-22 17:32:11 +01:00
committed by GitHub
parent 4f8c615245
commit 3e19aa5c97
37 changed files with 398 additions and 283 deletions
@@ -330,7 +330,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
},
ref: Ref<TTag>
) {
let { name, value, onChange, disabled = false, __demoMode = false, ...passThroughProps } = props
let { name, value, onChange, disabled = false, __demoMode = false, ...theirProps } = props
let comboboxPropsRef = useRef<StateDefinition['comboboxPropsRef']['current']>({
value,
@@ -481,9 +481,11 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
// Ensure that we update the inputRef if the value changes
useIsoMorphicEffect(syncInputValue, [syncInputValue])
let ourProps = ref === null ? {} : { ref }
let renderConfiguration = {
props: ref === null ? passThroughProps : { ...passThroughProps, ref },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_COMBOBOX_TAG,
name: 'Combobox',
@@ -556,7 +558,7 @@ let Input = forwardRefWithAs(function Input<
},
ref: Ref<HTMLInputElement>
) {
let { value, onChange, displayValue, ...passThroughProps } = props
let { value, onChange, displayValue, ...theirProps } = props
let [state] = useComboboxContext('Combobox.Input')
let data = useComboboxData()
let actions = useComboboxActions()
@@ -677,7 +679,7 @@ let Input = forwardRefWithAs(function Input<
[state]
)
let propsWeControl = {
let ourProps = {
ref: inputRef,
id,
role: 'combobox',
@@ -694,7 +696,8 @@ let Input = forwardRefWithAs(function Input<
}
return render({
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_INPUT_TAG,
name: 'Combobox.Input',
@@ -806,8 +809,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
() => ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }),
[state]
)
let passthroughProps = props
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: buttonRef,
id,
type: useResolveButtonType(props, state.buttonRef),
@@ -822,7 +825,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_BUTTON_TAG,
name: 'Combobox.Button',
@@ -855,9 +859,13 @@ let Label = forwardRefWithAs(function Label<TTag extends ElementType = typeof DE
() => ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }),
[state]
)
let propsWeControl = { ref: labelRef, id, onClick: handleClick }
let theirProps = props
let ourProps = { ref: labelRef, id, onClick: handleClick }
return render({
props: { ...props, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_LABEL_TAG,
name: 'Combobox.Label',
@@ -890,7 +898,7 @@ let Options = forwardRefWithAs(function Options<
},
ref: Ref<HTMLUListElement>
) {
let { hold = false, ...passthroughProps } = props
let { hold = false, ...theirProps } = props
let [state] = useComboboxContext('Combobox.Options')
let { optionsPropsRef } = state
@@ -936,7 +944,7 @@ let Options = forwardRefWithAs(function Options<
() => ({ open: state.comboboxState === ComboboxStates.Open }),
[state]
)
let propsWeControl = {
let ourProps = {
'aria-activedescendant':
state.activeOptionIndex === null ? undefined : state.options[state.activeOptionIndex]?.id,
'aria-labelledby': labelledby,
@@ -946,7 +954,8 @@ let Options = forwardRefWithAs(function Options<
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OPTIONS_TAG,
features: OptionsRenderFeatures,
@@ -986,7 +995,7 @@ let Option = forwardRefWithAs(function Option<
},
ref: Ref<HTMLLIElement>
) {
let { disabled = false, value, ...passthroughProps } = props
let { disabled = false, value, ...theirProps } = props
let [state] = useComboboxContext('Combobox.Option')
let data = useComboboxData()
let actions = useComboboxActions()
@@ -1072,7 +1081,7 @@ let Option = forwardRefWithAs(function Option<
[active, selected, disabled]
)
let propsWeControl = {
let ourProps = {
id,
ref: optionRef,
role: 'option',
@@ -1092,7 +1101,8 @@ let Option = forwardRefWithAs(function Option<
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OPTION_TAG,
name: 'Combobox.Option',
@@ -98,11 +98,12 @@ export let Description = forwardRefWithAs(function Description<
useIsoMorphicEffect(() => context.register(id), [id, context.register])
let passThroughProps = props
let propsWeControl = { ref: descriptionRef, ...context.props, id }
let theirProps = props
let ourProps = { ref: descriptionRef, ...context.props, id }
return render({
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot: context.slot || {},
defaultTag: DEFAULT_DESCRIPTION_TAG,
name: context.name || 'Description',
@@ -119,7 +119,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
},
ref: Ref<HTMLDivElement>
) {
let { open, onClose, initialFocus, __demoMode = false, ...rest } = props
let { open, onClose, initialFocus, __demoMode = false, ...theirProps } = props
let [nestedDialogCount, setNestedDialogCount] = useState(0)
let usesOpenClosedState = useOpenClosed()
@@ -292,7 +292,7 @@ let DialogRoot = forwardRefWithAs(function Dialog<
[dialogState]
)
let propsWeControl = {
let ourProps = {
ref: dialogRef,
id,
role: 'dialog',
@@ -303,7 +303,6 @@ let DialogRoot = forwardRefWithAs(function Dialog<
event.stopPropagation()
},
}
let passthroughProps = rest
return (
<StackProvider
@@ -331,7 +330,8 @@ let DialogRoot = forwardRefWithAs(function Dialog<
<ForcePortalRoot force={false}>
<DescriptionProvider slot={slot} name="Dialog.Description">
{render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_DIALOG_TAG,
features: DialogRenderFeatures,
@@ -379,16 +379,18 @@ let Overlay = forwardRefWithAs(function Overlay<
() => ({ open: dialogState === DialogStates.Open }),
[dialogState]
)
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: overlayRef,
id,
'aria-hidden': true,
onClick: handleClick,
}
let passthroughProps = props
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OVERLAY_TAG,
name: 'Dialog.Overlay',
@@ -421,11 +423,13 @@ let Title = forwardRefWithAs(function Title<TTag extends ElementType = typeof DE
() => ({ open: dialogState === DialogStates.Open }),
[dialogState]
)
let propsWeControl = { id }
let passthroughProps = props
let theirProps = props
let ourProps = { ref: titleRef, id }
return render({
props: { ref: titleRef, ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_TITLE_TAG,
name: 'Dialog.Title',
@@ -156,7 +156,7 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure<
},
ref: Ref<TTag>
) {
let { defaultOpen = false, ...passthroughProps } = props
let { defaultOpen = false, ...theirProps } = props
let buttonId = `headlessui-disclosure-button-${useId()}`
let panelId = `headlessui-disclosure-panel-${useId()}`
let internalDisclosureRef = useRef<HTMLElement | null>(null)
@@ -214,6 +214,10 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure<
[disclosureState, close]
)
let ourProps = {
ref: disclosureRef,
}
return (
<DisclosureContext.Provider value={reducerBag}>
<DisclosureAPIContext.Provider value={api}>
@@ -224,7 +228,8 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure<
})}
>
{render({
props: { ref: disclosureRef, ...passthroughProps },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_DISCLOSURE_TAG,
name: 'Disclosure',
@@ -320,8 +325,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
)
let type = useResolveButtonType(props, internalButtonRef)
let passthroughProps = props
let propsWeControl = isWithinPanel
let theirProps = props
let ourProps = isWithinPanel
? { ref: buttonRef, type, onKeyDown: handleKeyDown, onClick: handleClick }
: {
ref: buttonRef,
@@ -337,7 +342,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_BUTTON_TAG,
name: 'Disclosure.Button',
@@ -391,16 +397,18 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
() => ({ open: state.disclosureState === DisclosureStates.Open, close }),
[state, close]
)
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: panelRef,
id: state.panelId,
}
let passthroughProps = props
return (
<DisclosurePanelContext.Provider value={state.panelId}>
{render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_PANEL_TAG,
features: PanelRenderFeatures,
@@ -23,17 +23,18 @@ export let FocusTrap = forwardRefWithAs(function FocusTrap<
) {
let container = useRef<HTMLElement | null>(null)
let focusTrapRef = useSyncRefs(container, ref)
let { initialFocus, ...passthroughProps } = props
let { initialFocus, ...theirProps } = props
let ready = useServerHandoffComplete()
useFocusTrap(container, ready ? FocusTrapFeatures.All : FocusTrapFeatures.None, { initialFocus })
let propsWeControl = {
let ourProps = {
ref: focusTrapRef,
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
defaultTag: DEFAULT_FOCUS_TRAP_TAG,
name: 'FocusTrap',
})
@@ -88,22 +88,28 @@ export let Label = forwardRefWithAs(function Label<
},
ref: Ref<HTMLLabelElement>
) {
let { passive = false, ...passThroughProps } = props
let { passive = false, ...theirProps } = props
let context = useLabelContext()
let id = `headlessui-label-${useId()}`
let labelRef = useSyncRefs(ref)
useIsoMorphicEffect(() => context.register(id), [id, context.register])
let propsWeControl = { ref: labelRef, ...context.props, id }
let ourProps = { ref: labelRef, ...context.props, id }
let allProps = { ...passThroughProps, ...propsWeControl }
// @ts-expect-error props are dynamic via context, some components will
// provide an onClick then we can delete it.
if (passive) delete allProps['onClick']
if (passive) {
if ('onClick' in ourProps) {
delete (ourProps as any)['onClick']
}
if ('onClick' in theirProps) {
delete (theirProps as any)['onClick']
}
}
return render({
props: allProps,
ourProps,
theirProps,
slot: context.slot || {},
defaultTag: DEFAULT_LABEL_TAG,
name: context.name || 'Label',
@@ -314,7 +314,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
},
ref: Ref<TTag>
) {
let { value, name, onChange, disabled = false, horizontal = false, ...passThroughProps } = props
let { value, name, onChange, disabled = false, horizontal = false, ...theirProps } = props
const orientation = horizontal ? 'horizontal' : 'vertical'
let listboxRef = useSyncRefs(ref)
@@ -382,8 +382,11 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
[listboxState, disabled]
)
let ourProps = { ref: listboxRef }
let renderConfiguration = {
props: { ref: listboxRef, ...passThroughProps },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_LISTBOX_TAG,
name: 'Listbox',
@@ -513,8 +516,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
() => ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }),
[state]
)
let passthroughProps = props
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: buttonRef,
id,
type: useResolveButtonType(props, state.buttonRef),
@@ -529,7 +532,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_BUTTON_TAG,
name: 'Listbox.Button',
@@ -562,9 +566,12 @@ let Label = forwardRefWithAs(function Label<TTag extends ElementType = typeof DE
() => ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }),
[state]
)
let propsWeControl = { ref: labelRef, id, onClick: handleClick }
let theirProps = props
let ourProps = { ref: labelRef, id, onClick: handleClick }
return render({
props: { ...props, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_LABEL_TAG,
name: 'Listbox.Label',
@@ -702,7 +709,9 @@ let Options = forwardRefWithAs(function Options<
() => ({ open: state.listboxState === ListboxStates.Open }),
[state]
)
let propsWeControl = {
let theirProps = props
let ourProps = {
'aria-activedescendant':
state.activeOptionIndex === null ? undefined : state.options[state.activeOptionIndex]?.id,
'aria-multiselectable': state.propsRef.current.mode === ValueMode.Multi ? true : undefined,
@@ -714,10 +723,10 @@ let Options = forwardRefWithAs(function Options<
tabIndex: 0,
ref: optionsRef,
}
let passthroughProps = props
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OPTIONS_TAG,
features: OptionsRenderFeatures,
@@ -758,7 +767,7 @@ let Option = forwardRefWithAs(function Option<
},
ref: Ref<HTMLElement>
) {
let { disabled = false, value, ...passthroughProps } = props
let { disabled = false, value, ...theirProps } = props
let [state, dispatch] = useListboxContext('Listbox.Option')
let id = `headlessui-listbox-option-${useId()}`
let active =
@@ -839,7 +848,7 @@ let Option = forwardRefWithAs(function Option<
() => ({ active, selected, disabled }),
[active, selected, disabled]
)
let propsWeControl = {
let ourProps = {
id,
ref: optionRef,
role: 'option',
@@ -859,7 +868,8 @@ let Option = forwardRefWithAs(function Option<
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OPTION_TAG,
name: 'Listbox.Option',
@@ -1005,7 +1005,7 @@ describe('Keyboard interactions', () => {
// Click the menu button again
await click(getMenuButton())
// Active the last menu item
// Activate the last menu item
await mouseMove(getMenuItems()[2])
// Close menu, and invoke the item
@@ -255,6 +255,9 @@ let MenuRoot = forwardRefWithAs(function Menu<TTag extends ElementType = typeof
[menuState]
)
let theirProps = props
let ourProps = { ref: menuRef }
return (
<MenuContext.Provider value={reducerBag}>
<OpenClosedProvider
@@ -264,7 +267,8 @@ let MenuRoot = forwardRefWithAs(function Menu<TTag extends ElementType = typeof
})}
>
{render({
props: { ref: menuRef, ...props },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_MENU_TAG,
name: 'Menu',
@@ -355,8 +359,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
() => ({ open: state.menuState === MenuStates.Open }),
[state]
)
let passthroughProps = props
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: buttonRef,
id,
type: useResolveButtonType(props, state.buttonRef),
@@ -369,7 +373,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_BUTTON_TAG,
name: 'Menu.Button',
@@ -521,7 +526,9 @@ let Items = forwardRefWithAs(function Items<TTag extends ElementType = typeof DE
() => ({ open: state.menuState === MenuStates.Open }),
[state]
)
let propsWeControl = {
let theirProps = props
let ourProps = {
'aria-activedescendant':
state.activeItemIndex === null ? undefined : state.items[state.activeItemIndex]?.id,
'aria-labelledby': state.buttonRef.current?.id,
@@ -532,10 +539,10 @@ let Items = forwardRefWithAs(function Items<TTag extends ElementType = typeof DE
tabIndex: 0,
ref: itemsRef,
}
let passthroughProps = props
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_ITEMS_TAG,
features: ItemsRenderFeatures,
@@ -565,11 +572,10 @@ type MenuItemPropsWeControl =
let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFAULT_ITEM_TAG>(
props: Props<TTag, ItemRenderPropArg, MenuItemPropsWeControl> & {
disabled?: boolean
onClick?: (event: { preventDefault: Function }) => void
},
ref: Ref<HTMLElement>
) {
let { disabled = false, onClick, ...passthroughProps } = props
let { disabled = false, ...theirProps } = props
let [state, dispatch] = useMenuContext('Menu.Item')
let id = `headlessui-menu-item-${useId()}`
let active = state.activeItemIndex !== null ? state.items[state.activeItemIndex].id === id : false
@@ -606,9 +612,8 @@ let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFA
if (disabled) return event.preventDefault()
dispatch({ type: ActionTypes.CloseMenu })
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
if (onClick) return onClick(event)
},
[dispatch, state.buttonRef, disabled, onClick]
[dispatch, state.buttonRef, disabled]
)
let handleFocus = useCallback(() => {
@@ -634,7 +639,7 @@ let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFA
}, [disabled, active, dispatch])
let slot = useMemo<ItemRenderPropArg>(() => ({ active, disabled }), [active, disabled])
let propsWeControl = {
let ourProps = {
id,
ref: itemRef,
role: 'menuitem',
@@ -650,7 +655,8 @@ let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFA
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_ITEM_TAG,
name: 'Menu.Item',
@@ -261,6 +261,9 @@ let PopoverRoot = forwardRefWithAs(function Popover<
[popoverState, close]
)
let theirProps = props
let ourProps = { ref: popoverRef }
return (
<PopoverContext.Provider value={reducerBag}>
<PopoverAPIContext.Provider value={api}>
@@ -271,7 +274,8 @@ let PopoverRoot = forwardRefWithAs(function Popover<
})}
>
{render({
props: { ref: popoverRef, ...props },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_POPOVER_TAG,
name: 'Popover',
@@ -485,8 +489,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
)
let type = useResolveButtonType(props, internalButtonRef)
let passthroughProps = props
let propsWeControl = isWithinPanel
let theirProps = props
let ourProps = isWithinPanel
? {
ref: withinPanelButtonRef,
type,
@@ -505,7 +509,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
}
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_BUTTON_TAG,
name: 'Popover.Button',
@@ -555,16 +560,18 @@ let Overlay = forwardRefWithAs(function Overlay<
() => ({ open: popoverState === PopoverStates.Open }),
[popoverState]
)
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: overlayRef,
id,
'aria-hidden': true,
onClick: handleClick,
}
let passthroughProps = props
return render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OVERLAY_TAG,
features: OverlayRenderFeatures,
@@ -591,7 +598,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
},
ref: Ref<HTMLDivElement>
) {
let { focus = false, ...passthroughProps } = props
let { focus = false, ...theirProps } = props
let [state, dispatch] = usePopoverContext('Popover.Panel')
let { close } = usePopoverAPIContext('Popover.Panel')
@@ -721,7 +728,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
() => ({ open: state.popoverState === PopoverStates.Open, close }),
[state, close]
)
let propsWeControl = {
let ourProps = {
ref: panelRef,
id: state.panelId,
onKeyDown: handleKeyDown,
@@ -730,7 +737,8 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
return (
<PopoverPanelContext.Provider value={state.panelId}>
{render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_PANEL_TAG,
features: PanelRenderFeatures,
@@ -814,13 +822,15 @@ let Group = forwardRefWithAs(function Group<TTag extends ElementType = typeof DE
)
let slot = useMemo<GroupRenderPropArg>(() => ({}), [])
let propsWeControl = { ref: groupRef }
let passthroughProps = props
let theirProps = props
let ourProps = { ref: groupRef }
return (
<PopoverGroupContext.Provider value={contextBag}>
{render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_GROUP_TAG,
name: 'Popover.Group',
@@ -69,7 +69,7 @@ interface PortalRenderPropArg {}
let PortalRoot = forwardRefWithAs(function Portal<
TTag extends ElementType = typeof DEFAULT_PORTAL_TAG
>(props: Props<TTag, PortalRenderPropArg>, ref: Ref<HTMLElement>) {
let passthroughProps = props
let theirProps = props
let internalPortalRootRef = useRef<HTMLElement | null>(null)
let portalRef = useSyncRefs(
optionalRef<typeof internalPortalRootRef['current']>((ref) => {
@@ -105,11 +105,14 @@ let PortalRoot = forwardRefWithAs(function Portal<
if (!ready) return null
let ourProps = { ref: portalRef }
return !target || !element
? null
: createPortal(
render({
props: { ref: portalRef, ...passthroughProps },
ourProps,
theirProps,
defaultTag: DEFAULT_PORTAL_TAG,
name: 'Portal',
}),
@@ -130,13 +133,16 @@ let Group = forwardRefWithAs(function Group<TTag extends ElementType = typeof DE
},
ref: Ref<HTMLElement>
) {
let { target, ...passthroughProps } = props
let { target, ...theirProps } = props
let groupRef = useSyncRefs(ref)
let ourProps = { ref: groupRef }
return (
<PortalGroupContext.Provider value={target}>
{render({
props: { ref: groupRef, ...passthroughProps },
ourProps,
theirProps,
defaultTag: DEFAULT_GROUP_TAG,
name: 'Popover.Group',
})}
@@ -121,7 +121,7 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup<
},
ref: Ref<HTMLElement>
) {
let { value, name, onChange, disabled = false, ...passThroughProps } = props
let { value, name, onChange, disabled = false, ...theirProps } = props
let [{ options }, dispatch] = useReducer(stateReducer, {
options: [],
} as StateDefinition)
@@ -252,7 +252,7 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup<
[registerOption, firstOption, containsCheckedOption, triggerChange, disabled, value]
)
let propsWeControl = {
let ourProps = {
ref: radioGroupRef,
id,
role: 'radiogroup',
@@ -262,7 +262,8 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup<
}
let renderConfiguration = {
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
defaultTag: DEFAULT_RADIO_GROUP_TAG,
name: 'RadioGroup',
}
@@ -341,7 +342,7 @@ let Option = forwardRefWithAs(function Option<
let [describedby, DescriptionProvider] = useDescriptions()
let { addFlag, removeFlag, hasFlag } = useFlags(OptionState.Empty)
let { value, disabled = false, ...passThroughProps } = props
let { value, disabled = false, ...theirProps } = props
let propsRef = useRef({ value, disabled })
useIsoMorphicEffect(() => {
@@ -379,7 +380,7 @@ let Option = forwardRefWithAs(function Option<
let isDisabled = radioGroupDisabled || disabled
let checked = radioGroupValue === value
let propsWeControl = {
let ourProps = {
ref: optionRef,
id,
role: 'radio',
@@ -406,7 +407,8 @@ let Option = forwardRefWithAs(function Option<
<DescriptionProvider name="RadioGroup.Description">
<LabelProvider name="RadioGroup.Label">
{render({
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_OPTION_TAG,
name: 'RadioGroup.Option',
@@ -49,6 +49,9 @@ function Group<TTag extends ElementType = typeof DEFAULT_GROUP_TAG>(props: Props
[switchElement, setSwitchElement, labelledby, describedby]
)
let ourProps = {}
let theirProps = props
return (
<DescriptionProvider name="Switch.Description">
<LabelProvider
@@ -62,7 +65,12 @@ function Group<TTag extends ElementType = typeof DEFAULT_GROUP_TAG>(props: Props
}}
>
<GroupContext.Provider value={context}>
{render({ props, defaultTag: DEFAULT_GROUP_TAG, name: 'Switch.Group' })}
{render({
ourProps,
theirProps,
defaultTag: DEFAULT_GROUP_TAG,
name: 'Switch.Group',
})}
</GroupContext.Provider>
</LabelProvider>
</DescriptionProvider>
@@ -101,7 +109,7 @@ let SwitchRoot = forwardRefWithAs(function Switch<
},
ref: Ref<HTMLElement>
) {
let { checked, onChange, name, value, ...passThroughProps } = props
let { checked, onChange, name, value, ...theirProps } = props
let id = `headlessui-switch-${useId()}`
let groupContext = useContext(GroupContext)
let internalSwitchRef = useRef<HTMLButtonElement | null>(null)
@@ -136,7 +144,7 @@ let SwitchRoot = forwardRefWithAs(function Switch<
)
let slot = useMemo<SwitchRenderPropArg>(() => ({ checked }), [checked])
let propsWeControl = {
let ourProps = {
id,
ref: switchRef,
role: 'switch',
@@ -151,7 +159,8 @@ let SwitchRoot = forwardRefWithAs(function Switch<
}
let renderConfiguration = {
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_SWITCH_TAG,
name: 'Switch',
@@ -146,7 +146,7 @@ let Tabs = forwardRefWithAs(function Tabs<TTag extends ElementType = typeof DEFA
manual = false,
onChange,
selectedIndex = null,
...passThroughProps
...theirProps
} = props
const orientation = vertical ? 'vertical' : 'horizontal'
const activation = manual ? 'manual' : 'auto'
@@ -228,6 +228,10 @@ let Tabs = forwardRefWithAs(function Tabs<TTag extends ElementType = typeof DEFA
let SSRCounter = useRef(0)
let ourProps = {
ref: tabsRef,
}
return (
<TabsSSRContext.Provider value={typeof window === 'undefined' ? SSRCounter : null}>
<TabsContext.Provider value={providerBag}>
@@ -244,7 +248,8 @@ let Tabs = forwardRefWithAs(function Tabs<TTag extends ElementType = typeof DEFA
}}
/>
{render({
props: { ref: tabsRef, ...passThroughProps },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_TABS_TAG,
name: 'Tabs',
@@ -270,15 +275,17 @@ let List = forwardRefWithAs(function List<TTag extends ElementType = typeof DEFA
let listRef = useSyncRefs(ref)
let slot = { selectedIndex }
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: listRef,
role: 'tablist',
'aria-orientation': orientation,
}
let passThroughProps = props
return render({
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_LIST_TAG,
name: 'Tabs.List',
@@ -377,7 +384,9 @@ let TabRoot = forwardRefWithAs(function Tab<TTag extends ElementType = typeof DE
}, [])
let slot = useMemo(() => ({ selected }), [selected])
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: tabRef,
onKeyDown: handleKeyDown,
onFocus: activation === 'manual' ? handleFocus : handleSelection,
@@ -390,10 +399,10 @@ let TabRoot = forwardRefWithAs(function Tab<TTag extends ElementType = typeof DE
'aria-selected': selected,
tabIndex: selected ? 0 : -1,
}
let passThroughProps = props
return render({
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_TAB_TAG,
name: 'Tabs.Tab',
@@ -416,8 +425,12 @@ let Panels = forwardRefWithAs(function Panels<TTag extends ElementType = typeof
let slot = useMemo(() => ({ selectedIndex }), [selectedIndex])
let theirProps = props
let ourProps = { ref: panelsRef }
return render({
props: { ref: panelsRef, ...props },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_PANELS_TAG,
name: 'Tabs.Panels',
@@ -462,7 +475,9 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
SSRContext === null ? myIndex === selectedIndex : SSRContext.current++ === selectedIndex
let slot = useMemo(() => ({ selected }), [selected])
let propsWeControl = {
let theirProps = props
let ourProps = {
ref: panelRef,
id,
role: 'tabpanel',
@@ -470,10 +485,9 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
tabIndex: selected ? 0 : -1,
}
let passThroughProps = props
return render({
props: { ...passThroughProps, ...propsWeControl },
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_PANEL_TAG,
features: PanelRenderFeatures,
@@ -348,8 +348,8 @@ let TransitionChild = forwardRefWithAs(function TransitionChild<
}
}, [show, skip, state])
let propsWeControl = { ref: transitionRef }
let passthroughProps = rest
let theirProps = rest
let ourProps = { ref: transitionRef }
return (
<NestingContext.Provider value={nesting}>
@@ -360,7 +360,8 @@ let TransitionChild = forwardRefWithAs(function TransitionChild<
})}
>
{render({
props: { ...passthroughProps, ...propsWeControl },
ourProps,
theirProps,
defaultTag: DEFAULT_TRANSITION_CHILD_TAG,
features: TransitionChildRenderFeatures,
visible: state === TreeStates.Visible,
@@ -375,7 +376,7 @@ let TransitionRoot = forwardRefWithAs(function Transition<
TTag extends ElementType = typeof DEFAULT_TRANSITION_CHILD_TAG
>(props: TransitionChildProps<TTag> & { show?: boolean; appear?: boolean }, ref: Ref<HTMLElement>) {
// @ts-expect-error
let { show, appear = false, unmount, ...passthroughProps } = props as typeof props
let { show, appear = false, unmount, ...theirProps } = props as typeof props
let transitionRef = useSyncRefs(ref)
let usesOpenClosedState = useOpenClosed()
@@ -417,13 +418,12 @@ let TransitionRoot = forwardRefWithAs(function Transition<
<NestingContext.Provider value={nestingBag}>
<TransitionContext.Provider value={transitionBag}>
{render({
props: {
ourProps: {
...sharedProps,
as: Fragment,
children: (
<TransitionChild ref={transitionRef} {...sharedProps} {...passthroughProps} />
),
children: <TransitionChild ref={transitionRef} {...sharedProps} {...theirProps} />,
},
theirProps: {},
defaultTag: Fragment,
features: TransitionChildRenderFeatures,
visible: state === TreeStates.Visible,