Typescript - how to combine Union and Intersection types
In your example there are 2 problems that have to be solved and both stem from the same "issue" (feature).
In Typescript, the following doesn't work as we would sometimes want:
interface A {
a?: string;
}
interface B {
b?: string;
}
const x: A|B = {a: 'a', b: 'b'}; //works
What you want is to explicitly exclude B from A, and A from B - so that they can't appear together.
This question discusses the "XOR"ing of types, and suggests using the package ts-xor, or writing your own. Here's the example from an answer there (same code is used in ts-xor):
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
Now, with this we can finally solve your problem:
interface A {
a?: string;
}
interface B {
b?: string;
}
interface C {
c?: string;
}
type CombinationProps = XOR<XOR<A, B>, C>;
let c: CombinationProps;
c = {}
c = {a: 'a'}
c = {b: 'b'}
c = {c: 'c'}
c = {a: 'a', b: 'b'} // error
c = {b: 'b', c: 'c'} // error
c = {a: 'a', c: 'c'} // error
c = {a: 'a', b: 'b', c: 'c'} // error
More specifically, your types will be:
interface A {a?: string;}
interface B {b?: string;}
type CombinationProps = XOR<A, B>;
type ButtonProps = {tag: Tags.button} & JSX.IntrinsicElements['button'];
type AnchorProps = {tag: Tags.a} & JSX.IntrinsicElements['a'];
type InputProps = {tag: Tags.input} & JSX.IntrinsicElements['input'];
type Props = CombinationProps & XOR<XOR<ButtonProps,AnchorProps>, InputProps>;