Skip to content

Commit 989cd6b

Browse files
authored
Fix XYZPropsWeControl and cleanup internal TypeScript types (#2329)
* cleanup `XYZPropsWeControl` The idea behind the `PropsWeControl` is that we can omit all the fields that we are controlling entirely. In this case, passing a prop like `role`, but if we already set the role ourselves then the prop won't do anything at all. This is why we want to alert the end user that it is an "error". It can also happen that we "control" the value by default, but keep incoming props into account. For example we generate a unique ID for most components, but you can provide your own to override it. In this case we _don't_ want to include the ID in the `XYZPropsWeControl`. Additionally, we introduced some functionality months ago where we call event callbacks (`onClick`, ...) from the incoming props before our own callbacks. This means that by definition all `onXYZ` callbacks can be provided. * improve defining types Whenever we explicitly provide custom types for certain props, then we make sure to omit those keys first from the original props (of let's say an `input`). This is important so that TypeScript doesn't try to "merge" those types together. * cleanup: move `useEffect` * add `defaultValue` explicitly * ensure tests are not using `any` because of `onChange={console.log}` The `console.log` is typed as `(...args: any[]) => void` which means that it will incorrectly mark its incoming data as `any` as well. Converting it to `x => console.log(x)` makes TypeScript happy. Or in this case, angry since it found a bug. This is required because it _can_ be that your value (e.g.: the value of a Combobox) is an object (e.g.: a `User`), but it is also nullable. Therefore we can provide the value `null`. This would mean that eventually this resolves to `keyof null` which is `never`, but we just want a string in this case. ```diff -export type ByComparator<T> = (keyof T & string) | ((a: T, b: T) => boolean) +export type ByComparator<T> = + | (T extends null ? string : keyof T & string) + | ((a: T, b: T) => boolean) ``` * improve the internal types of the `Combobox` component * improve the internal types of the `Disclosure` component * improve the internal types of the `Listbox` component * improve the internal types of the `Menu` component * improve the internal types of the `Popover` component * improve the internal types of the `Tabs` component * improve the internal types of the `Transition` component * use `Override` in `Hidden` as well * cleanup unused code * don't check the `useSyncExternalStoreShimClient` * don't check the `useSyncExternalStoreShimServer` * improve types in the render tests * fix `Ref<TTag>` to be `Ref<HTMLElement>` * improve internal types of the `Transition` component (Vue) + add `attrs.class` as well * use different type for `AnyComponent` * update changelog
1 parent 948ae73 commit 989cd6b

File tree

23 files changed

+386
-400
lines changed

23 files changed

+386
-400
lines changed

packages/@headlessui-react/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Ensure `Transition` component completes if nothing is transitioning ([#2318](https://github.com/tailwindlabs/headlessui/pull/2318))
1313
- Enable native label behavior for `<Switch>` where possible ([#2265](https://github.com/tailwindlabs/headlessui/pull/2265))
1414
- Allow root containers from the `Dialog` component in the `FocusTrap` component ([#2322](https://github.com/tailwindlabs/headlessui/pull/2322))
15+
- Fix `XYZPropsWeControl` and cleanup internal TypeScript types ([#2329](https://github.com/tailwindlabs/headlessui/pull/2329))
1516

1617
## [1.7.12] - 2023-02-24
1718

packages/@headlessui-react/src/components/combobox/combobox.test.tsx

+109-109
Large diffs are not rendered by default.

packages/@headlessui-react/src/components/combobox/combobox.tsx

+37-41
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, {
44
createRef,
55
useCallback,
66
useContext,
7+
useEffect,
78
useMemo,
89
useReducer,
910
useRef,
@@ -14,7 +15,6 @@ import React, {
1415
MouseEvent as ReactMouseEvent,
1516
MutableRefObject,
1617
Ref,
17-
useEffect,
1818
} from 'react'
1919
import { ByComparator, EnsureArray, Expand, Props } from '../../types'
2020

@@ -385,31 +385,31 @@ export type ComboboxProps<
385385

386386
function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG>(
387387
props: ComboboxProps<TValue, true, true, TTag>,
388-
ref: Ref<TTag>
388+
ref: Ref<HTMLElement>
389389
): JSX.Element
390390
function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG>(
391391
props: ComboboxProps<TValue, true, false, TTag>,
392-
ref: Ref<TTag>
392+
ref: Ref<HTMLElement>
393393
): JSX.Element
394394
function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG>(
395395
props: ComboboxProps<TValue, false, false, TTag>,
396-
ref: Ref<TTag>
396+
ref: Ref<HTMLElement>
397397
): JSX.Element
398398
function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG>(
399399
props: ComboboxProps<TValue, false, true, TTag>,
400-
ref: Ref<TTag>
400+
ref: Ref<HTMLElement>
401401
): JSX.Element
402402

403403
function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_TAG>(
404404
props: ComboboxProps<TValue, boolean | undefined, boolean | undefined, TTag>,
405-
ref: Ref<TTag>
405+
ref: Ref<HTMLElement>
406406
) {
407407
let {
408408
value: controlledValue,
409409
defaultValue,
410410
onChange: controlledOnChange,
411411
name,
412-
by = (a: any, z: any) => a === z,
412+
by = (a: TValue, z: TValue) => a === z,
413413
disabled = false,
414414
__demoMode = false,
415415
nullable = false,
@@ -440,16 +440,18 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
440440
let buttonRef = useRef<_Data['buttonRef']['current']>(null)
441441
let optionsRef = useRef<_Data['optionsRef']['current']>(null)
442442

443+
type TActualValue = true extends typeof multiple ? EnsureArray<TValue>[number] : TValue
443444
let compare = useEvent(
445+
// @ts-expect-error Eventually we'll want to tackle this, but for now this will do.
444446
typeof by === 'string'
445-
? (a, z) => {
446-
let property = by as unknown as keyof TValue
447+
? (a: TActualValue, z: TActualValue) => {
448+
let property = by as unknown as keyof TActualValue
447449
return a?.[property] === z?.[property]
448450
}
449451
: by
450452
)
451453

452-
let isSelected: (value: unknown) => boolean = useCallback(
454+
let isSelected: (value: TValue) => boolean = useCallback(
453455
(compareValue) =>
454456
match(data.mode, {
455457
[ValueMode.Multi]: () =>
@@ -695,23 +697,24 @@ interface InputRenderPropArg {
695697
disabled: boolean
696698
}
697699
type InputPropsWeControl =
698-
| 'role'
699-
| 'aria-labelledby'
700-
| 'aria-expanded'
701700
| 'aria-activedescendant'
702701
| 'aria-autocomplete'
703-
| 'onKeyDown'
704-
| 'onChange'
705-
| 'displayValue'
702+
| 'aria-controls'
703+
| 'aria-expanded'
704+
| 'aria-labelledby'
705+
| 'disabled'
706+
| 'role'
706707

707708
export type ComboboxInputProps<TTag extends ElementType, TType> = Props<
708709
TTag,
709710
InputRenderPropArg,
710-
InputPropsWeControl
711-
> & {
712-
displayValue?(item: TType): string
713-
onChange?(event: React.ChangeEvent<HTMLInputElement>): void
714-
}
711+
InputPropsWeControl,
712+
{
713+
defaultValue?: TType
714+
displayValue?(item: TType): string
715+
onChange?(event: React.ChangeEvent<HTMLInputElement>): void
716+
}
717+
>
715718

716719
function InputFn<
717720
TTag extends ElementType = typeof DEFAULT_INPUT_TAG,
@@ -1010,15 +1013,12 @@ interface ButtonRenderPropArg {
10101013
value: any
10111014
}
10121015
type ButtonPropsWeControl =
1013-
// | 'type' // While we do "control" this prop we allow it to be overridden
1014-
| 'tabIndex'
1015-
| 'aria-haspopup'
10161016
| 'aria-controls'
10171017
| 'aria-expanded'
1018+
| 'aria-haspopup'
10181019
| 'aria-labelledby'
10191020
| 'disabled'
1020-
| 'onClick'
1021-
| 'onKeyDown'
1021+
| 'tabIndex'
10221022

10231023
export type ComboboxButtonProps<TTag extends ElementType> = Props<
10241024
TTag,
@@ -1131,13 +1131,8 @@ interface LabelRenderPropArg {
11311131
open: boolean
11321132
disabled: boolean
11331133
}
1134-
type LabelPropsWeControl = 'ref' | 'onClick'
11351134

1136-
export type ComboboxLabelProps<TTag extends ElementType> = Props<
1137-
TTag,
1138-
LabelRenderPropArg,
1139-
LabelPropsWeControl
1140-
>
1135+
export type ComboboxLabelProps<TTag extends ElementType> = Props<TTag, LabelRenderPropArg>
11411136

11421137
function LabelFn<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
11431138
props: ComboboxLabelProps<TTag>,
@@ -1175,18 +1170,18 @@ let DEFAULT_OPTIONS_TAG = 'ul' as const
11751170
interface OptionsRenderPropArg {
11761171
open: boolean
11771172
}
1178-
type OptionsPropsWeControl = 'aria-labelledby' | 'hold' | 'onKeyDown' | 'role' | 'tabIndex'
1173+
type OptionsPropsWeControl = 'aria-labelledby' | 'aria-multiselectable' | 'role' | 'tabIndex'
11791174

11801175
let OptionsRenderFeatures = Features.RenderStrategy | Features.Static
11811176

11821177
export type ComboboxOptionsProps<TTag extends ElementType> = Props<
11831178
TTag,
11841179
OptionsRenderPropArg,
1185-
OptionsPropsWeControl
1186-
> &
1180+
OptionsPropsWeControl,
11871181
PropsForFeatures<typeof OptionsRenderFeatures> & {
11881182
hold?: boolean
11891183
}
1184+
>
11901185

11911186
function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
11921187
props: ComboboxOptionsProps<TTag>,
@@ -1263,16 +1258,17 @@ interface OptionRenderPropArg {
12631258
selected: boolean
12641259
disabled: boolean
12651260
}
1266-
type ComboboxOptionPropsWeControl = 'role' | 'tabIndex' | 'aria-disabled' | 'aria-selected'
1261+
type OptionPropsWeControl = 'role' | 'tabIndex' | 'aria-disabled' | 'aria-selected'
12671262

12681263
export type ComboboxOptionProps<TTag extends ElementType, TType> = Props<
12691264
TTag,
12701265
OptionRenderPropArg,
1271-
ComboboxOptionPropsWeControl | 'value'
1272-
> & {
1273-
disabled?: boolean
1274-
value: TType
1275-
}
1266+
OptionPropsWeControl,
1267+
{
1268+
disabled?: boolean
1269+
value: TType
1270+
}
1271+
>
12761272

12771273
function OptionFn<
12781274
TTag extends ElementType = typeof DEFAULT_OPTION_TAG,

packages/@headlessui-react/src/components/dialog/dialog.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,21 @@ let DEFAULT_DIALOG_TAG = 'div' as const
118118
interface DialogRenderPropArg {
119119
open: boolean
120120
}
121-
type DialogPropsWeControl = 'role' | 'aria-modal' | 'aria-describedby' | 'aria-labelledby'
121+
type DialogPropsWeControl = 'role' | 'aria-describedby' | 'aria-labelledby' | 'aria-modal'
122122

123123
let DialogRenderFeatures = Features.RenderStrategy | Features.Static
124124

125125
export type DialogProps<TTag extends ElementType> = Props<
126126
TTag,
127127
DialogRenderPropArg,
128-
DialogPropsWeControl
129-
> &
128+
DialogPropsWeControl,
130129
PropsForFeatures<typeof DialogRenderFeatures> & {
131130
open?: boolean
132131
onClose(value: boolean): void
133132
initialFocus?: MutableRefObject<HTMLElement | null>
134133
__demoMode?: boolean
135134
}
135+
>
136136

137137
function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
138138
props: DialogProps<TTag>,
@@ -402,7 +402,7 @@ let DEFAULT_OVERLAY_TAG = 'div' as const
402402
interface OverlayRenderPropArg {
403403
open: boolean
404404
}
405-
type OverlayPropsWeControl = 'aria-hidden' | 'onClick'
405+
type OverlayPropsWeControl = 'aria-hidden'
406406

407407
export type DialogOverlayProps<TTag extends ElementType> = Props<
408408
TTag,
@@ -454,7 +454,7 @@ let DEFAULT_BACKDROP_TAG = 'div' as const
454454
interface BackdropRenderPropArg {
455455
open: boolean
456456
}
457-
type BackdropPropsWeControl = 'aria-hidden' | 'onClick'
457+
type BackdropPropsWeControl = 'aria-hidden'
458458

459459
export type DialogBackdropProps<TTag extends ElementType> = Props<
460460
TTag,

packages/@headlessui-react/src/components/disclosure/disclosure.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export type DisclosureProps<TTag extends ElementType> = Props<TTag, DisclosureRe
162162

163163
function DisclosureFn<TTag extends ElementType = typeof DEFAULT_DISCLOSURE_TAG>(
164164
props: DisclosureProps<TTag>,
165-
ref: Ref<TTag>
165+
ref: Ref<HTMLElement>
166166
) {
167167
let { defaultOpen = false, ...theirProps } = props
168168
let internalDisclosureRef = useRef<HTMLElement | null>(null)
@@ -247,14 +247,15 @@ let DEFAULT_BUTTON_TAG = 'button' as const
247247
interface ButtonRenderPropArg {
248248
open: boolean
249249
}
250-
type ButtonPropsWeControl =
251-
// | 'type' // We allow this to be overridden
252-
'aria-expanded' | 'aria-controls' | 'onKeyDown' | 'onClick'
250+
type ButtonPropsWeControl = 'aria-controls' | 'aria-expanded'
253251

254252
export type DisclosureButtonProps<TTag extends ElementType> = Props<
255253
TTag,
256254
ButtonRenderPropArg,
257-
ButtonPropsWeControl
255+
ButtonPropsWeControl,
256+
{
257+
disabled?: boolean
258+
}
258259
>
259260

260261
function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(

0 commit comments

Comments
 (0)