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:
@@ -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 }
|
||||
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user