diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 655bcda..34a3eac 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -172,7 +172,7 @@ describe('Rendering', () => { }) ) - describe.skip('Equality', () => { + describe('Equality', () => { let options = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 2514634..294319b 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -311,10 +311,11 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< props: Props< TTag, ComboboxRenderPropArg, - 'value' | 'onChange' | 'disabled' | 'name' | 'nullable' | 'multiple' + 'value' | 'onChange' | 'disabled' | 'name' | 'nullable' | 'multiple' | 'by' > & { value: TType onChange(value: TType): void + by?: (keyof TType & string) | ((a: TType, z: TType) => boolean) disabled?: boolean __demoMode?: boolean name?: string @@ -327,6 +328,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< name, value, onChange: theirOnChange, + by = (a, z) => a === z, disabled = false, __demoMode = false, nullable = false, @@ -352,7 +354,14 @@ let ComboboxRoot = forwardRefWithAs(function Combobox< let buttonRef = useRef<_Data['buttonRef']['current']>(null) let optionsRef = useRef<_Data['optionsRef']['current']>(null) - let compare = useEvent((a, z) => a === z) + let compare = useEvent( + typeof by === 'string' + ? (a: TType, z: TType) => { + let property = by as unknown as keyof TType + return a[property] === z[property] + } + : by + ) let isSelected: (value: TType) => boolean = useCallback( (compareValue) => diff --git a/packages/@headlessui-react/src/components/listbox/listbox.test.tsx b/packages/@headlessui-react/src/components/listbox/listbox.test.tsx index 0c9c487..8fe7eb1 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.test.tsx @@ -163,7 +163,7 @@ describe('Rendering', () => { }) ) - describe.skip('Equality', () => { + describe('Equality', () => { let options = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index 995d6e1..1c1c647 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -311,10 +311,11 @@ let ListboxRoot = forwardRefWithAs(function Listbox< props: Props< TTag, ListboxRenderPropArg, - 'value' | 'onChange' | 'disabled' | 'horizontal' | 'name' | 'multiple' + 'value' | 'onChange' | 'disabled' | 'horizontal' | 'name' | 'multiple' | 'by' > & { value: TType onChange(value: TType): void + by?: (keyof TType & string) | ((a: TType, z: TType) => boolean) disabled?: boolean horizontal?: boolean name?: string @@ -326,6 +327,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox< value, name, onChange, + by = (a, z) => a === z, disabled = false, horizontal = false, multiple = false, @@ -341,7 +343,14 @@ let ListboxRoot = forwardRefWithAs(function Listbox< value, onChange, mode: multiple ? ValueMode.Multi : ValueMode.Single, - compare: useEvent((a, z) => a === z), + compare: useEvent( + typeof by === 'string' + ? (a: TType, z: TType) => { + let property = by as unknown as keyof TType + return a[property] === z[property] + } + : by + ), }, }, labelRef: createRef(), diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx index 52def2f..916cf04 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx @@ -322,7 +322,7 @@ describe('Rendering', () => { }) ) - it.skip( + it( 'should expose internal data as a render prop', suppressConsoleLogs(async () => { function Example() { @@ -372,7 +372,7 @@ describe('Rendering', () => { }) ) - describe.skip('Equality', () => { + describe('Equality', () => { let options = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, 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 d668fb6..cd848b7 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -113,17 +113,25 @@ let RadioGroupRoot = forwardRefWithAs(function RadioGroup< props: Props< TTag, RadioGroupRenderPropArg, - RadioGroupPropsWeControl | 'value' | 'onChange' | 'disabled' | 'name' + RadioGroupPropsWeControl | 'value' | 'onChange' | 'disabled' | 'name' | 'by' > & { value: TType onChange(value: TType): void + by?: (keyof TType & string) | ((a: TType, z: TType) => boolean) disabled?: boolean name?: string }, ref: Ref ) { - let { value, name, onChange, disabled = false, ...theirProps } = props - let compare = useEvent((a, z) => a === z) + let { value, name, onChange, by = (a, z) => a === z, disabled = false, ...theirProps } = props + let compare = useEvent( + typeof by === 'string' + ? (a: TType, z: TType) => { + let property = by as unknown as keyof TType + return a[property] === z[property] + } + : by + ) let [state, dispatch] = useReducer(stateReducer, { options: [] } as StateDefinition) let options = state.options as unknown as Option[] let [labelledby, LabelProvider] = useLabels() diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index 445f21e..7b949d8 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -126,20 +126,20 @@ function _render( } let dataAttributes: Record = {} - // if (slot) { - // let exposeState = false - // let states = [] - // for (let [k, v] of Object.entries(slot)) { - // if (typeof v === 'boolean') { - // exposeState = true - // } - // if (v === true) { - // states.push(k) - // } - // } - // - // if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ') - // } + if (slot) { + let exposeState = false + let states = [] + for (let [k, v] of Object.entries(slot)) { + if (typeof v === 'boolean') { + exposeState = true + } + if (v === true) { + states.push(k) + } + } + + if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ') + } if (Component === Fragment) { if (Object.keys(compact(rest)).length > 0) { diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts index 990a9f1..77c9eb3 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts @@ -203,7 +203,7 @@ describe('Rendering', () => { }) ) - describe.skip('Equality', () => { + describe('Equality', () => { let options = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts index 67dffb2..401ca63 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -35,6 +35,10 @@ import { useOutsideClick } from '../../hooks/use-outside-click' import { Hidden, Features as HiddenFeatures } from '../../internal/hidden' import { objectToFormEntries } from '../../utils/form' +function defaultComparator(a: T, z: T): boolean { + return a === z +} + enum ComboboxStates { Open, Closed, @@ -112,6 +116,7 @@ export let Combobox = defineComponent({ props: { as: { type: [Object, String], default: 'template' }, disabled: { type: [Boolean], default: false }, + by: { type: [String, Function], default: () => defaultComparator }, modelValue: { type: [Object, String, Number, Boolean] }, name: { type: String }, nullable: { type: Boolean, default: false }, @@ -175,7 +180,11 @@ export let Combobox = defineComponent({ value, mode, compare(a: any, z: any) { - return a === z + if (typeof props.by === 'string') { + let property = props.by as unknown as any + return a[property] === z[property] + } + return props.by(a, z) }, nullable, inputRef, diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx index 7aebf55..628ea28 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx @@ -183,7 +183,7 @@ describe('Rendering', () => { }) ) - describe.skip('Equality', () => { + describe('Equality', () => { let options = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.ts b/packages/@headlessui-vue/src/components/listbox/listbox.ts index b74b0f4..5bd9062 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.ts +++ b/packages/@headlessui-vue/src/components/listbox/listbox.ts @@ -33,6 +33,10 @@ import { useOutsideClick } from '../../hooks/use-outside-click' import { Hidden, Features as HiddenFeatures } from '../../internal/hidden' import { objectToFormEntries } from '../../utils/form' +function defaultComparator(a: T, z: T): boolean { + return a === z +} + enum ListboxStates { Open, Closed, @@ -112,6 +116,7 @@ export let Listbox = defineComponent({ props: { as: { type: [Object, String], default: 'template' }, disabled: { type: [Boolean], default: false }, + by: { type: [String, Function], default: () => defaultComparator }, horizontal: { type: [Boolean], default: false }, modelValue: { type: [Object, String, Number, Boolean] }, name: { type: String, optional: true }, @@ -167,7 +172,11 @@ export let Listbox = defineComponent({ value, mode, compare(a: any, z: any) { - return a === z + if (typeof props.by === 'string') { + let property = props.by as unknown as any + return a[property] === z[property] + } + return props.by(a, z) }, orientation: computed(() => (props.horizontal ? 'horizontal' : 'vertical')), labelRef, diff --git a/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts b/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts index f8ad797..2b5ca7d 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts @@ -496,7 +496,7 @@ describe('Rendering', () => { assertActiveElement(getByText('Option 3')) }) - describe.skip('Equality', () => { + describe('Equality', () => { let options = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, 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 5c4409c..d80917e 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts @@ -27,6 +27,10 @@ import { Hidden, Features as HiddenFeatures } from '../../internal/hidden' import { attemptSubmit, objectToFormEntries } from '../../utils/form' import { getOwnerDocument } from '../../utils/owner' +function defaultComparator(a: T, z: T): boolean { + return a === z +} + interface Option { id: string element: Ref @@ -71,6 +75,7 @@ export let RadioGroup = defineComponent({ props: { as: { type: [Object, String], default: 'div' }, disabled: { type: [Boolean], default: false }, + by: { type: [String, Function], default: () => defaultComparator }, modelValue: { type: [Object, String, Number, Boolean] }, name: { type: String, optional: true }, }, @@ -102,7 +107,11 @@ export let RadioGroup = defineComponent({ ) ), compare(a: any, z: any) { - return a === z + if (typeof props.by === 'string') { + let property = props.by as unknown as any + return a[property] === z[property] + } + return props.by(a, z) }, change(nextValue: unknown) { if (props.disabled) return false diff --git a/packages/@headlessui-vue/src/utils/render.ts b/packages/@headlessui-vue/src/utils/render.ts index 8cf29b6..172bd0d 100644 --- a/packages/@headlessui-vue/src/utils/render.ts +++ b/packages/@headlessui-vue/src/utils/render.ts @@ -86,20 +86,20 @@ function _render({ let children = slots.default?.(slot) let dataAttributes: Record = {} - // if (slot) { - // let exposeState = false - // let states = [] - // for (let [k, v] of Object.entries(slot)) { - // if (typeof v === 'boolean') { - // exposeState = true - // } - // if (v === true) { - // states.push(k) - // } - // } - // - // if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ') - // } + if (slot) { + let exposeState = false + let states = [] + for (let [k, v] of Object.entries(slot)) { + if (typeof v === 'boolean') { + exposeState = true + } + if (v === true) { + states.push(k) + } + } + + if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ') + } if (as === 'template') { children = flattenFragments(children as VNode[])