Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues spreading omitted props onto components #3313

Merged
merged 4 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet!
### Fixed

- Fix issues spreading omitted props onto components ([#3313](https://github.com/tailwindlabs/headlessui/pull/3313))

## [2.1.0] - 2024-06-21

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
leaveFrom,
leaveTo,

// @ts-expect-error
...theirProps
} = props as typeof props
let container = useRef<HTMLElement | null>(null)
Expand Down Expand Up @@ -444,6 +443,8 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
className:
classNames(
// Incoming classes if any
// @ts-expect-error: className may not exist because not
// all components accept className (but all HTML elements do)
theirProps.className,

// Apply these classes immediately
Expand Down Expand Up @@ -498,7 +499,6 @@ function TransitionRootFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_C
props: TransitionRootProps<TTag>,
ref: Ref<HTMLElement>
) {
// @ts-expect-error
let { show, appear = false, unmount = true, ...theirProps } = props as typeof props
let internalTransitionRef = useRef<HTMLElement | null>(null)
let requiresRef = shouldForwardRef(props)
Expand Down Expand Up @@ -610,10 +610,8 @@ function ChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_CHILD_TAG>
return (
<>
{!hasTransitionContext && hasOpenClosedContext ? (
// @ts-expect-error This is an object
<TransitionRoot ref={ref} {...props} />
) : (
// @ts-expect-error This is an object
<InternalTransitionChild ref={ref} {...props} />
)}
</>
Expand Down
17 changes: 0 additions & 17 deletions packages/@headlessui-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import type { JSXElementConstructor, ReactElement, ReactNode } from 'react'

export type ReactTag = keyof JSX.IntrinsicElements | JSXElementConstructor<any>

// A unique placeholder we can use as a default. This is nice because we can use this instead of
// defaulting to null / never / ... and possibly collide with actual data.
// Ideally we use a unique symbol here.
let __ = '1D45E01E-AF44-47C4-988A-19A94EBAF55C' as const
export type __ = typeof __

export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never

export type PropsOf<TTag extends ReactTag> = TTag extends React.ElementType
Expand Down Expand Up @@ -55,15 +49,4 @@ export type Props<
ClassNameOverride<TTag, TSlot> &
Overrides

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
export type XOR<T, U> = T | U extends __
? never
: T extends __
? U
: U extends __
? T
: T | U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: T | U

export type EnsureArray<T> = T extends any[] ? T : Expand<T>[]
10 changes: 0 additions & 10 deletions packages/@headlessui-react/src/utils/render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -423,16 +423,6 @@ describe('Features.Static | Features.RenderStrategy', () => {
)
}

// TODO: Can we "legit" test this? 🤔
it('should result in a typescript error', () => {
testRender(
// @ts-expect-error static & unmount together are incompatible
<Dummy show={false} static unmount>
Contents
</Dummy>
)
})

// To avoid duplication, and to make sure that the features tested in isolation can also be
// re-used when they are combined.
testStaticFeature(Dummy)
Expand Down
18 changes: 11 additions & 7 deletions packages/@headlessui-react/src/utils/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
type ReactElement,
type Ref,
} from 'react'
import type { Expand, Props, XOR, __ } from '../types'
import type { Expand, Props } from '../types'
thecrypticace marked this conversation as resolved.
Show resolved Hide resolved
import { classNames } from './class-names'
import { match } from './match'

Expand Down Expand Up @@ -40,17 +40,21 @@ export enum RenderStrategy {
Hidden,
}

type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any
? R
: never

type PropsForFeature<
TPassedInFeatures extends RenderFeatures,
TForFeature extends RenderFeatures,
TProps,
> = {
[P in TPassedInFeatures]: P extends TForFeature ? TProps : __
}[TPassedInFeatures]
> = TPassedInFeatures extends TForFeature ? TProps : {}

export type PropsForFeatures<T extends RenderFeatures> = XOR<
PropsForFeature<T, RenderFeatures.Static, { static?: boolean }>,
PropsForFeature<T, RenderFeatures.RenderStrategy, { unmount?: boolean }>
export type PropsForFeatures<T extends RenderFeatures> = Expand<
UnionToIntersection<
| PropsForFeature<T, RenderFeatures.Static, { static?: boolean }>
| PropsForFeature<T, RenderFeatures.RenderStrategy, { unmount?: boolean }>
>
>

export function render<TFeature extends RenderFeatures, TTag extends ElementType, TSlot>({
Expand Down
Loading