Skip to content

Latest commit

 

History

History
137 lines (108 loc) · 4.49 KB

FAQ.md

File metadata and controls

137 lines (108 loc) · 4.49 KB

Frequently Asked Questions

Base

Q: sealed external interface. What is it?
A: 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 - vars, after Kotlin 2.0 - only vals)
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.

Q: How to create JSO instances?
A: Here is an example:

val instance = jso {
    foo = "bar"
}

JS equivalent:

const instance = {
  foo: "bar",
}

Q: Why should I avoid unsafeCast and asDynamic when working with Wrappers? A: 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 =
    value.unsafeCast<ClassName>()

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).

React

Q: How to add data attribute to HTMLElement’s properties? A: There are two cases:

For example, Playwright uses "data-testid" attribute by default, so we can develop an exemplifying extension property upon that.

  1. If the attribute is not specific to the HTMLElement - common for every HTMLElement, you need to create an extension function.
var HTMLAttributes<*>.dataTestId: String?
    get() = asDynamic()["data-testid"]
    set(value) {
        asDynamic()["data-testId"] = value
    }
  1. Otherwise, use Element’s props interface as a receiver.
    For example, if an element is HTMLInputElement, its props interface is InputHTMLAttributes, therefore it becomes a receiver:
var InputHTMLAttributes<*>.dataTestId: String?
    get() = asDynamic()["data-testid"]
    set(value) {
        asDynamic()["data-testId"] = value
    }
}