From b6bfc1c7ba6ed22ab5b1b6adc9073d79430d82df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 27 Jul 2020 13:17:46 +0200 Subject: [PATCH 1/8] Provide local JSX namespace for `jsx` --- packages/react/types/index.d.ts | 540 +++++++++++++++++++++++++++++++- packages/react/types/tests.tsx | 31 ++ 2 files changed, 566 insertions(+), 5 deletions(-) diff --git a/packages/react/types/index.d.ts b/packages/react/types/index.d.ts index 9a1e2accd..016e88f88 100644 --- a/packages/react/types/index.d.ts +++ b/packages/react/types/index.d.ts @@ -23,6 +23,7 @@ import { Ref, createElement } from 'react' +import * as PropTypes from 'prop-types' export { ArrayInterpolation, @@ -46,8 +47,6 @@ export function withEmotionCache( func: (props: Props, context: EmotionCache, ref: Ref) => ReactNode ): FC> -export const jsx: typeof createElement - export function css( template: TemplateStringsArray, ...args: Array @@ -94,8 +93,539 @@ export interface ClassNamesProps { */ export function ClassNames(props: ClassNamesProps): ReactElement -declare module 'react' { - interface Attributes { - css?: Interpolation +interface ReactIntrinsicElements { + // HTML + a: React.DetailedHTMLProps< + React.AnchorHTMLAttributes, + HTMLAnchorElement + > + abbr: React.DetailedHTMLProps, HTMLElement> + address: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + area: React.DetailedHTMLProps< + React.AreaHTMLAttributes, + HTMLAreaElement + > + article: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + aside: React.DetailedHTMLProps, HTMLElement> + audio: React.DetailedHTMLProps< + React.AudioHTMLAttributes, + HTMLAudioElement + > + b: React.DetailedHTMLProps, HTMLElement> + base: React.DetailedHTMLProps< + React.BaseHTMLAttributes, + HTMLBaseElement + > + bdi: React.DetailedHTMLProps, HTMLElement> + bdo: React.DetailedHTMLProps, HTMLElement> + big: React.DetailedHTMLProps, HTMLElement> + blockquote: React.DetailedHTMLProps< + React.BlockquoteHTMLAttributes, + HTMLElement + > + body: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLBodyElement + > + br: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLBRElement + > + button: React.DetailedHTMLProps< + React.ButtonHTMLAttributes, + HTMLButtonElement + > + canvas: React.DetailedHTMLProps< + React.CanvasHTMLAttributes, + HTMLCanvasElement + > + caption: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + cite: React.DetailedHTMLProps, HTMLElement> + code: React.DetailedHTMLProps, HTMLElement> + col: React.DetailedHTMLProps< + React.ColHTMLAttributes, + HTMLTableColElement + > + colgroup: React.DetailedHTMLProps< + React.ColgroupHTMLAttributes, + HTMLTableColElement + > + data: React.DetailedHTMLProps< + React.DataHTMLAttributes, + HTMLDataElement + > + datalist: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDataListElement + > + dd: React.DetailedHTMLProps, HTMLElement> + del: React.DetailedHTMLProps< + React.DelHTMLAttributes, + HTMLElement + > + details: React.DetailedHTMLProps< + React.DetailsHTMLAttributes, + HTMLElement + > + dfn: React.DetailedHTMLProps, HTMLElement> + dialog: React.DetailedHTMLProps< + React.DialogHTMLAttributes, + HTMLDialogElement + > + div: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + > + dl: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDListElement + > + dt: React.DetailedHTMLProps, HTMLElement> + em: React.DetailedHTMLProps, HTMLElement> + embed: React.DetailedHTMLProps< + React.EmbedHTMLAttributes, + HTMLEmbedElement + > + fieldset: React.DetailedHTMLProps< + React.FieldsetHTMLAttributes, + HTMLFieldSetElement + > + figcaption: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + figure: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + footer: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + form: React.DetailedHTMLProps< + React.FormHTMLAttributes, + HTMLFormElement + > + h1: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadingElement + > + h2: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadingElement + > + h3: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadingElement + > + h4: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadingElement + > + h5: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadingElement + > + h6: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadingElement + > + head: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHeadElement + > + header: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + hgroup: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + hr: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLHRElement + > + html: React.DetailedHTMLProps< + React.HtmlHTMLAttributes, + HTMLHtmlElement + > + i: React.DetailedHTMLProps, HTMLElement> + iframe: React.DetailedHTMLProps< + React.IframeHTMLAttributes, + HTMLIFrameElement + > + img: React.DetailedHTMLProps< + React.ImgHTMLAttributes, + HTMLImageElement + > + input: React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + > + ins: React.DetailedHTMLProps< + React.InsHTMLAttributes, + HTMLModElement + > + kbd: React.DetailedHTMLProps, HTMLElement> + keygen: React.DetailedHTMLProps< + React.KeygenHTMLAttributes, + HTMLElement + > + label: React.DetailedHTMLProps< + React.LabelHTMLAttributes, + HTMLLabelElement + > + legend: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLLegendElement + > + li: React.DetailedHTMLProps< + React.LiHTMLAttributes, + HTMLLIElement + > + link: React.DetailedHTMLProps< + React.LinkHTMLAttributes, + HTMLLinkElement + > + main: React.DetailedHTMLProps, HTMLElement> + map: React.DetailedHTMLProps< + React.MapHTMLAttributes, + HTMLMapElement + > + mark: React.DetailedHTMLProps, HTMLElement> + menu: React.DetailedHTMLProps< + React.MenuHTMLAttributes, + HTMLElement + > + menuitem: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + meta: React.DetailedHTMLProps< + React.MetaHTMLAttributes, + HTMLMetaElement + > + meter: React.DetailedHTMLProps< + React.MeterHTMLAttributes, + HTMLElement + > + nav: React.DetailedHTMLProps, HTMLElement> + noindex: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + noscript: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + object: React.DetailedHTMLProps< + React.ObjectHTMLAttributes, + HTMLObjectElement + > + ol: React.DetailedHTMLProps< + React.OlHTMLAttributes, + HTMLOListElement + > + optgroup: React.DetailedHTMLProps< + React.OptgroupHTMLAttributes, + HTMLOptGroupElement + > + option: React.DetailedHTMLProps< + React.OptionHTMLAttributes, + HTMLOptionElement + > + output: React.DetailedHTMLProps< + React.OutputHTMLAttributes, + HTMLElement + > + p: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLParagraphElement + > + param: React.DetailedHTMLProps< + React.ParamHTMLAttributes, + HTMLParamElement + > + picture: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + pre: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLPreElement + > + progress: React.DetailedHTMLProps< + React.ProgressHTMLAttributes, + HTMLProgressElement + > + q: React.DetailedHTMLProps< + React.QuoteHTMLAttributes, + HTMLQuoteElement + > + rp: React.DetailedHTMLProps, HTMLElement> + rt: React.DetailedHTMLProps, HTMLElement> + ruby: React.DetailedHTMLProps, HTMLElement> + s: React.DetailedHTMLProps, HTMLElement> + samp: React.DetailedHTMLProps, HTMLElement> + script: React.DetailedHTMLProps< + React.ScriptHTMLAttributes, + HTMLScriptElement + > + section: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + select: React.DetailedHTMLProps< + React.SelectHTMLAttributes, + HTMLSelectElement + > + small: React.DetailedHTMLProps, HTMLElement> + source: React.DetailedHTMLProps< + React.SourceHTMLAttributes, + HTMLSourceElement + > + span: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLSpanElement + > + strong: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + style: React.DetailedHTMLProps< + React.StyleHTMLAttributes, + HTMLStyleElement + > + sub: React.DetailedHTMLProps, HTMLElement> + summary: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + sup: React.DetailedHTMLProps, HTMLElement> + table: React.DetailedHTMLProps< + React.TableHTMLAttributes, + HTMLTableElement + > + template: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLTemplateElement + > + tbody: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLTableSectionElement + > + td: React.DetailedHTMLProps< + React.TdHTMLAttributes, + HTMLTableDataCellElement + > + textarea: React.DetailedHTMLProps< + React.TextareaHTMLAttributes, + HTMLTextAreaElement + > + tfoot: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLTableSectionElement + > + th: React.DetailedHTMLProps< + React.ThHTMLAttributes, + HTMLTableHeaderCellElement + > + thead: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLTableSectionElement + > + time: React.DetailedHTMLProps< + React.TimeHTMLAttributes, + HTMLElement + > + title: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLTitleElement + > + tr: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLTableRowElement + > + track: React.DetailedHTMLProps< + React.TrackHTMLAttributes, + HTMLTrackElement + > + u: React.DetailedHTMLProps, HTMLElement> + ul: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLUListElement + > + var: React.DetailedHTMLProps, HTMLElement> + video: React.DetailedHTMLProps< + React.VideoHTMLAttributes, + HTMLVideoElement + > + wbr: React.DetailedHTMLProps, HTMLElement> + webview: React.DetailedHTMLProps< + React.WebViewHTMLAttributes, + HTMLWebViewElement + > + // SVG + svg: React.SVGProps + animate: React.SVGProps + animateMotion: React.SVGProps + animateTransform: React.SVGProps + circle: React.SVGProps + clipPath: React.SVGProps + defs: React.SVGProps + desc: React.SVGProps + ellipse: React.SVGProps + feBlend: React.SVGProps + feColorMatrix: React.SVGProps + feComponentTransfer: React.SVGProps + feComposite: React.SVGProps + feConvolveMatrix: React.SVGProps + feDiffuseLighting: React.SVGProps + feDisplacementMap: React.SVGProps + feDistantLight: React.SVGProps + feDropShadow: React.SVGProps + feFlood: React.SVGProps + feFuncA: React.SVGProps + feFuncB: React.SVGProps + feFuncG: React.SVGProps + feFuncR: React.SVGProps + feGaussianBlur: React.SVGProps + feImage: React.SVGProps + feMerge: React.SVGProps + feMergeNode: React.SVGProps + feMorphology: React.SVGProps + feOffset: React.SVGProps + fePointLight: React.SVGProps + feSpecularLighting: React.SVGProps + feSpotLight: React.SVGProps + feTile: React.SVGProps + feTurbulence: React.SVGProps + filter: React.SVGProps + foreignObject: React.SVGProps + g: React.SVGProps + image: React.SVGProps + line: React.SVGProps + linearGradient: React.SVGProps + marker: React.SVGProps + mask: React.SVGProps + metadata: React.SVGProps + mpath: React.SVGProps + path: React.SVGProps + pattern: React.SVGProps + polygon: React.SVGProps + polyline: React.SVGProps + radialGradient: React.SVGProps + rect: React.SVGProps + stop: React.SVGProps + switch: React.SVGProps + symbol: React.SVGProps + text: React.SVGProps + textPath: React.SVGProps + tspan: React.SVGProps + use: React.SVGProps + view: React.SVGProps +} + +// naked 'any' type in a conditional type will short circuit and union both the then/else branches +// so boolean is only resolved for T = any +type IsExactlyAny = boolean extends (T extends never ? true : false) + ? true + : false + +type ExactlyAnyPropertyKeys = { + [K in keyof T]: IsExactlyAny extends true ? K : never +}[keyof T] +type NotExactlyAnyPropertyKeys = Exclude> + +// Try to resolve ill-defined props like for JS users: props can be any, or sometimes objects with properties of type any +type MergePropTypes = + // Distribute over P in case it is a union type + P extends any // If props is type any, use propTypes definitions + ? IsExactlyAny

extends true + ? T // If declared props have indexed properties, ignore inferred props entirely as keyof gets widened + : string extends keyof P + ? P // Prefer declared types which are not exactly any + : Pick> & + // For props which are exactly any, use the type inferred from propTypes if present + Pick>> & + // Keep leftover props not specified in propTypes + Pick> + : never + +// Any prop that has a default prop becomes optional, but its type is unchanged +// Undeclared default props are augmented into the resulting allowable attributes +// If declared props have indexed properties, ignore default props entirely as keyof gets widened +// Wrap in an outer-level conditional type to allow distribution over props that are unions +type Defaultize = P extends any + ? string extends keyof P + ? P + : Pick> & + Partial>> & + Partial>> + : never + +type ReactManagedAttributes = C extends { + propTypes: infer T + defaultProps: infer D +} + ? Defaultize>, D> + : C extends { propTypes: infer T } + ? MergePropTypes> + : C extends { defaultProps: infer D } ? Defaultize : P + +// We can't recurse forever because `type` can't be self-referential; +// let's assume it's reasonable to do a single React.lazy() around a single React.memo() / vice-versa +type ReactLibraryManagedAttributes = C extends + | React.MemoExoticComponent + | React.LazyExoticComponent + ? (T extends + | React.MemoExoticComponent + | React.LazyExoticComponent + ? ReactManagedAttributes + : ReactManagedAttributes) + : ReactManagedAttributes + +type WithConditionalCssProp

= P extends { className?: string } + ? P & { css?: Interpolation } + : P + +export const jsx: typeof createElement +export namespace jsx { + export namespace JSX { + interface Element extends React.ReactElement {} + interface ElementClass extends React.Component { + render(): React.ReactNode + } + interface ElementAttributesProperty { + props: {} + } + interface ElementChildrenAttribute { + children: {} + } + + type LibraryManagedAttributes = C extends React.ComponentType + ? WithConditionalCssProp + : WithConditionalCssProp> + + // tslint:disable-next-line:no-empty-interface + interface IntrinsicAttributes extends React.Attributes {} + // tslint:disable-next-line:no-empty-interface + interface IntrinsicClassAttributes extends React.ClassAttributes {} + + type IntrinsicElements = { + [K in keyof ReactIntrinsicElements]: ReactIntrinsicElements[K] & { + css?: Interpolation + } + } } } diff --git a/packages/react/types/tests.tsx b/packages/react/types/tests.tsx index d57b4d0d7..c1a7d8300 100644 --- a/packages/react/types/tests.tsx +++ b/packages/react/types/tests.tsx @@ -125,3 +125,34 @@ const anim1 = keyframes` color: ${theme.secondaryColor}; `} /> + +{ + const CompWithClassNameSupport = function CompWithClassNameSupport(_props: { + prop1: string + className?: string + }) { + return null + } + ; +} + +{ + const CompWithoutClassNameSupport = function CompWithoutClassNameSupport(_props: { + prop1: string + }) { + return null + } + + // $ExpectError + ; +} From 3e27ca35ffc0e59466145925bfd63ecaf7807442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 27 Jul 2020 13:31:45 +0200 Subject: [PATCH 2/8] Reuse global JSX namespace defined by React to declare our own --- packages/react/types/index.d.ts | 540 ++------------------------------ packages/react/types/tests.tsx | 34 +- 2 files changed, 48 insertions(+), 526 deletions(-) diff --git a/packages/react/types/index.d.ts b/packages/react/types/index.d.ts index 016e88f88..e265ceec2 100644 --- a/packages/react/types/index.d.ts +++ b/packages/react/types/index.d.ts @@ -23,7 +23,6 @@ import { Ref, createElement } from 'react' -import * as PropTypes from 'prop-types' export { ArrayInterpolation, @@ -93,537 +92,40 @@ export interface ClassNamesProps { */ export function ClassNames(props: ClassNamesProps): ReactElement -interface ReactIntrinsicElements { - // HTML - a: React.DetailedHTMLProps< - React.AnchorHTMLAttributes, - HTMLAnchorElement - > - abbr: React.DetailedHTMLProps, HTMLElement> - address: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - area: React.DetailedHTMLProps< - React.AreaHTMLAttributes, - HTMLAreaElement - > - article: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - aside: React.DetailedHTMLProps, HTMLElement> - audio: React.DetailedHTMLProps< - React.AudioHTMLAttributes, - HTMLAudioElement - > - b: React.DetailedHTMLProps, HTMLElement> - base: React.DetailedHTMLProps< - React.BaseHTMLAttributes, - HTMLBaseElement - > - bdi: React.DetailedHTMLProps, HTMLElement> - bdo: React.DetailedHTMLProps, HTMLElement> - big: React.DetailedHTMLProps, HTMLElement> - blockquote: React.DetailedHTMLProps< - React.BlockquoteHTMLAttributes, - HTMLElement - > - body: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLBodyElement - > - br: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLBRElement - > - button: React.DetailedHTMLProps< - React.ButtonHTMLAttributes, - HTMLButtonElement - > - canvas: React.DetailedHTMLProps< - React.CanvasHTMLAttributes, - HTMLCanvasElement - > - caption: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - cite: React.DetailedHTMLProps, HTMLElement> - code: React.DetailedHTMLProps, HTMLElement> - col: React.DetailedHTMLProps< - React.ColHTMLAttributes, - HTMLTableColElement - > - colgroup: React.DetailedHTMLProps< - React.ColgroupHTMLAttributes, - HTMLTableColElement - > - data: React.DetailedHTMLProps< - React.DataHTMLAttributes, - HTMLDataElement - > - datalist: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLDataListElement - > - dd: React.DetailedHTMLProps, HTMLElement> - del: React.DetailedHTMLProps< - React.DelHTMLAttributes, - HTMLElement - > - details: React.DetailedHTMLProps< - React.DetailsHTMLAttributes, - HTMLElement - > - dfn: React.DetailedHTMLProps, HTMLElement> - dialog: React.DetailedHTMLProps< - React.DialogHTMLAttributes, - HTMLDialogElement - > - div: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLDivElement - > - dl: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLDListElement - > - dt: React.DetailedHTMLProps, HTMLElement> - em: React.DetailedHTMLProps, HTMLElement> - embed: React.DetailedHTMLProps< - React.EmbedHTMLAttributes, - HTMLEmbedElement - > - fieldset: React.DetailedHTMLProps< - React.FieldsetHTMLAttributes, - HTMLFieldSetElement - > - figcaption: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - figure: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - footer: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - form: React.DetailedHTMLProps< - React.FormHTMLAttributes, - HTMLFormElement - > - h1: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadingElement - > - h2: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadingElement - > - h3: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadingElement - > - h4: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadingElement - > - h5: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadingElement - > - h6: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadingElement - > - head: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHeadElement - > - header: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - hgroup: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - hr: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLHRElement - > - html: React.DetailedHTMLProps< - React.HtmlHTMLAttributes, - HTMLHtmlElement - > - i: React.DetailedHTMLProps, HTMLElement> - iframe: React.DetailedHTMLProps< - React.IframeHTMLAttributes, - HTMLIFrameElement - > - img: React.DetailedHTMLProps< - React.ImgHTMLAttributes, - HTMLImageElement - > - input: React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement - > - ins: React.DetailedHTMLProps< - React.InsHTMLAttributes, - HTMLModElement - > - kbd: React.DetailedHTMLProps, HTMLElement> - keygen: React.DetailedHTMLProps< - React.KeygenHTMLAttributes, - HTMLElement - > - label: React.DetailedHTMLProps< - React.LabelHTMLAttributes, - HTMLLabelElement - > - legend: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLLegendElement - > - li: React.DetailedHTMLProps< - React.LiHTMLAttributes, - HTMLLIElement - > - link: React.DetailedHTMLProps< - React.LinkHTMLAttributes, - HTMLLinkElement - > - main: React.DetailedHTMLProps, HTMLElement> - map: React.DetailedHTMLProps< - React.MapHTMLAttributes, - HTMLMapElement - > - mark: React.DetailedHTMLProps, HTMLElement> - menu: React.DetailedHTMLProps< - React.MenuHTMLAttributes, - HTMLElement - > - menuitem: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - meta: React.DetailedHTMLProps< - React.MetaHTMLAttributes, - HTMLMetaElement - > - meter: React.DetailedHTMLProps< - React.MeterHTMLAttributes, - HTMLElement - > - nav: React.DetailedHTMLProps, HTMLElement> - noindex: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - noscript: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - object: React.DetailedHTMLProps< - React.ObjectHTMLAttributes, - HTMLObjectElement - > - ol: React.DetailedHTMLProps< - React.OlHTMLAttributes, - HTMLOListElement - > - optgroup: React.DetailedHTMLProps< - React.OptgroupHTMLAttributes, - HTMLOptGroupElement - > - option: React.DetailedHTMLProps< - React.OptionHTMLAttributes, - HTMLOptionElement - > - output: React.DetailedHTMLProps< - React.OutputHTMLAttributes, - HTMLElement - > - p: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLParagraphElement - > - param: React.DetailedHTMLProps< - React.ParamHTMLAttributes, - HTMLParamElement - > - picture: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - pre: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLPreElement - > - progress: React.DetailedHTMLProps< - React.ProgressHTMLAttributes, - HTMLProgressElement - > - q: React.DetailedHTMLProps< - React.QuoteHTMLAttributes, - HTMLQuoteElement - > - rp: React.DetailedHTMLProps, HTMLElement> - rt: React.DetailedHTMLProps, HTMLElement> - ruby: React.DetailedHTMLProps, HTMLElement> - s: React.DetailedHTMLProps, HTMLElement> - samp: React.DetailedHTMLProps, HTMLElement> - script: React.DetailedHTMLProps< - React.ScriptHTMLAttributes, - HTMLScriptElement - > - section: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - select: React.DetailedHTMLProps< - React.SelectHTMLAttributes, - HTMLSelectElement - > - small: React.DetailedHTMLProps, HTMLElement> - source: React.DetailedHTMLProps< - React.SourceHTMLAttributes, - HTMLSourceElement - > - span: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLSpanElement - > - strong: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - style: React.DetailedHTMLProps< - React.StyleHTMLAttributes, - HTMLStyleElement - > - sub: React.DetailedHTMLProps, HTMLElement> - summary: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > - sup: React.DetailedHTMLProps, HTMLElement> - table: React.DetailedHTMLProps< - React.TableHTMLAttributes, - HTMLTableElement - > - template: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLTemplateElement - > - tbody: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLTableSectionElement - > - td: React.DetailedHTMLProps< - React.TdHTMLAttributes, - HTMLTableDataCellElement - > - textarea: React.DetailedHTMLProps< - React.TextareaHTMLAttributes, - HTMLTextAreaElement - > - tfoot: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLTableSectionElement - > - th: React.DetailedHTMLProps< - React.ThHTMLAttributes, - HTMLTableHeaderCellElement - > - thead: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLTableSectionElement - > - time: React.DetailedHTMLProps< - React.TimeHTMLAttributes, - HTMLElement - > - title: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLTitleElement - > - tr: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLTableRowElement - > - track: React.DetailedHTMLProps< - React.TrackHTMLAttributes, - HTMLTrackElement - > - u: React.DetailedHTMLProps, HTMLElement> - ul: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLUListElement - > - var: React.DetailedHTMLProps, HTMLElement> - video: React.DetailedHTMLProps< - React.VideoHTMLAttributes, - HTMLVideoElement - > - wbr: React.DetailedHTMLProps, HTMLElement> - webview: React.DetailedHTMLProps< - React.WebViewHTMLAttributes, - HTMLWebViewElement - > - // SVG - svg: React.SVGProps - animate: React.SVGProps - animateMotion: React.SVGProps - animateTransform: React.SVGProps - circle: React.SVGProps - clipPath: React.SVGProps - defs: React.SVGProps - desc: React.SVGProps - ellipse: React.SVGProps - feBlend: React.SVGProps - feColorMatrix: React.SVGProps - feComponentTransfer: React.SVGProps - feComposite: React.SVGProps - feConvolveMatrix: React.SVGProps - feDiffuseLighting: React.SVGProps - feDisplacementMap: React.SVGProps - feDistantLight: React.SVGProps - feDropShadow: React.SVGProps - feFlood: React.SVGProps - feFuncA: React.SVGProps - feFuncB: React.SVGProps - feFuncG: React.SVGProps - feFuncR: React.SVGProps - feGaussianBlur: React.SVGProps - feImage: React.SVGProps - feMerge: React.SVGProps - feMergeNode: React.SVGProps - feMorphology: React.SVGProps - feOffset: React.SVGProps - fePointLight: React.SVGProps - feSpecularLighting: React.SVGProps - feSpotLight: React.SVGProps - feTile: React.SVGProps - feTurbulence: React.SVGProps - filter: React.SVGProps - foreignObject: React.SVGProps - g: React.SVGProps - image: React.SVGProps - line: React.SVGProps - linearGradient: React.SVGProps - marker: React.SVGProps - mask: React.SVGProps - metadata: React.SVGProps - mpath: React.SVGProps - path: React.SVGProps - pattern: React.SVGProps - polygon: React.SVGProps - polyline: React.SVGProps - radialGradient: React.SVGProps - rect: React.SVGProps - stop: React.SVGProps - switch: React.SVGProps - symbol: React.SVGProps - text: React.SVGProps - textPath: React.SVGProps - tspan: React.SVGProps - use: React.SVGProps - view: React.SVGProps -} - -// naked 'any' type in a conditional type will short circuit and union both the then/else branches -// so boolean is only resolved for T = any -type IsExactlyAny = boolean extends (T extends never ? true : false) - ? true - : false - -type ExactlyAnyPropertyKeys = { - [K in keyof T]: IsExactlyAny extends true ? K : never -}[keyof T] -type NotExactlyAnyPropertyKeys = Exclude> - -// Try to resolve ill-defined props like for JS users: props can be any, or sometimes objects with properties of type any -type MergePropTypes = - // Distribute over P in case it is a union type - P extends any // If props is type any, use propTypes definitions - ? IsExactlyAny

extends true - ? T // If declared props have indexed properties, ignore inferred props entirely as keyof gets widened - : string extends keyof P - ? P // Prefer declared types which are not exactly any - : Pick> & - // For props which are exactly any, use the type inferred from propTypes if present - Pick>> & - // Keep leftover props not specified in propTypes - Pick> - : never - -// Any prop that has a default prop becomes optional, but its type is unchanged -// Undeclared default props are augmented into the resulting allowable attributes -// If declared props have indexed properties, ignore default props entirely as keyof gets widened -// Wrap in an outer-level conditional type to allow distribution over props that are unions -type Defaultize = P extends any - ? string extends keyof P - ? P - : Pick> & - Partial>> & - Partial>> - : never - -type ReactManagedAttributes = C extends { - propTypes: infer T - defaultProps: infer D -} - ? Defaultize>, D> - : C extends { propTypes: infer T } - ? MergePropTypes> - : C extends { defaultProps: infer D } ? Defaultize : P - -// We can't recurse forever because `type` can't be self-referential; -// let's assume it's reasonable to do a single React.lazy() around a single React.memo() / vice-versa -type ReactLibraryManagedAttributes = C extends - | React.MemoExoticComponent - | React.LazyExoticComponent - ? (T extends - | React.MemoExoticComponent - | React.LazyExoticComponent - ? ReactManagedAttributes - : ReactManagedAttributes) - : ReactManagedAttributes - type WithConditionalCssProp

= P extends { className?: string } ? P & { css?: Interpolation } : P +// unpack all here to avoid infinite self-referencing when defining our own JSX namespace +type ReactJSXElement = JSX.Element +type ReactJSXElementClass = JSX.ElementClass +type ReactJSXElementAttributesProperty = JSX.ElementAttributesProperty +type ReactJSXElementChildrenAttribute = JSX.ElementChildrenAttribute +type ReactJSXLibraryManagedAttributes = JSX.LibraryManagedAttributes +type ReactJSXIntrinsicAttributes = JSX.IntrinsicAttributes +type ReactJSXIntrinsicClassAttributes = JSX.IntrinsicClassAttributes +type ReactJSXIntrinsicElements = JSX.IntrinsicElements + export const jsx: typeof createElement export namespace jsx { export namespace JSX { - interface Element extends React.ReactElement {} - interface ElementClass extends React.Component { - render(): React.ReactNode - } - interface ElementAttributesProperty { - props: {} - } - interface ElementChildrenAttribute { - children: {} - } + interface Element extends ReactJSXElement {} + interface ElementClass extends ReactJSXElementClass {} + interface ElementAttributesProperty + extends ReactJSXElementAttributesProperty {} + interface ElementChildrenAttribute + extends ReactJSXElementChildrenAttribute {} type LibraryManagedAttributes = C extends React.ComponentType ? WithConditionalCssProp - : WithConditionalCssProp> + : WithConditionalCssProp> - // tslint:disable-next-line:no-empty-interface - interface IntrinsicAttributes extends React.Attributes {} - // tslint:disable-next-line:no-empty-interface - interface IntrinsicClassAttributes extends React.ClassAttributes {} + interface IntrinsicAttributes extends ReactJSXIntrinsicAttributes {} + interface IntrinsicClassAttributes + extends ReactJSXIntrinsicClassAttributes {} type IntrinsicElements = { - [K in keyof ReactIntrinsicElements]: ReactIntrinsicElements[K] & { + [K in keyof ReactJSXIntrinsicElements]: ReactJSXIntrinsicElements[K] & { css?: Interpolation } } diff --git a/packages/react/types/tests.tsx b/packages/react/types/tests.tsx index c1a7d8300..472d9089c 100644 --- a/packages/react/types/tests.tsx +++ b/packages/react/types/tests.tsx @@ -1,5 +1,5 @@ /** @jsx jsx */ -import { ComponentClass } from 'react' +import * as React from 'react' import { ClassNames, Global, @@ -58,7 +58,10 @@ const ComponentWithCache = withEmotionCache((_props: {}, cache) => { `} /> -declare const MyComponent: ComponentClass<{ className?: string; world: string }> +declare const MyComponent: React.ComponentClass<{ + className?: string + world: string +}> ; { - const CompWithClassNameSupport = function CompWithClassNameSupport(_props: { + const CompWithClassNameSupport = (_props: { prop1: string className?: string - }) { + }) => { return null } ; + + const MemoedCompWithClassNameSupport = React.memo(CompWithClassNameSupport) + ; } { - const CompWithoutClassNameSupport = function CompWithoutClassNameSupport(_props: { - prop1: string - }) { + const CompWithoutClassNameSupport = (_props: { prop1: string }) => { return null } @@ -155,4 +164,15 @@ const anim1 = keyframes` backgroundColor: 'hotpink' }} /> + + const MemoedCompWithoutClassNameSupport = React.memo( + CompWithoutClassNameSupport + ) + // $ExpectError + ; } From 472eda69980b6873dd25ae0d500fca65a4902189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 27 Jul 2020 15:40:04 +0200 Subject: [PATCH 3/8] Add a .d.ts file that can be included in pragma-less projects --- packages/react/types/css-prop.d.ts | 9 +++++++++ packages/react/types/tsconfig.json | 15 ++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 packages/react/types/css-prop.d.ts diff --git a/packages/react/types/css-prop.d.ts b/packages/react/types/css-prop.d.ts new file mode 100644 index 000000000..6f35b6bfa --- /dev/null +++ b/packages/react/types/css-prop.d.ts @@ -0,0 +1,9 @@ +import {} from 'react' +import { Interpolation } from '@emotion/serialize' +import { Theme } from '.' + +declare module 'react' { + interface Attributes { + css?: Interpolation + } +} diff --git a/packages/react/types/tsconfig.json b/packages/react/types/tsconfig.json index ea6734342..a50fb726d 100644 --- a/packages/react/types/tsconfig.json +++ b/packages/react/types/tsconfig.json @@ -3,10 +3,7 @@ "baseUrl": "../", "forceConsistentCasingInFileNames": true, "jsx": "react", - "lib": [ - "es6", - "dom" - ], + "lib": ["es6", "dom"], "module": "commonjs", "noEmit": true, "noImplicitAny": true, @@ -15,13 +12,9 @@ "strictNullChecks": true, "strictFunctionTypes": true, "target": "es5", - "typeRoots": [ - "../" - ], + "typeRoots": ["../"], "types": [] }, - "include": [ - "./*.ts", - "./*.tsx" - ] + "include": ["./*.ts", "./*.tsx"], + "exclude": ["./css-prop.d.ts"] } From 09afe5a6e77033e8d71643abee4db78878b80dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 27 Jul 2020 16:01:12 +0200 Subject: [PATCH 4/8] Fixed TSLint errors --- packages/react/types/index.d.ts | 2 +- packages/react/types/tslint.json | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react/types/index.d.ts b/packages/react/types/index.d.ts index e265ceec2..7ee1cc65d 100644 --- a/packages/react/types/index.d.ts +++ b/packages/react/types/index.d.ts @@ -108,7 +108,7 @@ type ReactJSXIntrinsicElements = JSX.IntrinsicElements export const jsx: typeof createElement export namespace jsx { - export namespace JSX { + namespace JSX { interface Element extends ReactJSXElement {} interface ElementClass extends ReactJSXElementClass {} interface ElementAttributesProperty diff --git a/packages/react/types/tslint.json b/packages/react/types/tslint.json index 397bc8639..e082dbab9 100644 --- a/packages/react/types/tslint.json +++ b/packages/react/types/tslint.json @@ -20,6 +20,8 @@ ], "no-null-undefined-union": false, "no-object-literal-type-assertion": false, - "no-unnecessary-generics": false + "no-unnecessary-generics": false, + "no-empty-interface": false, + "strict-export-declare-modifiers": false } } From 89f8daca736a5d0ef53915d6d8a2e179a97dc5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 27 Jul 2020 20:31:13 +0200 Subject: [PATCH 5/8] Fix false positive dtslint errors --- packages/react/types/index.d.ts | 2 +- packages/react/types/tests.tsx | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/react/types/index.d.ts b/packages/react/types/index.d.ts index 7ee1cc65d..7c8e104c4 100644 --- a/packages/react/types/index.d.ts +++ b/packages/react/types/index.d.ts @@ -1,5 +1,5 @@ // Definitions by: Junyoung Clare Jang -// TypeScript Version: 3.1 +// TypeScript Version: 3.2 import { EmotionCache } from '@emotion/cache' import { diff --git a/packages/react/types/tests.tsx b/packages/react/types/tests.tsx index 472d9089c..ea7b99471 100644 --- a/packages/react/types/tests.tsx +++ b/packages/react/types/tests.tsx @@ -157,22 +157,14 @@ const anim1 = keyframes` return null } + // TS@next reports an error on a different line, so this has to be in a single line so `test:typescript` can validate this on all TS versions correctly // $ExpectError - ; + ; const MemoedCompWithoutClassNameSupport = React.memo( CompWithoutClassNameSupport ) + // TS@next reports an error on a different line, so this has to be in a single line so `test:typescript` can validate this on all TS versions correctly // $ExpectError - ; + ; } From 086de3b28659178d6e68f205e2e467ce4e8ea96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 28 Jul 2020 08:48:00 +0200 Subject: [PATCH 6/8] Do not allow css prop on props-less components --- packages/react/types/index.d.ts | 8 ++++---- packages/react/types/tests.tsx | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/react/types/index.d.ts b/packages/react/types/index.d.ts index 7c8e104c4..d11bfc343 100644 --- a/packages/react/types/index.d.ts +++ b/packages/react/types/index.d.ts @@ -92,8 +92,8 @@ export interface ClassNamesProps { */ export function ClassNames(props: ClassNamesProps): ReactElement -type WithConditionalCssProp

= P extends { className?: string } - ? P & { css?: Interpolation } +type WithConditionalCSSProp

= 'className' extends keyof P + ? (P extends { className?: string } ? P & { css?: Interpolation } : P) : P // unpack all here to avoid infinite self-referencing when defining our own JSX namespace @@ -117,8 +117,8 @@ export namespace jsx { extends ReactJSXElementChildrenAttribute {} type LibraryManagedAttributes = C extends React.ComponentType - ? WithConditionalCssProp - : WithConditionalCssProp> + ? WithConditionalCSSProp + : WithConditionalCSSProp> interface IntrinsicAttributes extends ReactJSXIntrinsicAttributes {} interface IntrinsicClassAttributes diff --git a/packages/react/types/tests.tsx b/packages/react/types/tests.tsx index ea7b99471..bbfa743b8 100644 --- a/packages/react/types/tests.tsx +++ b/packages/react/types/tests.tsx @@ -168,3 +168,11 @@ const anim1 = keyframes` // $ExpectError ; } + +{ + const CompWithoutProps = (_props: {}) => { + return null + } + // $ExpectError + ; +} From 3a7fc3d1f90ac7511d73c2a56cb757970a5dc355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 30 Jul 2020 21:00:09 +0200 Subject: [PATCH 7/8] add changeset --- .changeset/moody-ravens-deny.md | 13 +++++++++++++ .changeset/tidy-ghosts-smile.md | 5 ----- 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 .changeset/moody-ravens-deny.md delete mode 100644 .changeset/tidy-ghosts-smile.md diff --git a/.changeset/moody-ravens-deny.md b/.changeset/moody-ravens-deny.md new file mode 100644 index 000000000..3e633b788 --- /dev/null +++ b/.changeset/moody-ravens-deny.md @@ -0,0 +1,13 @@ +--- +'@emotion/react': patch +--- + +The way in which we provide TypeScript support for `css` prop has changed. Based on usage of our JSX pragma we are able to add support for `css` prop only for components that support `className` prop (as our `jsx` factory function takes provided `css` prop, resolves it and pass the generated `className` to the rendered component). This has been implemented using technique described [here](https://www.typescriptlang.org/docs/handbook/jsx.html#factory-functions). What is important - we no longer extend any global interfaces, so people shouldn't bump anymore into type conflicts for the `css` prop when using different libraries with the `css` prop support, such as `styled-components`. + +However, it's not possible to leverage `css` prop support being added conditionally based on a type of rendered component when one is not using our JSX pragma. For those cases when people use our pragma implicitly (for example when using our `@emotion/babel-preset-css-prop`) we have added special file that can be imported once to add support for the `css` prop globally, for all components. Use it like this: + +```ts +import {} from '@emotion/react/types/css-prop' +``` + +In this particular case we are forced to extend the existing `React.Attributes` interface. Previously we've been extending both `React.DOMAttributes` and `JSX.IntrinsicAttributes`. This change is really minor and shouldn't affect any consuming code. diff --git a/.changeset/tidy-ghosts-smile.md b/.changeset/tidy-ghosts-smile.md deleted file mode 100644 index 2f49b50af..000000000 --- a/.changeset/tidy-ghosts-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@emotion/react': patch ---- - -Changed the interface to which support for `css` prop is being added. It's now `React.Attributes` instead of `React.DOMAttributes` and `JSX.IntrinsicAttributes`. This change is really minor and shouldn't affect any consuming code. From ad1a84d513ac627b7121d77b0a18fef95bf05571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 30 Jul 2020 21:04:45 +0200 Subject: [PATCH 8/8] Add short docs for css prop + TS --- .changeset/moody-ravens-deny.md | 4 ++-- docs/typescript.mdx | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/moody-ravens-deny.md b/.changeset/moody-ravens-deny.md index 3e633b788..15cd7545e 100644 --- a/.changeset/moody-ravens-deny.md +++ b/.changeset/moody-ravens-deny.md @@ -2,9 +2,9 @@ '@emotion/react': patch --- -The way in which we provide TypeScript support for `css` prop has changed. Based on usage of our JSX pragma we are able to add support for `css` prop only for components that support `className` prop (as our `jsx` factory function takes provided `css` prop, resolves it and pass the generated `className` to the rendered component). This has been implemented using technique described [here](https://www.typescriptlang.org/docs/handbook/jsx.html#factory-functions). What is important - we no longer extend any global interfaces, so people shouldn't bump anymore into type conflicts for the `css` prop when using different libraries with the `css` prop support, such as `styled-components`. +The way in which we provide TypeScript support for `css` prop has changed. Based on usage of our jsx pragma we are able to add support for `css` prop only for components that support `className` prop (as our `jsx` factory function takes provided `css` prop, resolves it and pass the generated `className` to the rendered component). This has been implemented using technique described [here](https://www.typescriptlang.org/docs/handbook/jsx.html#factory-functions). What is important - we no longer extend any global interfaces, so people shouldn't bump anymore into type conflicts for the `css` prop when using different libraries with the `css` prop support, such as `styled-components`. -However, it's not possible to leverage `css` prop support being added conditionally based on a type of rendered component when one is not using our JSX pragma. For those cases when people use our pragma implicitly (for example when using our `@emotion/babel-preset-css-prop`) we have added special file that can be imported once to add support for the `css` prop globally, for all components. Use it like this: +However, it's not possible to leverage `css` prop support being added conditionally based on a type of rendered component when one is not using our jsx pragma. For those cases when people use our pragma implicitly (for example when using our `@emotion/babel-preset-css-prop`) we have added special file that can be imported once to add support for the `css` prop globally, for all components. Use it like this: ```ts import {} from '@emotion/react/types/css-prop' diff --git a/docs/typescript.mdx b/docs/typescript.mdx index 8388cbcae..de03d7180 100644 --- a/docs/typescript.mdx +++ b/docs/typescript.mdx @@ -55,6 +55,16 @@ import { css } from '@emotion'

``` +### `css` prop + +When using our jsx pragma the support for `css` prop is being added only for components that accepts `className` prop, as our `jsx` factory function takes provided `css` prop, resolves it and pass the generated `className` to the rendered component. + +However, it's not possible to leverage `css` prop support being added conditionally based on a type of rendered component when one is not using our jsx pragma. For those cases when people use our pragma implicitly (for example when using our `@emotion/babel-preset-css-prop`) we have a special file that can be imported once to add support for the `css` prop globally, for all components. Use it like this: + +```ts +import {} from '@emotion/react/types/css-prop' +``` + ## @emotion/styled ### HTML/SVG elements