In most cases, it’s a JavaScript Object (JSO).
type PointLike = {
x: number
y: number
}
class Point {
constructor(
pointLike: PointLike
) {
/* do nothing */
}
}
const point = new Point({
x: 4.2,
y: 2.7
})
@JsPlainObject
sealed external interface PointLike {
var x: Float
var y: Float
}
class Point(pointLike: PointLike) {
val x: Float = pointLike.x
val y: Float = pointLike.y
}
val pointLike: PointLike = jso {
x = 4.2
y = 2.7
}
val point = Point(pointLike)
Typical JSO example:
EventInit / ProgressEventInit,
Options / Options usage / ConstructorOptions,
Props / SmartTabsProps
JSO deviates from other things in the absence of methods (JSO has only mutable properties - var
s,
after Kotlin 2.0 - only val
s)
We mark such interfaces with an annotation @JsPlainObject
- work in progress.
If the interface is marked, it’s definitely a JSO.
There also is an annotation @JsExternalInheritorsOnly
(e.g. React Props) that requires all its children interfaces,
classes, and objects to be external as well.
Here is an example:
val instance = jso {
foo = "bar"
}
JS equivalent:
const instance = {
foo: "bar",
}
First of all, unsafeCast
and asDynamic
are not recommended for external declarations, unless you are a
declarations' owner.
Some types' instances cannot be created by constructor invocation or interface implementation. For such types we
provide strict factory functions. It is a responsibility of library authors.
Example:
sealed external interface ClassName
inline fun ClassName(
value: String,
): ClassName =
unsafeCast(value)
val value = ClassName("my-class")
If there is no strict factory function for a type of this kind
please create an issue.
Otherwise, when you use unsafeCast
or asDynamic
, the type can be incorrect when the library updates.
Typical use cases are:
JSO
(look at the previous item for JSO creation example)- Opaque alias
Opaque alias is the interface that mimics another external interface but encapsulates some logic inside (e.g., a
String
opaque interface without any string operations).
There are two cases:
For example, Playwright uses "data-testid" attribute by default, so we can develop an exemplifying extension property upon that.
- If the attribute is not specific to the
HTMLElement
- common for everyHTMLElement
, you need to create an extension function.
var HTMLAttributes<*>.dataTestId: String?
get() = asDynamic()["data-testid"]
set(value) {
asDynamic()["data-testId"] = value
}
- Otherwise, use Element’s props interface as a receiver.
For example, if an element isHTMLInputElement
, its props interface isInputHTMLAttributes
, therefore it becomes a receiver:
var InputHTMLAttributes<*>.dataTestId: String?
get() = asDynamic()["data-testid"]
set(value) {
asDynamic()["data-testId"] = value
}
}