microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.65k stars 12.44k forks source link

#53502 breaks @wordpress/components types on DT #53941

Closed sandersn closed 1 year ago

sandersn commented 1 year ago

Before #53502,

<C.DropdownMenu icon="move" label="Select a direction">
    {({ onClose }) => (
        <div>
            <button onClick={onClose}>Click me</button>
        </div>
    )}
</C.DropdownMenu>;

Gave a contextual type to onClose. Now it does not. Here are some snippets of the wordpress/components code:

In dropdown-menu/index.d.ts:

declare namespace DropdownMenu {
    // ....
    interface PropsWithChildren extends BaseProps {
        /**
         * A function render prop which should return an element or elements
         * valid for use in a `DropdownMenu`: `MenuItem`, `MenuItemsChoice`, or
         * `MenuGroup`.
         */
        children(props: Dropdown.RenderProps): JSX.Element;
        controls?: never | undefined;
    }
    // .....
    type Props = PropsWithChildren | PropsWithControls;
}
declare const DropdownMenu: ComponentType<DropdownMenu.Props>;

export default DropdownMenu;

and in dropdown/index.d.ts

declare namespace Dropdown {
    // ....
    interface RenderProps {
        /**
         * Whether the dropdown menu is opened or not.
         */
        isOpen: boolean;
        /**
         * A function switching the dropdown menu's state from open to closed
         * and vice versa.
         */
        onToggle(): void;
        /**
         * A function that closes the menu if invoked.
         */
        onClose(): void;
    }
}
sandersn commented 1 year ago

@Andarist you are probably interested in this too

Andarist commented 1 year ago

Ye, definitely! Thanks for the ping - I can investigate this over the weekend.

Andarist commented 1 year ago

complete/standalone repro:

import * as React from "react";

declare namespace DropdownMenu {
  interface BaseProps {
    icon: string;
    label: string;
  }
  interface PropsWithChildren extends BaseProps {
    children(props: { onClose: () => void }): JSX.Element;
    controls?: never | undefined;
  }
  interface PropsWithControls extends BaseProps {
    controls: Control[];
    children?: never | undefined;
  }
  interface Control {
    title: string;
  }
  type Props = PropsWithChildren | PropsWithControls;
}
declare const DropdownMenu: React.ComponentType<DropdownMenu.Props>;

<DropdownMenu icon="move" label="Select a direction">
  {({ onClose }) => (
    <div>
      <button onClick={onClose}>Click me</button>
    </div>
  )}
</DropdownMenu>;

TS playground

The fix is to expand the logic in discriminateContextualTypeByJSXAttributes to include child jsx expression as an implicit attribute. I'm on it.