Properly merge incoming props with own props (#1651)

* sort props in error message

This will make the error message consistent regardless which props (and
in what order) they are applied.

* WIP

* `click()` on a disabled element should no-op

* incomingProps was already merged

* cleanup tests a bit and make it consistent with the React tests

* cleanup unused code

* update changelog
This commit is contained in:
Robin Malfait
2022-07-07 17:01:45 +02:00
committed by GitHub
parent 6253aa52b3
commit 0260afa2df
23 changed files with 244 additions and 114 deletions
@@ -234,6 +234,7 @@ export async function click(
) {
try {
if (element === null) return expect(element).not.toBe(null)
if (element.disabled) return
let options = { button }
+1
View File
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix getting Vue dom elements ([#1610](https://github.com/tailwindlabs/headlessui/pull/1610))
- Ensure `CMD`+`Backspace` works in nullable mode for `Combobox` component ([#1617](https://github.com/tailwindlabs/headlessui/pull/1617))
- Properly merge incoming props with own props ([#1651](https://github.com/tailwindlabs/headlessui/pull/1651))
## [1.6.5] - 2022-06-20
@@ -4832,7 +4832,7 @@ describe('Mouse interactions', () => {
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="alice">alice</ComboboxOption>
<ComboboxOption disabled value="bob"> bob </ComboboxOption>
<ComboboxOption disabled value="bob">bob</ComboboxOption>
<ComboboxOption value="charlie">charlie</ComboboxOption>
</ComboboxOptions>
</Combobox>
@@ -4849,7 +4849,7 @@ describe('Mouse interactions', () => {
// We should not be able to focus the first option
await focus(options[1])
assertNoActiveComboboxOption()
assertNotActiveComboboxOption(options[1])
})
)
@@ -439,7 +439,7 @@ export let Combobox = defineComponent({
)
return () => {
let { name, modelValue, disabled, ...incomingProps } = props
let { name, modelValue, disabled, ...theirProps } = props
let slot = {
open: comboboxState.value === ComboboxStates.Open,
disabled,
@@ -466,10 +466,11 @@ export let Combobox = defineComponent({
)
: []),
render({
props: {
theirProps: {
...attrs,
...omit(incomingProps, ['nullable', 'multiple', 'onUpdate:modelValue', 'by']),
...omit(theirProps, ['nullable', 'multiple', 'onUpdate:modelValue', 'by']),
},
ourProps: {},
slot,
slots,
attrs,
@@ -500,9 +501,11 @@ export let ComboboxLabel = defineComponent({
}
let ourProps = { id, ref: api.labelRef, onClick: handleClick }
let theirProps = props
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -601,9 +604,11 @@ export let ComboboxButton = defineComponent({
onKeydown: handleKeydown,
onClick: handleClick,
}
let theirProps = props
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -755,10 +760,11 @@ export let ComboboxInput = defineComponent({
tabIndex: 0,
ref: api.inputRef,
}
let incomingProps = omit(props, ['displayValue'])
let theirProps = omit(props, ['displayValue'])
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -827,10 +833,11 @@ export let ComboboxOptions = defineComponent({
ref: api.optionsRef,
role: 'listbox',
}
let incomingProps = omit(props, ['hold'])
let theirProps = omit(props, ['hold'])
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -937,8 +944,11 @@ export let ComboboxOption = defineComponent({
onMouseleave: handleLeave,
}
let theirProps = props
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -78,7 +78,7 @@ export let Description = defineComponent({
return () => {
let { name = 'Description', slot = ref({}), props = {} } = context
let incomingProps = myProps
let theirProps = myProps
let ourProps = {
...Object.entries(props).reduce(
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
@@ -88,7 +88,8 @@ export let Description = defineComponent({
}
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot: slot.value,
attrs,
slots,
@@ -280,7 +280,7 @@ export let Dialog = defineComponent({
'aria-labelledby': titleId.value,
'aria-describedby': describedby.value,
}
let { open: _, initialFocus, ...incomingProps } = props
let { open: _, initialFocus, ...theirProps } = props
let slot = { open: dialogState.value === DialogStates.Open }
@@ -302,7 +302,8 @@ export let Dialog = defineComponent({
},
() =>
render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -344,10 +345,11 @@ export let DialogOverlay = defineComponent({
'aria-hidden': true,
onClick: handleClick,
}
let incomingProps = props
let theirProps = props
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot: { open: api.dialogState.value === DialogStates.Open },
attrs,
slots,
@@ -381,7 +383,7 @@ export let DialogBackdrop = defineComponent({
})
return () => {
let incomingProps = props
let theirProps = props
let ourProps = {
id,
ref: internalBackdropRef,
@@ -391,7 +393,8 @@ export let DialogBackdrop = defineComponent({
return h(ForcePortalRoot, { force: true }, () =>
h(Portal, () =>
render({
props: { ...attrs, ...incomingProps, ...ourProps },
ourProps,
theirProps: { ...attrs, ...theirProps },
slot: { open: api.dialogState.value === DialogStates.Open },
attrs,
slots,
@@ -426,10 +429,11 @@ export let DialogPanel = defineComponent({
ref: api.panelRef,
onClick: handleClick,
}
let incomingProps = props
let theirProps = props
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot: { open: api.dialogState.value === DialogStates.Open },
attrs,
slots,
@@ -457,10 +461,11 @@ export let DialogTitle = defineComponent({
return () => {
let ourProps = { id }
let incomingProps = props
let theirProps = props
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot: { open: api.dialogState.value === DialogStates.Open },
attrs,
slots,
@@ -118,9 +118,16 @@ export let Disclosure = defineComponent({
)
return () => {
let { defaultOpen: _, ...incomingProps } = props
let { defaultOpen: _, ...theirProps } = props
let slot = { open: disclosureState.value === DisclosureStates.Open, close: api.close }
return render({ props: incomingProps, slot, slots, attrs, name: 'Disclosure' })
return render({
theirProps,
ourProps: {},
slot,
slots,
attrs,
name: 'Disclosure',
})
}
},
})
@@ -223,7 +230,8 @@ export let DisclosureButton = defineComponent({
}
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -263,7 +271,8 @@ export let DisclosurePanel = defineComponent({
let ourProps = { id: api.panelId, ref: api.panel }
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -103,7 +103,7 @@ export let FocusTrap = Object.assign(
return () => {
let slot = {}
let ourProps = { 'data-hi': 'container', ref: container }
let { features, initialFocus, containers: _containers, ...incomingProps } = props
let { features, initialFocus, containers: _containers, ...theirProps } = props
return h(Fragment, [
Boolean(features & Features.TabLock) &&
@@ -114,7 +114,8 @@ export let FocusTrap = Object.assign(
features: HiddenFeatures.Focusable,
}),
render({
props: { ...attrs, ...incomingProps, ...ourProps },
ourProps,
theirProps: { ...attrs, ...theirProps },
slot,
attrs,
slots,
@@ -77,7 +77,7 @@ export let Label = defineComponent({
return () => {
let { name = 'Label', slot = {}, props = {} } = context
let { passive, ...incomingProps } = myProps
let { passive, ...theirProps } = myProps
let ourProps = {
...Object.entries(props).reduce(
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
@@ -85,14 +85,19 @@ export let Label = defineComponent({
),
id,
}
let allProps = { ...incomingProps, ...ourProps }
// @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) {
// @ts-expect-error props are dynamic via context, some components will provide an onClick
// then we can delete it.
delete ourProps['onClick']
// @ts-expect-error props are dynamic via context, some components will provide an onClick
// then we can delete it.
delete theirProps['onClick']
}
return render({
props: allProps,
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -323,7 +323,7 @@ export let Listbox = defineComponent({
)
return () => {
let { name, modelValue, disabled, ...incomingProps } = props
let { name, modelValue, disabled, ...theirProps } = props
let slot = { open: listboxState.value === ListboxStates.Open, disabled }
@@ -346,9 +346,10 @@ export let Listbox = defineComponent({
)
: []),
render({
props: {
ourProps: {},
theirProps: {
...attrs,
...omit(incomingProps, ['onUpdate:modelValue', 'horizontal', 'multiple', 'by']),
...omit(theirProps, ['onUpdate:modelValue', 'horizontal', 'multiple', 'by']),
},
slot,
slots,
@@ -381,7 +382,8 @@ export let ListboxLabel = defineComponent({
let ourProps = { id, ref: api.labelRef, onClick: handleClick }
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -480,7 +482,8 @@ export let ListboxButton = defineComponent({
}
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -604,10 +607,11 @@ export let ListboxOptions = defineComponent({
tabIndex: 0,
ref: api.optionsRef,
}
let incomingProps = props
let theirProps = props
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -753,7 +757,8 @@ export let ListboxOption = defineComponent({
}
return render({
props: { ...omit(props, ['value', 'disabled']), ...ourProps },
ourProps,
theirProps: omit(props, ['value', 'disabled']),
slot,
attrs,
slots,
@@ -317,16 +317,16 @@ describe('Rendering', () => {
'',
'The current component <MenuButton /> is rendering a "template".',
'However we need to passthrough the following props:',
' - disabled',
' - ref',
' - id',
' - type',
' - aria-haspopup',
' - aria-controls',
' - aria-expanded',
' - aria-haspopup',
' - disabled',
' - id',
' - onClick',
' - onKeydown',
' - onKeyup',
' - onClick',
' - ref',
' - type',
'',
'You can apply a few solutions:',
' - Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
@@ -518,9 +518,9 @@ describe('Rendering', () => {
' - id',
' - onKeydown',
' - onKeyup',
' - ref',
' - role',
' - tabIndex',
' - ref',
'',
'You can apply a few solutions:',
' - Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
@@ -680,18 +680,18 @@ describe('Rendering', () => {
'',
'The current component <MenuItem /> is rendering a "template".',
'However we need to passthrough the following props:',
' - aria-disabled',
' - disabled',
' - id',
' - onClick',
' - onFocus',
' - onMouseleave',
' - onMousemove',
' - onPointerleave',
' - onPointermove',
' - ref',
' - role',
' - tabIndex',
' - aria-disabled',
' - onClick',
' - onFocus',
' - onPointermove',
' - onMousemove',
' - onPointerleave',
' - onMouseleave',
'',
'You can apply a few solutions:',
' - Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
@@ -3452,11 +3452,11 @@ describe('Mouse interactions', () => {
<Menu>
<MenuButton>Trigger</MenuButton>
<MenuItems>
<MenuItem as="a" @click="clickHandler">alice</MenuItem>
<MenuItem as="a" @click="clickHandler" disabled>
<MenuItem as="button" @click="clickHandler">alice</MenuItem>
<MenuItem as="button" @click="clickHandler" disabled>
bob
</MenuItem>
<MenuItem>
<MenuItem disabled>
<button @click="clickHandler">charlie</button>
</MenuItem>
</MenuItems>
@@ -3472,13 +3472,11 @@ describe('Mouse interactions', () => {
let items = getMenuItems()
await focus(items[0])
await focus(items[1])
await press(Keys.Enter)
await click(items[1])
expect(clickHandler).not.toHaveBeenCalled()
// Activate the last item
await focus(items[2])
await press(Keys.Enter)
await click(getMenuItems()[2])
expect(clickHandler).not.toHaveBeenCalled()
})
})
@@ -228,7 +228,7 @@ export let Menu = defineComponent({
return () => {
let slot = { open: menuState.value === MenuStates.Open }
return render({ props, slot, slots, attrs, name: 'Menu' })
return render({ ourProps: {}, theirProps: props, slot, slots, attrs, name: 'Menu' })
}
},
})
@@ -314,9 +314,11 @@ export let MenuButton = defineComponent({
onKeyup: handleKeyUp,
onClick: handleClick,
}
let theirProps = props
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -458,10 +460,11 @@ export let MenuItems = defineComponent({
ref: api.itemsRef,
}
let incomingProps = props
let theirProps = props
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -551,9 +554,11 @@ export let MenuItem = defineComponent({
onPointerleave: handleLeave,
onMouseleave: handleLeave,
}
let theirProps = props
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -227,10 +227,8 @@ export let Popover = defineComponent({
return () => {
let slot = { open: popoverState.value === PopoverStates.Open, close: api.close }
return render({
props: {
...props,
ref: internalPopoverRef,
},
theirProps: props,
ourProps: { ref: internalPopoverRef },
slot,
slots,
attrs,
@@ -390,7 +388,8 @@ export let PopoverButton = defineComponent({
return h(Fragment, [
render({
props: { ...attrs, ...props, ...ourProps },
ourProps,
theirProps: { ...attrs, ...props },
slot,
attrs: attrs,
slots: slots,
@@ -446,7 +445,8 @@ export let PopoverOverlay = defineComponent({
}
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -630,7 +630,8 @@ export let PopoverPanel = defineComponent({
onFocus: handleBeforeFocus,
}),
render({
props: { ...attrs, ...props, ...ourProps },
ourProps,
theirProps: { ...attrs, ...props },
slot,
attrs,
slots,
@@ -712,7 +713,8 @@ export let PopoverGroup = defineComponent({
let ourProps = { ref: groupRef }
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot: {},
attrs,
slots,
@@ -88,7 +88,8 @@ export let Portal = defineComponent({
Teleport,
{ to: myTarget.value },
render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot: {},
attrs,
slots,
@@ -121,9 +122,16 @@ export let PortalGroup = defineComponent({
provide(PortalGroupContext, api)
return () => {
let { target: _, ...incomingProps } = props
let { target: _, ...theirProps } = props
return render({ props: incomingProps, slot: {}, attrs, slots, name: 'PortalGroup' })
return render({
theirProps,
ourProps: {},
slot: {},
attrs,
slots,
name: 'PortalGroup',
})
}
},
})
@@ -211,7 +211,7 @@ export let RadioGroup = defineComponent({
let id = `headlessui-radiogroup-${useId()}`
return () => {
let { modelValue, disabled, name, ...incomingProps } = props
let { modelValue, disabled, name, ...theirProps } = props
let ourProps = {
ref: radioGroupRef,
@@ -241,7 +241,8 @@ export let RadioGroup = defineComponent({
)
: []),
render({
props: { ...attrs, ...incomingProps, ...ourProps },
ourProps,
theirProps: { ...attrs, ...theirProps },
slot: {},
attrs,
slots,
@@ -307,7 +308,7 @@ export let RadioGroupOption = defineComponent({
}
return () => {
let incomingProps = omit(props, ['value', 'disabled'])
let theirProps = omit(props, ['value', 'disabled'])
let slot = {
checked: checked.value,
@@ -330,7 +331,8 @@ export let RadioGroupOption = defineComponent({
}
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -55,7 +55,8 @@ export let SwitchGroup = defineComponent({
provide(GroupContext, api)
return () => render({ props, slot: {}, slots, attrs, name: 'SwitchGroup' })
return () =>
render({ theirProps: props, ourProps: {}, slot: {}, slots, attrs, name: 'SwitchGroup' })
},
})
@@ -108,7 +109,7 @@ export let Switch = defineComponent({
}
return () => {
let { name, value, modelValue, ...incomingProps } = props
let { name, value, modelValue, ...theirProps } = props
let slot = { checked: modelValue }
let ourProps = {
id,
@@ -141,7 +142,8 @@ export let Switch = defineComponent({
)
: null,
render({
props: { ...attrs, ...incomingProps, ...ourProps },
ourProps,
theirProps: { ...attrs, ...theirProps },
slot,
attrs,
slots,
@@ -154,10 +154,11 @@ export let TabGroup = defineComponent({
},
}),
render({
props: {
theirProps: {
...attrs,
...omit(props, ['selectedIndex', 'defaultIndex', 'manual', 'vertical', 'onChange']),
},
ourProps: {},
slot,
slots,
attrs,
@@ -185,10 +186,11 @@ export let TabList = defineComponent({
role: 'tablist',
'aria-orientation': api.orientation.value,
}
let incomingProps = props
let theirProps = props
return render({
props: { ...incomingProps, ...ourProps },
ourProps,
theirProps,
slot,
attrs,
slots,
@@ -307,7 +309,8 @@ export let Tab = defineComponent({
}
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -331,7 +334,8 @@ export let TabPanels = defineComponent({
let slot = { selectedIndex: api.selectedIndex.value }
return render({
props,
theirProps: props,
ourProps: {},
slot,
attrs,
slots,
@@ -373,7 +377,8 @@ export let TabPanel = defineComponent({
}
return render({
props: { ...props, ...ourProps },
ourProps,
theirProps: props,
slot,
attrs,
slots,
@@ -327,10 +327,11 @@ export let TransitionChild = defineComponent({
} = props
let ourProps = { ref: container }
let incomingProps = rest
let theirProps = rest
return render({
props: { ...incomingProps, ...ourProps },
theirProps,
ourProps,
slot: {},
slots,
attrs,
@@ -416,7 +417,7 @@ export let TransitionRoot = defineComponent({
provide(TransitionContext, transitionBag)
return () => {
let incomingProps = omit(props, [
let theirProps = omit(props, [
'show',
'appear',
'unmount',
@@ -428,10 +429,11 @@ export let TransitionRoot = defineComponent({
let sharedProps = { unmount: props.unmount }
return render({
props: {
ourProps: {
...sharedProps,
as: 'template',
},
theirProps: {},
slot: {},
slots: {
...slots,
@@ -445,7 +447,7 @@ export let TransitionRoot = defineComponent({
onAfterLeave: () => emit('afterLeave'),
...attrs,
...sharedProps,
...incomingProps,
...theirProps,
},
slots.default
),
@@ -39,7 +39,8 @@ export let Hidden = defineComponent({
}
return render({
props: { ...theirProps, ...ourProps },
ourProps,
theirProps,
slot: {},
attrs,
slots,
@@ -24,8 +24,15 @@ export let ForcePortalRoot = defineComponent({
provide(ForcePortalRootContext, props.force)
return () => {
let { force, ...incomingProps } = props
return render({ props: incomingProps, slot: {}, slots, attrs, name: 'ForcePortalRoot' })
let { force, ...theirProps } = props
return render({
theirProps,
ourProps: {},
slot: {},
slots,
attrs,
name: 'ForcePortalRoot',
})
}
},
})
@@ -232,6 +232,7 @@ export async function click(
) {
try {
if (element === null) return expect(element).not.toBe(null)
if (element.disabled) return
let options = { button }
@@ -9,7 +9,7 @@ let Dummy = defineComponent({
as: { type: [Object, String], default: 'div' },
},
setup(props, { attrs, slots }) {
return () => render({ props, slots, attrs, slot: {}, name: 'Dummy' })
return () => render({ theirProps: props, ourProps: {}, slots, attrs, slot: {}, name: 'Dummy' })
},
})
@@ -60,7 +60,8 @@ describe('Validation', () => {
PassThrough(props, context) {
props.as = props.as ?? 'template'
return render({
props,
theirProps: props,
ourProps: {},
attrs: context.attrs,
slots: context.slots,
slot: {},
+68 -10
View File
@@ -29,9 +29,12 @@ export enum RenderStrategy {
export function render({
visible = true,
features = Features.None,
ourProps,
theirProps,
...main
}: {
props: Record<string, any>
ourProps: Record<string, any>
theirProps: Record<string, any>
slot: Record<string, any>
attrs: Record<string, any>
slots: Slots
@@ -40,16 +43,19 @@ export function render({
features?: Features
visible?: boolean
}) {
let props = mergeProps(theirProps, ourProps)
let mainWithProps = Object.assign(main, { props })
// Visible always render
if (visible) return _render(main)
if (visible) return _render(mainWithProps)
if (features & Features.Static) {
// When the `static` prop is passed as `true`, then the user is in control, thus we don't care about anything else
if (main.props.static) return _render(main)
if (props.static) return _render(mainWithProps)
}
if (features & Features.RenderStrategy) {
let strategy = main.props.unmount ?? true ? RenderStrategy.Unmount : RenderStrategy.Hidden
let strategy = props.unmount ?? true ? RenderStrategy.Unmount : RenderStrategy.Hidden
return match(strategy, {
[RenderStrategy.Unmount]() {
@@ -58,14 +64,14 @@ export function render({
[RenderStrategy.Hidden]() {
return _render({
...main,
props: { ...main.props, hidden: true, style: { display: 'none' } },
props: { ...props, hidden: true, style: { display: 'none' } },
})
},
})
}
// No features enabled, just render
return _render(main)
return _render(mainWithProps)
}
function _render({
@@ -116,6 +122,7 @@ function _render({
`However we need to passthrough the following props:`,
Object.keys(incomingProps)
.concat(Object.keys(attrs))
.sort((a, z) => a.localeCompare(z))
.map((line) => ` - ${line}`)
.join('\n'),
'',
@@ -130,10 +137,7 @@ function _render({
)
}
return cloneVNode(
firstChild,
Object.assign({}, incomingProps as Record<string, any>, dataAttributes)
)
return cloneVNode(firstChild, Object.assign({}, incomingProps, dataAttributes))
}
if (Array.isArray(children) && children.length === 1) {
@@ -173,6 +177,60 @@ function flattenFragments(children: VNode[]): VNode[] {
})
}
function mergeProps(...listOfProps: Record<any, any>[]) {
if (listOfProps.length === 0) return {}
if (listOfProps.length === 1) return listOfProps[0]
let target: Record<any, any> = {}
let eventHandlers: Record<
string,
((event: { defaultPrevented: boolean }, ...args: any[]) => void | undefined)[]
> = {}
for (let props of listOfProps) {
for (let prop in props) {
// Collect event handlers
if (prop.startsWith('on') && typeof props[prop] === 'function') {
eventHandlers[prop] ??= []
eventHandlers[prop].push(props[prop])
} else {
// Override incoming prop
target[prop] = props[prop]
}
}
}
// Do not attach any event handlers when there is a `disabled` or `aria-disabled` prop set.
if (target.disabled || target['aria-disabled']) {
return Object.assign(
target,
// Set all event listeners that we collected to `undefined`. This is
// important because of the `cloneElement` from above, which merges the
// existing and new props, they don't just override therefore we have to
// explicitly nullify them.
Object.fromEntries(Object.keys(eventHandlers).map((eventName) => [eventName, undefined]))
)
}
// Merge event handlers
for (let eventName in eventHandlers) {
Object.assign(target, {
[eventName](event: { defaultPrevented: boolean }, ...args: any[]) {
let handlers = eventHandlers[eventName]
for (let handler of handlers) {
if (event?.defaultPrevented) return
handler(event, ...args)
}
},
})
}
return target
}
export function compact<T extends Record<any, any>>(object: T) {
let clone = Object.assign({}, object)
for (let key in clone) {