From 221be4a0c4180293de6cd17fe81e438e6e2a78b0 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 22 Mar 2022 11:11:38 +0100 Subject: [PATCH 1/7] 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! --- .../src/components/combobox/combobox.tsx | 20 ++++++++--------- .../components/description/description.tsx | 4 ++-- .../src/components/dialog/dialog.tsx | 12 +++++----- .../src/components/disclosure/disclosure.tsx | 12 +++++----- .../src/components/focus-trap/focus-trap.tsx | 4 ++-- .../src/components/label/label.tsx | 4 ++-- .../src/components/listbox/listbox.tsx | 16 +++++++------- .../src/components/menu/menu.tsx | 12 +++++----- .../src/components/popover/popover.tsx | 16 +++++++------- .../src/components/portal/portal.tsx | 8 +++---- .../components/radio-group/radio-group.tsx | 8 +++---- .../src/components/switch/switch.tsx | 4 ++-- .../src/components/tabs/tabs.tsx | 16 +++++++------- .../src/components/transitions/transition.tsx | 10 ++++----- .../@headlessui-react/src/utils/render.ts | 22 +++++++++---------- .../src/components/combobox/combobox.ts | 12 +++++----- .../src/components/description/description.ts | 4 ++-- .../src/components/dialog/dialog.ts | 12 +++++----- .../src/components/disclosure/disclosure.ts | 4 ++-- .../src/components/focus-trap/focus-trap.ts | 4 ++-- .../src/components/label/label.ts | 4 ++-- .../src/components/listbox/listbox.ts | 8 +++---- .../src/components/menu/menu.ts | 4 ++-- .../src/components/portal/portal.ts | 4 ++-- .../src/components/radio-group/radio-group.ts | 8 +++---- .../src/components/switch/switch.ts | 4 ++-- .../src/components/tabs/tabs.ts | 4 ++-- .../src/components/transitions/transition.ts | 8 +++---- .../src/internal/portal-force-root.ts | 4 ++-- packages/@headlessui-vue/src/utils/render.ts | 10 ++++----- 30 files changed, 130 insertions(+), 132 deletions(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index e7ee8a32d3..4dcc5e3da6 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -330,7 +330,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< }, ref: Ref ) { - let { name, value, onChange, disabled = false, __demoMode = false, ...passThroughProps } = props + let { name, value, onChange, disabled = false, __demoMode = false, ...incomingProps } = props let comboboxPropsRef = useRef({ value, @@ -483,7 +483,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< useIsoMorphicEffect(syncInputValue, [syncInputValue]) let renderConfiguration = { - props: ref === null ? passThroughProps : { ...passThroughProps, ref }, + props: ref === null ? incomingProps : { ...incomingProps, ref }, slot, defaultTag: DEFAULT_COMBOBOX_TAG, name: 'Combobox', @@ -556,7 +556,7 @@ let Input = forwardRefWithAs(function Input< }, ref: Ref ) { - let { value, onChange, displayValue, ...passThroughProps } = props + let { value, onChange, displayValue, ...incomingProps } = props let [state] = useComboboxContext('Combobox.Input') let data = useComboboxData() let actions = useComboboxActions() @@ -694,7 +694,7 @@ let Input = forwardRefWithAs(function Input< } return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_INPUT_TAG, name: 'Combobox.Input', @@ -806,7 +806,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }), [state] ) - let passthroughProps = props + let incomingProps = props let propsWeControl = { ref: buttonRef, id, @@ -822,7 +822,7 @@ let Button = forwardRefWithAs(function Button ) { - let { hold = false, ...passthroughProps } = props + let { hold = false, ...incomingProps } = props let [state] = useComboboxContext('Combobox.Options') let { optionsPropsRef } = state @@ -946,7 +946,7 @@ let Options = forwardRefWithAs(function Options< } return render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_OPTIONS_TAG, features: OptionsRenderFeatures, @@ -986,7 +986,7 @@ let Option = forwardRefWithAs(function Option< }, ref: Ref ) { - let { disabled = false, value, ...passthroughProps } = props + let { disabled = false, value, ...incomingProps } = props let [state] = useComboboxContext('Combobox.Option') let data = useComboboxData() let actions = useComboboxActions() @@ -1092,7 +1092,7 @@ let Option = forwardRefWithAs(function Option< } return render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_OPTION_TAG, name: 'Combobox.Option', diff --git a/packages/@headlessui-react/src/components/description/description.tsx b/packages/@headlessui-react/src/components/description/description.tsx index 2b4336aef9..a9124df094 100644 --- a/packages/@headlessui-react/src/components/description/description.tsx +++ b/packages/@headlessui-react/src/components/description/description.tsx @@ -98,11 +98,11 @@ export let Description = forwardRefWithAs(function Description< useIsoMorphicEffect(() => context.register(id), [id, context.register]) - let passThroughProps = props + let incomingProps = props let propsWeControl = { ref: descriptionRef, ...context.props, id } return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot: context.slot || {}, defaultTag: DEFAULT_DESCRIPTION_TAG, name: context.name || 'Description', diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index 3de5c0ffb3..c07a3d2069 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -303,7 +303,7 @@ let DialogRoot = forwardRefWithAs(function Dialog< event.stopPropagation() }, } - let passthroughProps = rest + let incomingProps = rest return ( {render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_DIALOG_TAG, features: DialogRenderFeatures, @@ -385,10 +385,10 @@ let Overlay = forwardRefWithAs(function Overlay< 'aria-hidden': true, onClick: handleClick, } - let passthroughProps = props + let incomingProps = props return render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_OVERLAY_TAG, name: 'Dialog.Overlay', @@ -422,10 +422,10 @@ let Title = forwardRefWithAs(function Title ) { - let { defaultOpen = false, ...passthroughProps } = props + let { defaultOpen = false, ...incomingProps } = props let buttonId = `headlessui-disclosure-button-${useId()}` let panelId = `headlessui-disclosure-panel-${useId()}` let internalDisclosureRef = useRef(null) @@ -224,7 +224,7 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure< })} > {render({ - props: { ref: disclosureRef, ...passthroughProps }, + props: { ref: disclosureRef, ...incomingProps }, slot, defaultTag: DEFAULT_DISCLOSURE_TAG, name: 'Disclosure', @@ -320,7 +320,7 @@ let Button = forwardRefWithAs(function Button {render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_PANEL_TAG, features: PanelRenderFeatures, diff --git a/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx b/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx index 15e882a5f7..64e95baeec 100644 --- a/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx +++ b/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx @@ -23,7 +23,7 @@ export let FocusTrap = forwardRefWithAs(function FocusTrap< ) { let container = useRef(null) let focusTrapRef = useSyncRefs(container, ref) - let { initialFocus, ...passthroughProps } = props + let { initialFocus, ...incomingProps } = props let ready = useServerHandoffComplete() useFocusTrap(container, ready ? FocusTrapFeatures.All : FocusTrapFeatures.None, { initialFocus }) @@ -33,7 +33,7 @@ export let FocusTrap = forwardRefWithAs(function FocusTrap< } return render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, defaultTag: DEFAULT_FOCUS_TRAP_TAG, name: 'FocusTrap', }) diff --git a/packages/@headlessui-react/src/components/label/label.tsx b/packages/@headlessui-react/src/components/label/label.tsx index 4da9864126..1330c95ab5 100644 --- a/packages/@headlessui-react/src/components/label/label.tsx +++ b/packages/@headlessui-react/src/components/label/label.tsx @@ -88,7 +88,7 @@ export let Label = forwardRefWithAs(function Label< }, ref: Ref ) { - let { passive = false, ...passThroughProps } = props + let { passive = false, ...incomingProps } = props let context = useLabelContext() let id = `headlessui-label-${useId()}` let labelRef = useSyncRefs(ref) @@ -97,7 +97,7 @@ export let Label = forwardRefWithAs(function Label< let propsWeControl = { ref: labelRef, ...context.props, id } - let allProps = { ...passThroughProps, ...propsWeControl } + let allProps = { ...incomingProps, ...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'] diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index 9da7c7ca2d..81092f4c70 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -314,7 +314,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox< }, ref: Ref ) { - let { value, name, onChange, disabled = false, horizontal = false, ...passThroughProps } = props + let { value, name, onChange, disabled = false, horizontal = false, ...incomingProps } = props const orientation = horizontal ? 'horizontal' : 'vertical' let listboxRef = useSyncRefs(ref) @@ -383,7 +383,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox< ) let renderConfiguration = { - props: { ref: listboxRef, ...passThroughProps }, + props: { ref: listboxRef, ...incomingProps }, slot, defaultTag: DEFAULT_LISTBOX_TAG, name: 'Listbox', @@ -513,7 +513,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }), [state] ) - let passthroughProps = props + let incomingProps = props let propsWeControl = { ref: buttonRef, id, @@ -529,7 +529,7 @@ let Button = forwardRefWithAs(function Button ) { - let { disabled = false, value, ...passthroughProps } = props + let { disabled = false, value, ...incomingProps } = props let [state, dispatch] = useListboxContext('Listbox.Option') let id = `headlessui-listbox-option-${useId()}` let active = @@ -859,7 +859,7 @@ let Option = forwardRefWithAs(function Option< } return render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_OPTION_TAG, name: 'Listbox.Option', diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index cfeb84c9c5..10ea7cc481 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -355,7 +355,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.menuState === MenuStates.Open }), [state] ) - let passthroughProps = props + let incomingProps = props let propsWeControl = { ref: buttonRef, id, @@ -369,7 +369,7 @@ let Button = forwardRefWithAs(function Button ) { - let { disabled = false, onClick, ...passthroughProps } = props + let { disabled = false, onClick, ...incomingProps } = 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 @@ -650,7 +650,7 @@ let Item = forwardRefWithAs(function Item ) { - let { focus = false, ...passthroughProps } = props + let { focus = false, ...incomingProps } = props let [state, dispatch] = usePopoverContext('Popover.Panel') let { close } = usePopoverAPIContext('Popover.Panel') @@ -730,7 +730,7 @@ let Panel = forwardRefWithAs(function Panel {render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_PANEL_TAG, features: PanelRenderFeatures, @@ -815,12 +815,12 @@ let Group = forwardRefWithAs(function Group(() => ({}), []) let propsWeControl = { ref: groupRef } - let passthroughProps = props + let incomingProps = props return ( {render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_GROUP_TAG, name: 'Popover.Group', diff --git a/packages/@headlessui-react/src/components/portal/portal.tsx b/packages/@headlessui-react/src/components/portal/portal.tsx index 992ee15dd2..e3110390a2 100644 --- a/packages/@headlessui-react/src/components/portal/portal.tsx +++ b/packages/@headlessui-react/src/components/portal/portal.tsx @@ -69,7 +69,7 @@ interface PortalRenderPropArg {} let PortalRoot = forwardRefWithAs(function Portal< TTag extends ElementType = typeof DEFAULT_PORTAL_TAG >(props: Props, ref: Ref) { - let passthroughProps = props + let incomingProps = props let internalPortalRootRef = useRef(null) let portalRef = useSyncRefs( optionalRef((ref) => { @@ -109,7 +109,7 @@ let PortalRoot = forwardRefWithAs(function Portal< ? null : createPortal( render({ - props: { ref: portalRef, ...passthroughProps }, + props: { ref: portalRef, ...incomingProps }, defaultTag: DEFAULT_PORTAL_TAG, name: 'Portal', }), @@ -130,13 +130,13 @@ let Group = forwardRefWithAs(function Group ) { - let { target, ...passthroughProps } = props + let { target, ...incomingProps } = props let groupRef = useSyncRefs(ref) return ( {render({ - props: { ref: groupRef, ...passthroughProps }, + props: { ref: groupRef, ...incomingProps }, defaultTag: DEFAULT_GROUP_TAG, name: 'Popover.Group', })} diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx index 1788946225..e0b4677b05 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -121,7 +121,7 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup< }, ref: Ref ) { - let { value, name, onChange, disabled = false, ...passThroughProps } = props + let { value, name, onChange, disabled = false, ...incomingProps } = props let [{ options }, dispatch] = useReducer(stateReducer, { options: [], } as StateDefinition) @@ -262,7 +262,7 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup< } let renderConfiguration = { - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, defaultTag: DEFAULT_RADIO_GROUP_TAG, name: 'RadioGroup', } @@ -341,7 +341,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, ...incomingProps } = props let propsRef = useRef({ value, disabled }) useIsoMorphicEffect(() => { @@ -406,7 +406,7 @@ let Option = forwardRefWithAs(function Option< {render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_OPTION_TAG, name: 'RadioGroup.Option', diff --git a/packages/@headlessui-react/src/components/switch/switch.tsx b/packages/@headlessui-react/src/components/switch/switch.tsx index 16e86b48d8..e774d9e817 100644 --- a/packages/@headlessui-react/src/components/switch/switch.tsx +++ b/packages/@headlessui-react/src/components/switch/switch.tsx @@ -101,7 +101,7 @@ let SwitchRoot = forwardRefWithAs(function Switch< }, ref: Ref ) { - let { checked, onChange, name, value, ...passThroughProps } = props + let { checked, onChange, name, value, ...incomingProps } = props let id = `headlessui-switch-${useId()}` let groupContext = useContext(GroupContext) let internalSwitchRef = useRef(null) @@ -151,7 +151,7 @@ let SwitchRoot = forwardRefWithAs(function Switch< } let renderConfiguration = { - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, defaultTag: DEFAULT_SWITCH_TAG, name: 'Switch', diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index 10b249517d..c90ab0b4a0 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -146,7 +146,7 @@ let Tabs = forwardRefWithAs(function Tabs {render({ - props: { ref: tabsRef, ...passThroughProps }, + props: { ref: tabsRef, ...incomingProps }, slot, defaultTag: DEFAULT_TABS_TAG, name: 'Tabs', @@ -275,10 +275,10 @@ let List = forwardRefWithAs(function List @@ -360,7 +360,7 @@ let TransitionChild = forwardRefWithAs(function TransitionChild< })} > {render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, defaultTag: DEFAULT_TRANSITION_CHILD_TAG, features: TransitionChildRenderFeatures, visible: state === TreeStates.Visible, @@ -375,7 +375,7 @@ let TransitionRoot = forwardRefWithAs(function Transition< TTag extends ElementType = typeof DEFAULT_TRANSITION_CHILD_TAG >(props: TransitionChildProps & { show?: boolean; appear?: boolean }, ref: Ref) { // @ts-expect-error - let { show, appear = false, unmount, ...passthroughProps } = props as typeof props + let { show, appear = false, unmount, ...incomingProps } = props as typeof props let transitionRef = useSyncRefs(ref) let usesOpenClosedState = useOpenClosed() @@ -420,9 +420,7 @@ let TransitionRoot = forwardRefWithAs(function Transition< props: { ...sharedProps, as: Fragment, - children: ( - - ), + children: , }, defaultTag: Fragment, features: TransitionChildRenderFeatures, diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index eedd1e5ab9..c8addf24cf 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -106,7 +106,7 @@ function _render( as: Component = tag, children, refName = 'ref', - ...passThroughProps + ...incomingProps } = omit(props, ['unmount', 'static']) // This allows us to use `` @@ -117,12 +117,12 @@ function _render( | ReactElement[] // Allow for className to be a function with the slot as the contents - if (passThroughProps.className && typeof passThroughProps.className === 'function') { - ;(passThroughProps as any).className = passThroughProps.className(slot) + if (incomingProps.className && typeof incomingProps.className === 'function') { + ;(incomingProps as any).className = incomingProps.className(slot) } if (Component === Fragment) { - if (Object.keys(compact(passThroughProps)).length > 0) { + if (Object.keys(compact(incomingProps)).length > 0) { if ( !isValidElement(resolvedChildren) || (Array.isArray(resolvedChildren) && resolvedChildren.length > 1) @@ -133,7 +133,7 @@ function _render( '', `The current component <${name} /> is rendering a "Fragment".`, `However we need to passthrough the following props:`, - Object.keys(passThroughProps) + Object.keys(incomingProps) .map((line) => ` - ${line}`) .join('\n'), '', @@ -153,7 +153,7 @@ function _render( Object.assign( {}, // Filter out undefined values so that they don't override the existing values - mergeEventFunctions(compact(omit(passThroughProps, ['ref'])), resolvedChildren.props, [ + mergeEventFunctions(compact(omit(incomingProps, ['ref'])), resolvedChildren.props, [ 'onClick', ]), refRelatedProps @@ -164,7 +164,7 @@ function _render( return createElement( Component, - Object.assign({}, omit(passThroughProps, ['ref']), Component !== Fragment && refRelatedProps), + Object.assign({}, omit(incomingProps, ['ref']), Component !== Fragment && refRelatedProps), resolvedChildren ) } @@ -184,17 +184,17 @@ function _render( * so that we can refactor this later (if needed). */ function mergeEventFunctions( - passThroughProps: Record, + incomingProps: Record, existingProps: Record, functionsToMerge: string[] ) { - let clone = Object.assign({}, passThroughProps) + let clone = Object.assign({}, incomingProps) for (let func of functionsToMerge) { - if (passThroughProps[func] !== undefined && existingProps[func] !== undefined) { + if (incomingProps[func] !== undefined && existingProps[func] !== undefined) { Object.assign(clone, { [func](event: { defaultPrevented: boolean }) { // Props we control - if (!event.defaultPrevented) passThroughProps[func](event) + if (!event.defaultPrevented) incomingProps[func](event) // Existing props on the component if (!event.defaultPrevented) existingProps[func](event) diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts index 45581ff4d6..11a69db6ed 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -342,7 +342,7 @@ export let Combobox = defineComponent({ ) return () => { - let { name, modelValue, disabled, ...passThroughProps } = props + let { name, modelValue, disabled, ...incomingProps } = props let slot = { open: comboboxState.value === ComboboxStates.Open, disabled, @@ -351,7 +351,7 @@ export let Combobox = defineComponent({ } let renderConfiguration = { - props: omit(passThroughProps, ['onUpdate:modelValue']), + props: omit(incomingProps, ['onUpdate:modelValue']), slot, slots, attrs, @@ -647,10 +647,10 @@ export let ComboboxInput = defineComponent({ tabIndex: 0, ref: api.inputRef, } - let passThroughProps = omit(props, ['displayValue']) + let incomingProps = omit(props, ['displayValue']) return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, @@ -719,10 +719,10 @@ export let ComboboxOptions = defineComponent({ ref: api.optionsRef, role: 'listbox', } - let passThroughProps = omit(props, ['hold']) + let incomingProps = omit(props, ['hold']) return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/description/description.ts b/packages/@headlessui-vue/src/components/description/description.ts index 6ae3423979..c60a2dcec0 100644 --- a/packages/@headlessui-vue/src/components/description/description.ts +++ b/packages/@headlessui-vue/src/components/description/description.ts @@ -78,7 +78,7 @@ export let Description = defineComponent({ return () => { let { name = 'Description', slot = ref({}), props = {} } = context - let passThroughProps = myProps + let incomingProps = myProps let propsWeControl = { ...Object.entries(props).reduce( (acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }), @@ -88,7 +88,7 @@ export let Description = defineComponent({ } return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot: slot.value, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/dialog/dialog.ts b/packages/@headlessui-vue/src/components/dialog/dialog.ts index 886f972502..5fa0e8f2af 100644 --- a/packages/@headlessui-vue/src/components/dialog/dialog.ts +++ b/packages/@headlessui-vue/src/components/dialog/dialog.ts @@ -275,7 +275,7 @@ export let Dialog = defineComponent({ 'aria-describedby': describedby.value, onClick: handleClick, } - let { open: _, initialFocus, ...passThroughProps } = props + let { open: _, initialFocus, ...incomingProps } = props let slot = { open: dialogState.value === DialogStates.Open } @@ -284,7 +284,7 @@ export let Dialog = defineComponent({ h(PortalGroup, { target: internalDialogRef.value }, () => h(ForcePortalRoot, { force: false }, () => render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, @@ -324,10 +324,10 @@ export let DialogOverlay = defineComponent({ 'aria-hidden': true, onClick: handleClick, } - let passThroughProps = props + let incomingProps = props return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot: { open: api.dialogState.value === DialogStates.Open }, attrs, slots, @@ -355,10 +355,10 @@ export let DialogTitle = defineComponent({ return () => { let propsWeControl = { id } - let passThroughProps = props + let incomingProps = props return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot: { open: api.dialogState.value === DialogStates.Open }, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/disclosure/disclosure.ts b/packages/@headlessui-vue/src/components/disclosure/disclosure.ts index d68e3ef0de..79588daf0b 100644 --- a/packages/@headlessui-vue/src/components/disclosure/disclosure.ts +++ b/packages/@headlessui-vue/src/components/disclosure/disclosure.ts @@ -118,9 +118,9 @@ export let Disclosure = defineComponent({ ) return () => { - let { defaultOpen: _, ...passThroughProps } = props + let { defaultOpen: _, ...incomingProps } = props let slot = { open: disclosureState.value === DisclosureStates.Open, close: api.close } - return render({ props: passThroughProps, slot, slots, attrs, name: 'Disclosure' }) + return render({ props: incomingProps, slot, slots, attrs, name: 'Disclosure' }) } }, }) diff --git a/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts b/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts index e4d14cc1df..6af1362297 100644 --- a/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts +++ b/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts @@ -26,10 +26,10 @@ export let FocusTrap = defineComponent({ return () => { let slot = {} let propsWeControl = { ref: container } - let { initialFocus, ...passThroughProps } = props + let { initialFocus, ...incomingProps } = props return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/label/label.ts b/packages/@headlessui-vue/src/components/label/label.ts index f29b99fae6..f952868114 100644 --- a/packages/@headlessui-vue/src/components/label/label.ts +++ b/packages/@headlessui-vue/src/components/label/label.ts @@ -77,7 +77,7 @@ export let Label = defineComponent({ return () => { let { name = 'Label', slot = {}, props = {} } = context - let { passive, ...passThroughProps } = myProps + let { passive, ...incomingProps } = myProps let propsWeControl = { ...Object.entries(props).reduce( (acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }), @@ -85,7 +85,7 @@ export let Label = defineComponent({ ), id, } - let allProps = { ...passThroughProps, ...propsWeControl } + let allProps = { ...incomingProps, ...propsWeControl } // @ts-expect-error props are dynamic via context, some components will // provide an onClick then we can delete it. diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.ts b/packages/@headlessui-vue/src/components/listbox/listbox.ts index f7eb005a3a..c3636f0ff1 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.ts +++ b/packages/@headlessui-vue/src/components/listbox/listbox.ts @@ -305,11 +305,11 @@ export let Listbox = defineComponent({ ) return () => { - let { name, modelValue, disabled, ...passThroughProps } = props + let { name, modelValue, disabled, ...incomingProps } = props let slot = { open: listboxState.value === ListboxStates.Open, disabled } let renderConfiguration = { - props: omit(passThroughProps, ['onUpdate:modelValue', 'horizontal']), + props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal']), slot, slots, attrs, @@ -585,10 +585,10 @@ export let ListboxOptions = defineComponent({ tabIndex: 0, ref: api.optionsRef, } - let passThroughProps = props + let incomingProps = props return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/menu/menu.ts b/packages/@headlessui-vue/src/components/menu/menu.ts index 690d5548ff..c67a197ce5 100644 --- a/packages/@headlessui-vue/src/components/menu/menu.ts +++ b/packages/@headlessui-vue/src/components/menu/menu.ts @@ -457,10 +457,10 @@ export let MenuItems = defineComponent({ ref: api.itemsRef, } - let passThroughProps = props + let incomingProps = props return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/portal/portal.ts b/packages/@headlessui-vue/src/components/portal/portal.ts index d36a0b1d09..04a6fdac5f 100644 --- a/packages/@headlessui-vue/src/components/portal/portal.ts +++ b/packages/@headlessui-vue/src/components/portal/portal.ts @@ -116,9 +116,9 @@ export let PortalGroup = defineComponent({ provide(PortalGroupContext, api) return () => { - let { target: _, ...passThroughProps } = props + let { target: _, ...incomingProps } = props - return render({ props: passThroughProps, slot: {}, attrs, slots, name: 'PortalGroup' }) + return render({ props: incomingProps, slot: {}, attrs, slots, name: 'PortalGroup' }) } }, }) diff --git a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts index 963240d287..466862d717 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts @@ -191,7 +191,7 @@ export let RadioGroup = defineComponent({ let id = `headlessui-radiogroup-${useId()}` return () => { - let { modelValue, disabled, name, ...passThroughProps } = props + let { modelValue, disabled, name, ...incomingProps } = props let propsWeControl = { ref: radioGroupRef, @@ -203,7 +203,7 @@ export let RadioGroup = defineComponent({ } let renderConfiguration = { - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot: {}, attrs, slots, @@ -290,7 +290,7 @@ export let RadioGroupOption = defineComponent({ } return () => { - let passThroughProps = omit(props, ['value', 'disabled']) + let incomingProps = omit(props, ['value', 'disabled']) let slot = { checked: checked.value, @@ -313,7 +313,7 @@ export let RadioGroupOption = defineComponent({ } return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/switch/switch.ts b/packages/@headlessui-vue/src/components/switch/switch.ts index 590321824c..d817a78c74 100644 --- a/packages/@headlessui-vue/src/components/switch/switch.ts +++ b/packages/@headlessui-vue/src/components/switch/switch.ts @@ -103,7 +103,7 @@ export let Switch = defineComponent({ } return () => { - let { name, value, modelValue, ...passThroughProps } = props + let { name, value, modelValue, ...incomingProps } = props let slot = { checked: modelValue } let propsWeControl = { id, @@ -120,7 +120,7 @@ export let Switch = defineComponent({ } let renderConfiguration = { - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/tabs/tabs.ts b/packages/@headlessui-vue/src/components/tabs/tabs.ts index 7d940a8c60..98318d4a8c 100644 --- a/packages/@headlessui-vue/src/components/tabs/tabs.ts +++ b/packages/@headlessui-vue/src/components/tabs/tabs.ts @@ -180,10 +180,10 @@ export let TabList = defineComponent({ role: 'tablist', 'aria-orientation': api.orientation.value, } - let passThroughProps = props + let incomingProps = props return render({ - props: { ...passThroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot, attrs, slots, diff --git a/packages/@headlessui-vue/src/components/transitions/transition.ts b/packages/@headlessui-vue/src/components/transitions/transition.ts index dc967707c7..3bbe3bc11b 100644 --- a/packages/@headlessui-vue/src/components/transitions/transition.ts +++ b/packages/@headlessui-vue/src/components/transitions/transition.ts @@ -327,10 +327,10 @@ export let TransitionChild = defineComponent({ } = props let propsWeControl = { ref: container } - let passthroughProps = rest + let incomingProps = rest return render({ - props: { ...passthroughProps, ...propsWeControl }, + props: { ...incomingProps, ...propsWeControl }, slot: {}, slots, attrs, @@ -416,7 +416,7 @@ export let TransitionRoot = defineComponent({ provide(TransitionContext, transitionBag) return () => { - let passThroughProps = omit(props, ['show', 'appear', 'unmount']) + let incomingProps = omit(props, ['show', 'appear', 'unmount']) let sharedProps = { unmount: props.unmount } return render({ @@ -437,7 +437,7 @@ export let TransitionRoot = defineComponent({ onAfterLeave: () => emit('afterLeave'), ...attrs, ...sharedProps, - ...passThroughProps, + ...incomingProps, }, slots.default ), diff --git a/packages/@headlessui-vue/src/internal/portal-force-root.ts b/packages/@headlessui-vue/src/internal/portal-force-root.ts index 6b31030047..7857091ad9 100644 --- a/packages/@headlessui-vue/src/internal/portal-force-root.ts +++ b/packages/@headlessui-vue/src/internal/portal-force-root.ts @@ -24,8 +24,8 @@ export let ForcePortalRoot = defineComponent({ provide(ForcePortalRootContext, props.force) return () => { - let { force, ...passThroughProps } = props - return render({ props: passThroughProps, slot: {}, slots, attrs, name: 'ForcePortalRoot' }) + let { force, ...incomingProps } = props + return render({ props: incomingProps, slot: {}, slots, attrs, name: 'ForcePortalRoot' }) } }, }) diff --git a/packages/@headlessui-vue/src/utils/render.ts b/packages/@headlessui-vue/src/utils/render.ts index 8e424a755a..05221a73e1 100644 --- a/packages/@headlessui-vue/src/utils/render.ts +++ b/packages/@headlessui-vue/src/utils/render.ts @@ -81,12 +81,12 @@ function _render({ slots: Slots name: string }) { - let { as, ...passThroughProps } = omit(props, ['unmount', 'static']) + let { as, ...incomingProps } = omit(props, ['unmount', 'static']) let children = slots.default?.(slot) if (as === 'template') { - if (Object.keys(passThroughProps).length > 0 || Object.keys(attrs).length > 0) { + if (Object.keys(incomingProps).length > 0 || Object.keys(attrs).length > 0) { let [firstChild, ...other] = children ?? [] if (!isValidElement(firstChild) || other.length > 0) { @@ -96,7 +96,7 @@ function _render({ '', `The current component <${name} /> is rendering a "template".`, `However we need to passthrough the following props:`, - Object.keys(passThroughProps) + Object.keys(incomingProps) .concat(Object.keys(attrs)) .map((line) => ` - ${line}`) .join('\n'), @@ -112,7 +112,7 @@ function _render({ ) } - return cloneVNode(firstChild, passThroughProps as Record) + return cloneVNode(firstChild, incomingProps as Record) } if (Array.isArray(children) && children.length === 1) { @@ -122,7 +122,7 @@ function _render({ return children } - return h(as, passThroughProps, children) + return h(as, incomingProps, children) } export function compact>(object: T) { From 302f5e6c3ce871bb9ad334500863cdf163fb1a4b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 22 Mar 2022 13:27:11 +0100 Subject: [PATCH 2/7] 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. --- .../src/components/combobox/combobox.tsx | 32 +++++++++++------ .../components/description/description.tsx | 5 +-- .../src/components/dialog/dialog.tsx | 20 ++++++----- .../src/components/disclosure/disclosure.tsx | 20 +++++++---- .../src/components/focus-trap/focus-trap.tsx | 5 +-- .../src/components/label/label.tsx | 18 ++++++---- .../src/components/listbox/listbox.tsx | 28 ++++++++++----- .../src/components/menu/menu.tsx | 26 ++++++++------ .../src/components/popover/popover.tsx | 28 ++++++++++----- .../src/components/portal/portal.tsx | 14 +++++--- .../components/radio-group/radio-group.tsx | 10 +++--- .../src/components/switch/switch.tsx | 15 ++++++-- .../src/components/tabs/tabs.tsx | 34 +++++++++++++------ .../src/components/transitions/transition.tsx | 14 +++++--- .../src/internal/visually-hidden.tsx | 33 ++++++++++-------- .../src/utils/render.test.tsx | 15 +++++--- 16 files changed, 208 insertions(+), 109 deletions(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 4dcc5e3da6..b1b30d6276 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -330,7 +330,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< }, ref: Ref ) { - let { name, value, onChange, disabled = false, __demoMode = false, ...incomingProps } = props + let { name, value, onChange, disabled = false, __demoMode = false, ...propsTheyControl } = props let comboboxPropsRef = useRef({ value, @@ -481,9 +481,11 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< // Ensure that we update the inputRef if the value changes useIsoMorphicEffect(syncInputValue, [syncInputValue]) + let propsWeControl = ref === null ? {} : { ref } let renderConfiguration = { - props: ref === null ? incomingProps : { ...incomingProps, ref }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_COMBOBOX_TAG, name: 'Combobox', @@ -556,7 +558,7 @@ let Input = forwardRefWithAs(function Input< }, ref: Ref ) { - let { value, onChange, displayValue, ...incomingProps } = props + let { value, onChange, displayValue, ...propsTheyControl } = props let [state] = useComboboxContext('Combobox.Input') let data = useComboboxData() let actions = useComboboxActions() @@ -694,7 +696,8 @@ let Input = forwardRefWithAs(function Input< } return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_INPUT_TAG, name: 'Combobox.Input', @@ -806,7 +809,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }), [state] ) - let incomingProps = props + let propsTheyControl = props let propsWeControl = { ref: buttonRef, id, @@ -822,7 +825,8 @@ let Button = forwardRefWithAs(function Button ({ open: state.comboboxState === ComboboxStates.Open, disabled: state.disabled }), [state] ) + + let propsTheyControl = props let propsWeControl = { ref: labelRef, id, onClick: handleClick } + return render({ - props: { ...props, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_LABEL_TAG, name: 'Combobox.Label', @@ -890,7 +898,7 @@ let Options = forwardRefWithAs(function Options< }, ref: Ref ) { - let { hold = false, ...incomingProps } = props + let { hold = false, ...propsTheyControl } = props let [state] = useComboboxContext('Combobox.Options') let { optionsPropsRef } = state @@ -946,7 +954,8 @@ let Options = forwardRefWithAs(function Options< } return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OPTIONS_TAG, features: OptionsRenderFeatures, @@ -986,7 +995,7 @@ let Option = forwardRefWithAs(function Option< }, ref: Ref ) { - let { disabled = false, value, ...incomingProps } = props + let { disabled = false, value, ...propsTheyControl } = props let [state] = useComboboxContext('Combobox.Option') let data = useComboboxData() let actions = useComboboxActions() @@ -1092,7 +1101,8 @@ let Option = forwardRefWithAs(function Option< } return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OPTION_TAG, name: 'Combobox.Option', diff --git a/packages/@headlessui-react/src/components/description/description.tsx b/packages/@headlessui-react/src/components/description/description.tsx index a9124df094..147a98687f 100644 --- a/packages/@headlessui-react/src/components/description/description.tsx +++ b/packages/@headlessui-react/src/components/description/description.tsx @@ -98,11 +98,12 @@ export let Description = forwardRefWithAs(function Description< useIsoMorphicEffect(() => context.register(id), [id, context.register]) - let incomingProps = props + let propsTheyControl = props let propsWeControl = { ref: descriptionRef, ...context.props, id } return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot: context.slot || {}, defaultTag: DEFAULT_DESCRIPTION_TAG, name: context.name || 'Description', diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index c07a3d2069..ccc8ce57b1 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -119,7 +119,7 @@ let DialogRoot = forwardRefWithAs(function Dialog< }, ref: Ref ) { - let { open, onClose, initialFocus, __demoMode = false, ...rest } = props + let { open, onClose, initialFocus, __demoMode = false, ...propsTheyControl } = props let [nestedDialogCount, setNestedDialogCount] = useState(0) let usesOpenClosedState = useOpenClosed() @@ -303,7 +303,6 @@ let DialogRoot = forwardRefWithAs(function Dialog< event.stopPropagation() }, } - let incomingProps = rest return ( {render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_DIALOG_TAG, features: DialogRenderFeatures, @@ -379,16 +379,18 @@ let Overlay = forwardRefWithAs(function Overlay< () => ({ open: dialogState === DialogStates.Open }), [dialogState] ) + + let propsTheyControl = props let propsWeControl = { ref: overlayRef, id, 'aria-hidden': true, onClick: handleClick, } - let incomingProps = props return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OVERLAY_TAG, name: 'Dialog.Overlay', @@ -421,11 +423,13 @@ let Title = forwardRefWithAs(function Title ({ open: dialogState === DialogStates.Open }), [dialogState] ) - let propsWeControl = { id } - let incomingProps = props + + let propsTheyControl = props + let propsWeControl = { ref: titleRef, id } return render({ - props: { ref: titleRef, ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_TITLE_TAG, name: 'Dialog.Title', diff --git a/packages/@headlessui-react/src/components/disclosure/disclosure.tsx b/packages/@headlessui-react/src/components/disclosure/disclosure.tsx index 4f9c731037..6d8428cbfb 100644 --- a/packages/@headlessui-react/src/components/disclosure/disclosure.tsx +++ b/packages/@headlessui-react/src/components/disclosure/disclosure.tsx @@ -156,7 +156,7 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure< }, ref: Ref ) { - let { defaultOpen = false, ...incomingProps } = props + let { defaultOpen = false, ...propsTheyControl } = props let buttonId = `headlessui-disclosure-button-${useId()}` let panelId = `headlessui-disclosure-panel-${useId()}` let internalDisclosureRef = useRef(null) @@ -214,6 +214,10 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure< [disclosureState, close] ) + let propsWeControl = { + ref: disclosureRef, + } + return ( @@ -224,7 +228,8 @@ let DisclosureRoot = forwardRefWithAs(function Disclosure< })} > {render({ - props: { ref: disclosureRef, ...incomingProps }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_DISCLOSURE_TAG, name: 'Disclosure', @@ -320,7 +325,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.disclosureState === DisclosureStates.Open, close }), [state, close] ) + + let propsTheyControl = props let propsWeControl = { ref: panelRef, id: state.panelId, } - let incomingProps = props return ( {render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_PANEL_TAG, features: PanelRenderFeatures, diff --git a/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx b/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx index 64e95baeec..4a8b17df3a 100644 --- a/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx +++ b/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx @@ -23,7 +23,7 @@ export let FocusTrap = forwardRefWithAs(function FocusTrap< ) { let container = useRef(null) let focusTrapRef = useSyncRefs(container, ref) - let { initialFocus, ...incomingProps } = props + let { initialFocus, ...propsTheyControl } = props let ready = useServerHandoffComplete() useFocusTrap(container, ready ? FocusTrapFeatures.All : FocusTrapFeatures.None, { initialFocus }) @@ -33,7 +33,8 @@ export let FocusTrap = forwardRefWithAs(function FocusTrap< } return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, defaultTag: DEFAULT_FOCUS_TRAP_TAG, name: 'FocusTrap', }) diff --git a/packages/@headlessui-react/src/components/label/label.tsx b/packages/@headlessui-react/src/components/label/label.tsx index 1330c95ab5..edbc906506 100644 --- a/packages/@headlessui-react/src/components/label/label.tsx +++ b/packages/@headlessui-react/src/components/label/label.tsx @@ -88,7 +88,7 @@ export let Label = forwardRefWithAs(function Label< }, ref: Ref ) { - let { passive = false, ...incomingProps } = props + let { passive = false, ...propsTheyControl } = props let context = useLabelContext() let id = `headlessui-label-${useId()}` let labelRef = useSyncRefs(ref) @@ -97,13 +97,19 @@ export let Label = forwardRefWithAs(function Label< let propsWeControl = { ref: labelRef, ...context.props, id } - let allProps = { ...incomingProps, ...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 propsWeControl) { + delete (propsWeControl as any)['onClick'] + } + + if ('onClick' in propsTheyControl) { + delete (propsTheyControl as any)['onClick'] + } + } return render({ - props: allProps, + propsWeControl, + propsTheyControl, slot: context.slot || {}, defaultTag: DEFAULT_LABEL_TAG, name: context.name || 'Label', diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index 81092f4c70..6859e0a138 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -314,7 +314,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox< }, ref: Ref ) { - let { value, name, onChange, disabled = false, horizontal = false, ...incomingProps } = props + let { value, name, onChange, disabled = false, horizontal = false, ...propsTheyControl } = props const orientation = horizontal ? 'horizontal' : 'vertical' let listboxRef = useSyncRefs(ref) @@ -382,8 +382,11 @@ let ListboxRoot = forwardRefWithAs(function Listbox< [listboxState, disabled] ) + let propsWeControl = { ref: listboxRef } + let renderConfiguration = { - props: { ref: listboxRef, ...incomingProps }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_LISTBOX_TAG, name: 'Listbox', @@ -513,7 +516,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }), [state] ) - let incomingProps = props + let propsTheyControl = props let propsWeControl = { ref: buttonRef, id, @@ -529,7 +532,8 @@ let Button = forwardRefWithAs(function Button ({ open: state.listboxState === ListboxStates.Open, disabled: state.disabled }), [state] ) + let propsTheyControl = props let propsWeControl = { ref: labelRef, id, onClick: handleClick } + return render({ - props: { ...props, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_LABEL_TAG, name: 'Listbox.Label', @@ -702,6 +709,8 @@ let Options = forwardRefWithAs(function Options< () => ({ open: state.listboxState === ListboxStates.Open }), [state] ) + + let propsTheyControl = props let propsWeControl = { 'aria-activedescendant': state.activeOptionIndex === null ? undefined : state.options[state.activeOptionIndex]?.id, @@ -714,10 +723,10 @@ let Options = forwardRefWithAs(function Options< tabIndex: 0, ref: optionsRef, } - let incomingProps = props return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OPTIONS_TAG, features: OptionsRenderFeatures, @@ -758,7 +767,7 @@ let Option = forwardRefWithAs(function Option< }, ref: Ref ) { - let { disabled = false, value, ...incomingProps } = props + let { disabled = false, value, ...propsTheyControl } = props let [state, dispatch] = useListboxContext('Listbox.Option') let id = `headlessui-listbox-option-${useId()}` let active = @@ -859,7 +868,8 @@ let Option = forwardRefWithAs(function Option< } return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OPTION_TAG, name: 'Listbox.Option', diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index 10ea7cc481..7eba4bfa9c 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -255,6 +255,9 @@ let MenuRoot = forwardRefWithAs(function Menu {render({ - props: { ref: menuRef, ...props }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_MENU_TAG, name: 'Menu', @@ -355,7 +359,7 @@ let Button = forwardRefWithAs(function Button ({ open: state.menuState === MenuStates.Open }), [state] ) - let incomingProps = props + let propsTheyControl = props let propsWeControl = { ref: buttonRef, id, @@ -369,7 +373,8 @@ let Button = forwardRefWithAs(function Button ({ open: state.menuState === MenuStates.Open }), [state] ) + + let propsTheyControl = props let propsWeControl = { 'aria-activedescendant': state.activeItemIndex === null ? undefined : state.items[state.activeItemIndex]?.id, @@ -532,10 +539,10 @@ let Items = forwardRefWithAs(function Items( props: Props & { disabled?: boolean - onClick?: (event: { preventDefault: Function }) => void }, ref: Ref ) { - let { disabled = false, onClick, ...incomingProps } = props + let { disabled = false, ...propsTheyControl } = 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 state.buttonRef.current?.focus({ preventScroll: true })) - if (onClick) return onClick(event) }, - [dispatch, state.buttonRef, disabled, onClick] + [dispatch, state.buttonRef, disabled] ) let handleFocus = useCallback(() => { @@ -650,7 +655,8 @@ let Item = forwardRefWithAs(function Item @@ -271,7 +274,8 @@ let PopoverRoot = forwardRefWithAs(function Popover< })} > {render({ - props: { ref: popoverRef, ...props }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_POPOVER_TAG, name: 'Popover', @@ -485,7 +489,7 @@ let Button = forwardRefWithAs(function Button ({ open: popoverState === PopoverStates.Open }), [popoverState] ) + + let propsTheyControl = props let propsWeControl = { ref: overlayRef, id, 'aria-hidden': true, onClick: handleClick, } - let incomingProps = props return render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OVERLAY_TAG, features: OverlayRenderFeatures, @@ -591,7 +598,7 @@ let Panel = forwardRefWithAs(function Panel ) { - let { focus = false, ...incomingProps } = props + let { focus = false, ...propsTheyControl } = props let [state, dispatch] = usePopoverContext('Popover.Panel') let { close } = usePopoverAPIContext('Popover.Panel') @@ -730,7 +737,8 @@ let Panel = forwardRefWithAs(function Panel {render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_PANEL_TAG, features: PanelRenderFeatures, @@ -814,13 +822,15 @@ let Group = forwardRefWithAs(function Group(() => ({}), []) + + let propsTheyControl = props let propsWeControl = { ref: groupRef } - let incomingProps = props return ( {render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_GROUP_TAG, name: 'Popover.Group', diff --git a/packages/@headlessui-react/src/components/portal/portal.tsx b/packages/@headlessui-react/src/components/portal/portal.tsx index e3110390a2..6405f0305d 100644 --- a/packages/@headlessui-react/src/components/portal/portal.tsx +++ b/packages/@headlessui-react/src/components/portal/portal.tsx @@ -69,7 +69,7 @@ interface PortalRenderPropArg {} let PortalRoot = forwardRefWithAs(function Portal< TTag extends ElementType = typeof DEFAULT_PORTAL_TAG >(props: Props, ref: Ref) { - let incomingProps = props + let propsTheyControl = props let internalPortalRootRef = useRef(null) let portalRef = useSyncRefs( optionalRef((ref) => { @@ -105,11 +105,14 @@ let PortalRoot = forwardRefWithAs(function Portal< if (!ready) return null + let propsWeControl = { ref: portalRef } + return !target || !element ? null : createPortal( render({ - props: { ref: portalRef, ...incomingProps }, + propsWeControl, + propsTheyControl, defaultTag: DEFAULT_PORTAL_TAG, name: 'Portal', }), @@ -130,13 +133,16 @@ let Group = forwardRefWithAs(function Group ) { - let { target, ...incomingProps } = props + let { target, ...propsTheyControl } = props let groupRef = useSyncRefs(ref) + let propsWeControl = { ref: groupRef } + return ( {render({ - props: { ref: groupRef, ...incomingProps }, + propsWeControl, + propsTheyControl, defaultTag: DEFAULT_GROUP_TAG, name: 'Popover.Group', })} diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx index e0b4677b05..35521db0e4 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -121,7 +121,7 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup< }, ref: Ref ) { - let { value, name, onChange, disabled = false, ...incomingProps } = props + let { value, name, onChange, disabled = false, ...propsTheyControl } = props let [{ options }, dispatch] = useReducer(stateReducer, { options: [], } as StateDefinition) @@ -262,7 +262,8 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup< } let renderConfiguration = { - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, 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, ...incomingProps } = props + let { value, disabled = false, ...propsTheyControl } = props let propsRef = useRef({ value, disabled }) useIsoMorphicEffect(() => { @@ -406,7 +407,8 @@ let Option = forwardRefWithAs(function Option< {render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_OPTION_TAG, name: 'RadioGroup.Option', diff --git a/packages/@headlessui-react/src/components/switch/switch.tsx b/packages/@headlessui-react/src/components/switch/switch.tsx index e774d9e817..7ea5c639ca 100644 --- a/packages/@headlessui-react/src/components/switch/switch.tsx +++ b/packages/@headlessui-react/src/components/switch/switch.tsx @@ -49,6 +49,9 @@ function Group(props: Props [switchElement, setSwitchElement, labelledby, describedby] ) + let propsWeControl = {} + let propsTheyControl = props + return ( (props: Props }} > - {render({ props, defaultTag: DEFAULT_GROUP_TAG, name: 'Switch.Group' })} + {render({ + propsWeControl, + propsTheyControl, + defaultTag: DEFAULT_GROUP_TAG, + name: 'Switch.Group', + })} @@ -101,7 +109,7 @@ let SwitchRoot = forwardRefWithAs(function Switch< }, ref: Ref ) { - let { checked, onChange, name, value, ...incomingProps } = props + let { checked, onChange, name, value, ...propsTheyControl } = props let id = `headlessui-switch-${useId()}` let groupContext = useContext(GroupContext) let internalSwitchRef = useRef(null) @@ -151,7 +159,8 @@ let SwitchRoot = forwardRefWithAs(function Switch< } let renderConfiguration = { - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_SWITCH_TAG, name: 'Switch', diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index c90ab0b4a0..127c4e73d2 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -146,7 +146,7 @@ let Tabs = forwardRefWithAs(function Tabs @@ -244,7 +248,8 @@ let Tabs = forwardRefWithAs(function Tabs {render({ - props: { ref: tabsRef, ...incomingProps }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_TABS_TAG, name: 'Tabs', @@ -270,15 +275,17 @@ let List = forwardRefWithAs(function List ({ selected }), [selected]) + + let propsTheyControl = props let propsWeControl = { ref: tabRef, onKeyDown: handleKeyDown, @@ -390,10 +399,10 @@ let TabRoot = forwardRefWithAs(function Tab ({ selectedIndex }), [selectedIndex]) + let propsTheyControl = props + let propsWeControl = { ref: panelsRef } + return render({ - props: { ref: panelsRef, ...props }, + propsWeControl, + propsTheyControl, slot, defaultTag: DEFAULT_PANELS_TAG, name: 'Tabs.Panels', @@ -462,6 +475,8 @@ let Panel = forwardRefWithAs(function Panel ({ selected }), [selected]) + + let propsTheyControl = props let propsWeControl = { ref: panelRef, id, @@ -470,10 +485,9 @@ let Panel = forwardRefWithAs(function Panel @@ -360,7 +360,8 @@ let TransitionChild = forwardRefWithAs(function TransitionChild< })} > {render({ - props: { ...incomingProps, ...propsWeControl }, + propsWeControl, + propsTheyControl, 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 & { show?: boolean; appear?: boolean }, ref: Ref) { // @ts-expect-error - let { show, appear = false, unmount, ...incomingProps } = props as typeof props + let { show, appear = false, unmount, ...propsTheyControl } = props as typeof props let transitionRef = useSyncRefs(ref) let usesOpenClosedState = useOpenClosed() @@ -417,11 +418,14 @@ let TransitionRoot = forwardRefWithAs(function Transition< {render({ - props: { + propsWeControl: { ...sharedProps, as: Fragment, - children: , + children: ( + + ), }, + propsTheyControl: {}, defaultTag: Fragment, features: TransitionChildRenderFeatures, visible: state === TreeStates.Visible, diff --git a/packages/@headlessui-react/src/internal/visually-hidden.tsx b/packages/@headlessui-react/src/internal/visually-hidden.tsx index 9aceeff5a9..5536e7a3bd 100644 --- a/packages/@headlessui-react/src/internal/visually-hidden.tsx +++ b/packages/@headlessui-react/src/internal/visually-hidden.tsx @@ -7,22 +7,25 @@ let DEFAULT_VISUALLY_HIDDEN_TAG = 'div' as const export let VisuallyHidden = forwardRefWithAs(function VisuallyHidden< TTag extends ElementType = typeof DEFAULT_VISUALLY_HIDDEN_TAG >(props: Props, ref: Ref) { - return render({ - props: { - ...props, - ref, - style: { - position: 'absolute', - width: 1, - height: 1, - padding: 0, - margin: -1, - overflow: 'hidden', - clip: 'rect(0, 0, 0, 0)', - whiteSpace: 'nowrap', - borderWidth: '0', - }, + let propsTheyControl = props + let propsWeControl = { + ref, + style: { + position: 'absolute', + width: 1, + height: 1, + padding: 0, + margin: -1, + overflow: 'hidden', + clip: 'rect(0, 0, 0, 0)', + whiteSpace: 'nowrap', + borderWidth: '0', }, + } + + return render({ + propsWeControl, + propsTheyControl, slot: {}, defaultTag: DEFAULT_VISUALLY_HIDDEN_TAG, name: 'VisuallyHidden', diff --git a/packages/@headlessui-react/src/utils/render.test.tsx b/packages/@headlessui-react/src/utils/render.test.tsx index 9c743c4520..eac6c9045a 100644 --- a/packages/@headlessui-react/src/utils/render.test.tsx +++ b/packages/@headlessui-react/src/utils/render.test.tsx @@ -19,7 +19,8 @@ describe('Default functionality', () => { return (
{render({ - props, + propsWeControl: {}, + propsTheyControl: props, slot, defaultTag: 'div', name: 'Dummy', @@ -80,7 +81,8 @@ describe('Default functionality', () => { return (
{render({ - props: { ...props, ref }, + propsWeControl: { ref }, + propsTheyControl: props, slot, defaultTag: 'div', name: 'OtherDummy', @@ -323,7 +325,8 @@ describe('Features.Static', () => { return (
{render({ - props: rest, + propsWeControl: {}, + propsTheyControl: rest, slot, defaultTag: 'div', features: EnabledFeatures, @@ -428,7 +431,8 @@ describe('Features.RenderStrategy', () => { return (
{render({ - props: rest, + propsWeControl: {}, + propsTheyControl: rest, slot, defaultTag: 'div', features: EnabledFeatures, @@ -455,7 +459,8 @@ describe('Features.Static | Features.RenderStrategy', () => { return (
{render({ - props: rest, + propsWeControl: {}, + propsTheyControl: rest, slot, defaultTag: 'div', features: EnabledFeatures, From b86d2d22f25dcacb842ca3578dd12cc4bf15c297 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 22 Mar 2022 13:28:28 +0100 Subject: [PATCH 3/7] update `render` API to accept `propsWeControl` and `propsTheyControl` --- packages/@headlessui-react/src/utils/render.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index c8addf24cf..66d0e68f1b 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -47,20 +47,24 @@ export type PropsForFeatures = XOR< > export function render({ - props, + propsWeControl, + propsTheyControl, slot, defaultTag, features, visible = true, name, }: { - props: Expand & PropsForFeatures> + propsWeControl: Expand & PropsForFeatures> + propsTheyControl: Expand> slot?: TSlot defaultTag: ElementType features?: TFeature visible?: boolean name: string }) { + let props = mergeProps(propsTheyControl, propsWeControl) + // Visible always render if (visible) return _render(props, slot, defaultTag, name) @@ -106,7 +110,7 @@ function _render( as: Component = tag, children, refName = 'ref', - ...incomingProps + ...rest } = omit(props, ['unmount', 'static']) // This allows us to use `` @@ -117,12 +121,12 @@ function _render( | ReactElement[] // Allow for className to be a function with the slot as the contents - if (incomingProps.className && typeof incomingProps.className === 'function') { - ;(incomingProps as any).className = incomingProps.className(slot) + if (rest.className && typeof rest.className === 'function') { + ;(rest as any).className = rest.className(slot) } if (Component === Fragment) { - if (Object.keys(compact(incomingProps)).length > 0) { + if (Object.keys(compact(rest)).length > 0) { if ( !isValidElement(resolvedChildren) || (Array.isArray(resolvedChildren) && resolvedChildren.length > 1) @@ -133,7 +137,7 @@ function _render( '', `The current component <${name} /> is rendering a "Fragment".`, `However we need to passthrough the following props:`, - Object.keys(incomingProps) + Object.keys(rest) .map((line) => ` - ${line}`) .join('\n'), '', From a567a100c50770cb04320f977aabfd088fd87c47 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 22 Mar 2022 13:28:59 +0100 Subject: [PATCH 4/7] 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`, ... --- .../@headlessui-react/src/utils/render.ts | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index 66d0e68f1b..5168fa71b9 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -157,9 +157,7 @@ function _render( Object.assign( {}, // Filter out undefined values so that they don't override the existing values - mergeEventFunctions(compact(omit(incomingProps, ['ref'])), resolvedChildren.props, [ - 'onClick', - ]), + mergeProps(resolvedChildren.props, compact(omit(rest, ['ref']))), refRelatedProps ) ) @@ -168,46 +166,63 @@ function _render( return createElement( Component, - Object.assign({}, omit(incomingProps, ['ref']), Component !== Fragment && refRelatedProps), + Object.assign({}, omit(rest, ['ref']), Component !== Fragment && refRelatedProps), resolvedChildren ) } -/** - * We can use this function for the following useCase: - * - *