Skip to content

Commit 83a0ce6

Browse files
committed
docs(dom): explain element classifiers
1 parent 75482fd commit 83a0ce6

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
@@ -300,6 +300,33 @@ let element = document->WebAPI.Document.createElement("div")
300300
let node = element->WebAPI.Element.asNode
301301
```
302302

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

305332
`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
@@ -49,4 +49,19 @@ let element: WebAPI.Element.t = document->WebAPI.Document.createElement("div")
4949
let node: WebAPI.Node.t = element->WebAPI.Element.asNode
5050
```
5151

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

‎src/dom-nodes/Element.res‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,11 +506,32 @@ Returns true if qualifiedName is now present, and false otherwise.
506506

507507
include Impl({type t = t})
508508

509+
/**
510+
`isInstanceOf(value)`
511+
512+
Returns whether `value` is an `Element` created in the current JavaScript realm.
513+
514+
This is a runtime check. It returns `false` when `globalThis.Element` is unavailable, such as in
515+
some server or worker environments.
516+
*/
509517
let isInstanceOf = (_: 'value): bool =>
510518
%raw(`typeof globalThis.Element === "function" && param instanceof globalThis.Element`)
511519

512520
/**
513-
Returns the value as an Element when it is an Element in the current realm, and None otherwise.
521+
`classify(value)`
522+
523+
Safely narrows a value from a broad DOM or library type to `Element.t`.
524+
525+
Use this when an event target, JavaScript binding, or union-like API cannot express its concrete DOM
526+
type statically. Returns `Some(element)` when the value is an `Element` in the current realm, and
527+
`None` when it is not or when the `Element` constructor is unavailable.
528+
529+
```res
530+
switch value->Element.classify {
531+
| Some(element) => element->Element.hasAttribute("data-ready")
532+
| None => false
533+
}
534+
```
514535
*/
515536
let classify = (value: 'value): option<t> =>
516537
if value->isInstanceOf {

‎src/html/HTMLElement.res‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,32 @@ rather than relying on coercion.
107107

108108
include Impl({type t = t})
109109

110+
/**
111+
`isInstanceOf(value)`
112+
113+
Returns whether `value` is an `HTMLElement` created in the current JavaScript realm.
114+
115+
This is a runtime check. It returns `false` when `globalThis.HTMLElement` is unavailable, such as in
116+
some server or worker environments.
117+
*/
110118
let isInstanceOf = (_: 'value): bool =>
111119
%raw(`typeof globalThis.HTMLElement === "function" && param instanceof globalThis.HTMLElement`)
112120

113121
/**
114-
Returns the value as an HTMLElement when it is an HTMLElement in the current realm, and None otherwise.
122+
`classify(value)`
123+
124+
Safely narrows a value from a broad DOM or library type to `HTMLElement.t`.
125+
126+
Use this when an event target or element-returning API does not distinguish HTML elements from SVG
127+
or other element kinds. Returns `Some(element)` for an `HTMLElement` in the current realm, and
128+
`None` when the value is not an HTML element or when the `HTMLElement` constructor is unavailable.
129+
130+
```res
131+
switch value->HTMLElement.classify {
132+
| Some(element) => element->HTMLElement.focus
133+
| None => ()
134+
}
135+
```
115136
*/
116137
let classify = (value: 'value): option<t> =>
117138
if value->isInstanceOf {

‎src/html/HTMLInputElement.res‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,32 @@ type t = {
230230

231231
include HTMLElement.Impl({type t = t})
232232

233+
/**
234+
`isInstanceOf(value)`
235+
236+
Returns whether `value` is an `HTMLInputElement` created in the current JavaScript realm.
237+
238+
This is a runtime check. It returns `false` when `globalThis.HTMLInputElement` is unavailable, such
239+
as in some server or worker environments.
240+
*/
233241
let isInstanceOf = (_: 'value): bool =>
234242
%raw(`typeof globalThis.HTMLInputElement === "function" && param instanceof globalThis.HTMLInputElement`)
235243

236244
/**
237-
Returns the value as an HTMLInputElement when it is an HTMLInputElement in the current realm, and None otherwise.
245+
`classify(value)`
246+
247+
Safely narrows a value from a broad event target or element type to `HTMLInputElement.t`.
248+
249+
Use this for values such as React form event targets, which are typed more broadly than the input
250+
element that emitted the event. Returns `Some(input)` for an `HTMLInputElement` in the current realm,
251+
and `None` when the value is not an input or when the `HTMLInputElement` constructor is unavailable.
252+
253+
```res
254+
switch target->HTMLInputElement.classify {
255+
| Some(input) => Some(input.value)
256+
| None => None
257+
}
258+
```
238259
*/
239260
let classify = (value: 'value): option<t> =>
240261
if value->isInstanceOf {

‎src/window/Document.res‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,11 +460,29 @@ external hasStorageAccess: DOM.document => promise<bool> = "hasStorageAccess"
460460
@send
461461
external requestStorageAccess: DOM.document => promise<unit> = "requestStorageAccess"
462462

463+
/**
464+
`isInstanceOf(value)`
465+
466+
Returns whether `value` is a `Document` created in the current JavaScript realm.
467+
468+
This is a runtime check. It returns `false` when `globalThis.Document` is unavailable, such as in
469+
some server or worker environments.
470+
*/
463471
let isInstanceOf = (_: 'value): bool =>
464472
%raw(`typeof globalThis.Document === "function" && param instanceof globalThis.Document`)
465473

466474
/**
467-
Returns the value as a Document when it is a Document in the current realm, and None otherwise.
475+
`classify(value)`
476+
477+
Safely narrows a value from a broad DOM or library type to `DOM.document`.
478+
479+
Use this when a JavaScript binding or union-like API can contain a document but cannot express that
480+
concrete type statically. Returns `Some(document)` when the value is a `Document` in the current
481+
realm, and `None` when it is not or when the `Document` constructor is unavailable.
482+
483+
```res
484+
let document = value->Document.classify
485+
```
468486
*/
469487
let classify = (value: 'value): option<DOM.document> =>
470488
if value->isInstanceOf {

0 commit comments

Comments
 (0)