If you desire to ensure that in cases where top
is not present, then offsetTop
should also be absent, you can define that part of the type as a union like so:
type Top =
{ top: number, offsetTop?: number } |
{ top?: never, offsetTop?: never };
In TypeScript, there isn't a direct way to prevent a property key in an object type, but you can come close by specifying that the key is an optional property whose value is the impossible never
type. Since there are no actual values of type never
, the most logical thing to do is to exclude the property (or perhaps set it to undefined
). So, Top
either has a top
property of type number
and an optional offsetProperty
also of type number
, or it lacks both a top
and an offsetTop
property.
Similarly for the left
/offsetLeft
pair:
type Left =
{ left: number, offsetLeft?: number } |
{ left?: never, offsetLeft?: never };
By combining those with Base
, you can create Test
:
interface Base {
behavior?: "auto" | "smooth"
}
type Test = Base & Top & Left;
As Test
represents the intersection of Base
, Top
, and Left
, a value of type Test
must adhere to all three definitions.
Let's put it to the test:
let t: Test;
t = { behavior: "auto", top: 1 }; //valid
t = { top: 1, offsetTop: 2 }; // valid
t = { top: 1, offsetTop: 2, left: 3 }; // valid
t = { top: 1, offsetTop: 2, left: 3, offsetLeft: 4 }; // valid
t = { left: 3 }; // valid
t = { left: 3, offsetLeft: 4 }; // valid
t = { offsetTop: 2 }; // error
t = { offsetLeft: 4 }; // error
t = { top: 1, offsetTop: 2, offsetLeft: 4 }; // error
It seems to be working correctly. The compiler accepts valid values while rejecting invalid ones that contain an offsetTop
without a top
or an offsetLeft
without a left
.
Playground link to code