Skip to content

Commit f6a3e4b

Browse files
committed
docs(dom): explain element classifiers
1 parent bc50065 commit f6a3e4b

6 files changed

Lines changed: 128 additions & 5 deletions

File tree

‎docs/content/docs/api-surface.mdx‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,33 @@ let element: WebAPI.Element.t = WebAPI.Document.createElement("div")
287287
let node = element->WebAPI.Element.asNode
288288
```
289289

290+
### Classifying DOM values
291+
292+
Event libraries and browser APIs sometimes return a type that is broader than the value present at
293+
runtime. A React form event target, for example, does not tell ReScript whether the target is an
294+
input, select, or another element. Calling an input-specific API then requires narrowing that value.
295+
296+
Use `classify` instead of repeating an `instanceof` binding and an unchecked `Obj.magic` cast. The
297+
classifier performs the runtime check and returns the correctly typed value as an `option`.
298+
299+
```ReScript
300+
let checkInput = target =>
301+
switch target->WebAPI.HTMLInputElement.classify {
302+
| Some(input) => input->WebAPI.HTMLInputElement.checkValidity
303+
| None => false
304+
}
305+
```
306+
307+
The classifier parameter is polymorphic, so a library-owned target can be passed directly without an
308+
identity cast to a WebAPI type first. `Element.classify`, `Document.classify`,
309+
`HTMLElement.classify`, and `HTMLInputElement.classify` follow the same contract. Their matching
310+
`isInstanceOf` functions are available when only a boolean check is needed.
311+
312+
Classification uses the corresponding constructor from `globalThis`. It returns `None` rather than
313+
raising when that constructor is unavailable in a server or worker environment. Like JavaScript
314+
`instanceof`, it only recognizes values created in the current realm; values from another window or
315+
iframe may not match. Enable the `WebAPI.HTML` feature when using the HTML-specific classifiers.
316+
290317
## Visual Viewport
291318

292319
`Window.visualViewport` returns a nullable `WebAPI.VisualViewport.t`.

‎docs/content/docs/philosophy.mdx‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,19 @@ let element: WebAPI.Element.t = WebAPI.Document.createElement("div")
5353
let node = element->WebAPI.Element.asNode
5454
```
5555

56-
Any other conversions should be treated as unsafe casts and used with caution, because the type system cannot guarantee they are valid at runtime.
56+
Converting in the other direction requires proving the runtime type. Broad DOM values commonly come
57+
from event libraries, selector APIs, or JavaScript bindings that cannot describe the concrete
58+
interface statically. Use the destination module's `classify` function for that downcast instead of
59+
calling `Obj.magic` directly.
60+
61+
```ReScript
62+
switch target->WebAPI.HTMLInputElement.classify {
63+
| Some(input) => input->WebAPI.HTMLInputElement.checkValidity
64+
| None => false
65+
}
66+
```
67+
68+
Classifiers return an `option` because a runtime value may not implement the requested interface.
69+
They also return `None` when the browser constructor is unavailable. The checked cast remains inside
70+
the binding, so application code handles absence explicitly and receives the concrete public `t`
71+
type only after a successful check.

‎src/DOM/Document.res‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,29 @@ Creates an instance of the element for the specified tag.
2828
@scope("globalThis.document")
2929
external createElement: (string, ~options: string=?) => Element.t = "createElement"
3030

31+
/**
32+
`isInstanceOf(value)`
33+
34+
Returns whether `value` is a `Document` created in the current JavaScript realm.
35+
36+
This is a runtime check. It returns `false` when `globalThis.Document` is unavailable, such as in
37+
some server or worker environments.
38+
*/
3139
let isInstanceOf = (_: 'value): bool =>
3240
%raw(`typeof globalThis.Document === "function" && param instanceof globalThis.Document`)
3341

3442
/**
35-
Returns the value as a Document when it is a Document in the current realm, and None otherwise.
43+
`classify(value)`
44+
45+
Safely narrows a value from a broad DOM or library type to `Document.t`.
46+
47+
Use this when a JavaScript binding or union-like API can contain a document but cannot express that
48+
concrete type statically. Returns `Some(document)` when the value is a `Document` in the current
49+
realm, and `None` when it is not or when the `Document` constructor is unavailable.
50+
51+
```res
52+
let document = value->Document.classify
53+
```
3654
*/
3755
let classify = (value: 'value): option<t> =>
3856
if value->isInstanceOf {

‎src/DOM/Element.res‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,32 @@ Returns true if element has an attribute whose qualified name is qualifiedName,
3030

3131
include Impl({type t = t})
3232

33+
/**
34+
`isInstanceOf(value)`
35+
36+
Returns whether `value` is an `Element` created in the current JavaScript realm.
37+
38+
This is a runtime check. It returns `false` when `globalThis.Element` is unavailable, such as in
39+
some server or worker environments.
40+
*/
3341
let isInstanceOf = (_: 'value): bool =>
3442
%raw(`typeof globalThis.Element === "function" && param instanceof globalThis.Element`)
3543

3644
/**
37-
Returns the value as an Element when it is an Element in the current realm, and None otherwise.
45+
`classify(value)`
46+
47+
Safely narrows a value from a broad DOM or library type to `Element.t`.
48+
49+
Use this when an event target, JavaScript binding, or union-like API cannot express its concrete DOM
50+
type statically. Returns `Some(element)` when the value is an `Element` in the current realm, and
51+
`None` when it is not or when the `Element` constructor is unavailable.
52+
53+
```res
54+
switch value->Element.classify {
55+
| Some(element) => element->Element.hasAttribute("data-ready")
56+
| None => false
57+
}
58+
```
3859
*/
3960
let classify = (value: 'value): option<t> =>
4061
if value->isInstanceOf {

‎src/HTML/HTMLElement.res‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,32 @@ module Impl = (
5454

5555
include Impl({type t = t})
5656

57+
/**
58+
`isInstanceOf(value)`
59+
60+
Returns whether `value` is an `HTMLElement` created in the current JavaScript realm.
61+
62+
This is a runtime check. It returns `false` when `globalThis.HTMLElement` is unavailable, such as in
63+
some server or worker environments.
64+
*/
5765
let isInstanceOf = (_: 'value): bool =>
5866
%raw(`typeof globalThis.HTMLElement === "function" && param instanceof globalThis.HTMLElement`)
5967

6068
/**
61-
Returns the value as an HTMLElement when it is an HTMLElement in the current realm, and None otherwise.
69+
`classify(value)`
70+
71+
Safely narrows a value from a broad DOM or library type to `HTMLElement.t`.
72+
73+
Use this when an event target or element-returning API does not distinguish HTML elements from SVG
74+
or other element kinds. Returns `Some(element)` for an `HTMLElement` in the current realm, and
75+
`None` when the value is not an HTML element or when the `HTMLElement` constructor is unavailable.
76+
77+
```res
78+
switch value->HTMLElement.classify {
79+
| Some(element) => element->HTMLElement.focus
80+
| None => ()
81+
}
82+
```
6283
*/
6384
let classify = (value: 'value): option<t> =>
6485
if value->isInstanceOf {

‎src/HTML/HTMLInputElement.res‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,32 @@ type t = DOM.htmlInputElement = private {...DOM.htmlInputElement}
22

33
include HTMLElement.Impl({type t = t})
44

5+
/**
6+
`isInstanceOf(value)`
7+
8+
Returns whether `value` is an `HTMLInputElement` created in the current JavaScript realm.
9+
10+
This is a runtime check. It returns `false` when `globalThis.HTMLInputElement` is unavailable, such
11+
as in some server or worker environments.
12+
*/
513
let isInstanceOf = (_: 'value): bool =>
614
%raw(`typeof globalThis.HTMLInputElement === "function" && param instanceof globalThis.HTMLInputElement`)
715

816
/**
9-
Returns the value as an HTMLInputElement when it is an HTMLInputElement in the current realm, and None otherwise.
17+
`classify(value)`
18+
19+
Safely narrows a value from a broad event target or element type to `HTMLInputElement.t`.
20+
21+
Use this for values such as React form event targets, which are typed more broadly than the input
22+
element that emitted the event. Returns `Some(input)` for an `HTMLInputElement` in the current realm,
23+
and `None` when the value is not an input or when the `HTMLInputElement` constructor is unavailable.
24+
25+
```res
26+
switch target->HTMLInputElement.classify {
27+
| Some(input) => input->HTMLInputElement.checkValidity
28+
| None => false
29+
}
30+
```
1031
*/
1132
let classify = (value: 'value): option<t> =>
1233
if value->isInstanceOf {

0 commit comments

Comments
 (0)