From 75482fd15c49e2d6a423c35825771e130cca1ca0 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 18 Sep 2026 07:13:19 -0400 Subject: [PATCH 1/3] feat(dom): add safe element classifiers --- src/dom-nodes/Element.res | 13 ++++++++++++- src/html/HTMLElement.res | 13 +++++++++++++ src/html/HTMLInputElement.res | 13 +++++++++++++ src/window/Document.res | 13 ++++++++++++- tests/DOMAPI/Classify__test.res | 33 +++++++++++++++++++++++++++++++++ tests/index.js | 1 + 6 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/DOMAPI/Classify__test.res diff --git a/src/dom-nodes/Element.res b/src/dom-nodes/Element.res index 3ab0a09f..35c44a0e 100644 --- a/src/dom-nodes/Element.res +++ b/src/dom-nodes/Element.res @@ -506,4 +506,15 @@ Returns true if qualifiedName is now present, and false otherwise. include Impl({type t = t}) -let isInstanceOf = (_: 't): bool => %raw(`param instanceof Element`) +let isInstanceOf = (_: 'value): bool => + %raw(`typeof globalThis.Element === "function" && param instanceof globalThis.Element`) + +/** +Returns the value as an Element when it is an Element in the current realm, and None otherwise. +*/ +let classify = (value: 'value): option => + if value->isInstanceOf { + Some(Obj.magic(value)) + } else { + None + } diff --git a/src/html/HTMLElement.res b/src/html/HTMLElement.res index 25c513b7..6b87a9f8 100644 --- a/src/html/HTMLElement.res +++ b/src/html/HTMLElement.res @@ -106,3 +106,16 @@ rather than relying on coercion. } include Impl({type t = t}) + +let isInstanceOf = (_: 'value): bool => + %raw(`typeof globalThis.HTMLElement === "function" && param instanceof globalThis.HTMLElement`) + +/** +Returns the value as an HTMLElement when it is an HTMLElement in the current realm, and None otherwise. +*/ +let classify = (value: 'value): option => + if value->isInstanceOf { + Some(Obj.magic(value)) + } else { + None + } diff --git a/src/html/HTMLInputElement.res b/src/html/HTMLInputElement.res index d90e5fbd..32e0e6a2 100644 --- a/src/html/HTMLInputElement.res +++ b/src/html/HTMLInputElement.res @@ -230,6 +230,19 @@ type t = { include HTMLElement.Impl({type t = t}) +let isInstanceOf = (_: 'value): bool => + %raw(`typeof globalThis.HTMLInputElement === "function" && param instanceof globalThis.HTMLInputElement`) + +/** +Returns the value as an HTMLInputElement when it is an HTMLInputElement in the current realm, and None otherwise. +*/ +let classify = (value: 'value): option => + if value->isInstanceOf { + Some(Obj.magic(value)) + } else { + None + } + /** Increments a range input control's value by the value given by the Step attribute. If the optional parameter is used, will increment the input control's value by that value. @param n Value to increment the value by. diff --git a/src/window/Document.res b/src/window/Document.res index b38bcab3..4908a402 100644 --- a/src/window/Document.res +++ b/src/window/Document.res @@ -460,7 +460,18 @@ external hasStorageAccess: DOM.document => promise = "hasStorageAccess" @send external requestStorageAccess: DOM.document => promise = "requestStorageAccess" -let isInstanceOf = (_: 't): bool => %raw(`param instanceof Document`) +let isInstanceOf = (_: 'value): bool => + %raw(`typeof globalThis.Document === "function" && param instanceof globalThis.Document`) + +/** +Returns the value as a Document when it is a Document in the current realm, and None otherwise. +*/ +let classify = (value: 'value): option => + if value->isInstanceOf { + Some(Obj.magic(value)) + } else { + None + } /** Returns the Location associated with this document, which provides information about the current URL and methods for navigating to another URL. diff --git a/tests/DOMAPI/Classify__test.res b/tests/DOMAPI/Classify__test.res new file mode 100644 index 00000000..296edcc9 --- /dev/null +++ b/tests/DOMAPI/Classify__test.res @@ -0,0 +1,33 @@ +%%raw(` +globalThis.Element = class Element {} +globalThis.Document = class Document {} +globalThis.HTMLElement = class HTMLElement extends globalThis.Element {} +globalThis.HTMLInputElement = class HTMLInputElement extends globalThis.HTMLElement { + checkValidity() { + return true + } +} +`) + +let element: unknown = %raw(`new globalThis.Element()`) +let document: unknown = %raw(`new globalThis.Document()`) +let htmlElement: unknown = %raw(`new globalThis.HTMLElement()`) +let input: unknown = %raw(`new globalThis.HTMLInputElement()`) + +assert(element->Element.classify->Option.isSome) +assert(document->Document.classify->Option.isSome) +assert(htmlElement->HTMLElement.classify->Option.isSome) +assert(input->HTMLInputElement.classify->Option.isSome) + +assert(input->Element.classify->Option.isSome) +assert(input->HTMLElement.classify->Option.isSome) +assert(document->Element.classify->Option.isNone) +assert(htmlElement->HTMLInputElement.classify->Option.isNone) + +input +->HTMLInputElement.classify +->Option.forEach(input => assert(input->HTMLInputElement.checkValidity)) + +%%raw(`delete globalThis.HTMLInputElement`) + +assert(input->HTMLInputElement.classify->Option.isNone) diff --git a/tests/index.js b/tests/index.js index 9d73db6a..c989c737 100644 --- a/tests/index.js +++ b/tests/index.js @@ -12,6 +12,7 @@ const rescriptConfig = JSON.parse(fs.readFileSync(path.join(repoRoot, "rescript. const compiledSuffix = rescriptConfig.suffix ?? ".js"; const runtimeTests = [ + "DOMAPI/Classify__test.res", "FetchAPI/Headers__test.res", "FetchAPI/Request__test.res", "FetchAPI/Response__test.res", From 83a0ce6969626daa20f9f62eda329692b39a3645 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 18 Sep 2026 07:20:59 -0400 Subject: [PATCH 2/3] docs(dom): explain element classifiers --- docs/content/docs/api-surface.mdx | 27 +++++++++++++++++++++++++++ docs/content/docs/philosophy.mdx | 17 ++++++++++++++++- src/dom-nodes/Element.res | 23 ++++++++++++++++++++++- src/html/HTMLElement.res | 23 ++++++++++++++++++++++- src/html/HTMLInputElement.res | 23 ++++++++++++++++++++++- src/window/Document.res | 20 +++++++++++++++++++- 6 files changed, 128 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/api-surface.mdx b/docs/content/docs/api-surface.mdx index cc80575a..0cbcb022 100644 --- a/docs/content/docs/api-surface.mdx +++ b/docs/content/docs/api-surface.mdx @@ -300,6 +300,33 @@ let element = document->WebAPI.Document.createElement("div") let node = element->WebAPI.Element.asNode ``` +### Classifying DOM values + +Event libraries and browser APIs sometimes return a type that is broader than the value present at +runtime. A React form event target, for example, does not tell ReScript whether the target is an +input, select, or another element. Calling an input-specific API then requires narrowing that value. + +Use `classify` instead of repeating an `instanceof` binding and an unchecked `Obj.magic` cast. The +classifier performs the runtime check and returns the correctly typed value as an `option`. + +```ReScript +let readInputValue = target => + switch target->WebAPI.HTMLInputElement.classify { + | Some(input) => Some(input.value) + | None => None + } +``` + +The classifier parameter is polymorphic, so a library-owned target can be passed directly without an +identity cast to a WebAPI type first. `Element.classify`, `Document.classify`, +`HTMLElement.classify`, and `HTMLInputElement.classify` follow the same contract. Their matching +`isInstanceOf` functions are available when only a boolean check is needed. + +Classification uses the corresponding constructor from `globalThis`. It returns `None` rather than +raising when that constructor is unavailable in a server or worker environment. Like JavaScript +`instanceof`, it only recognizes values created in the current realm; values from another window or +iframe may not match. Enable the `WebAPI.HTML` feature when using the HTML-specific classifiers. + ## Visual Viewport `Window.visualViewport` returns a nullable `WebAPI.VisualViewport.t`. diff --git a/docs/content/docs/philosophy.mdx b/docs/content/docs/philosophy.mdx index 2b52fa65..32a0f623 100644 --- a/docs/content/docs/philosophy.mdx +++ b/docs/content/docs/philosophy.mdx @@ -49,4 +49,19 @@ let element: WebAPI.Element.t = document->WebAPI.Document.createElement("div") let node: WebAPI.Node.t = element->WebAPI.Element.asNode ``` -Any other conversions should be treated as unsafe casts and used with caution, because the type system cannot guarantee they are valid at runtime. +Converting in the other direction requires proving the runtime type. Broad DOM values commonly come +from event libraries, selector APIs, or JavaScript bindings that cannot describe the concrete +interface statically. Use the destination module's `classify` function for that downcast instead of +calling `Obj.magic` directly. + +```ReScript +switch target->WebAPI.HTMLInputElement.classify { +| Some(input) => Some(input.value) +| None => None +} +``` + +Classifiers return an `option` because a runtime value may not implement the requested interface. +They also return `None` when the browser constructor is unavailable. The checked cast remains inside +the binding, so application code handles absence explicitly and receives the concrete public +interface type only after a successful check. diff --git a/src/dom-nodes/Element.res b/src/dom-nodes/Element.res index 35c44a0e..768e384a 100644 --- a/src/dom-nodes/Element.res +++ b/src/dom-nodes/Element.res @@ -506,11 +506,32 @@ Returns true if qualifiedName is now present, and false otherwise. include Impl({type t = t}) +/** +`isInstanceOf(value)` + +Returns whether `value` is an `Element` created in the current JavaScript realm. + +This is a runtime check. It returns `false` when `globalThis.Element` is unavailable, such as in +some server or worker environments. +*/ let isInstanceOf = (_: 'value): bool => %raw(`typeof globalThis.Element === "function" && param instanceof globalThis.Element`) /** -Returns the value as an Element when it is an Element in the current realm, and None otherwise. +`classify(value)` + +Safely narrows a value from a broad DOM or library type to `Element.t`. + +Use this when an event target, JavaScript binding, or union-like API cannot express its concrete DOM +type statically. Returns `Some(element)` when the value is an `Element` in the current realm, and +`None` when it is not or when the `Element` constructor is unavailable. + +```res +switch value->Element.classify { +| Some(element) => element->Element.hasAttribute("data-ready") +| None => false +} +``` */ let classify = (value: 'value): option => if value->isInstanceOf { diff --git a/src/html/HTMLElement.res b/src/html/HTMLElement.res index 6b87a9f8..4f436620 100644 --- a/src/html/HTMLElement.res +++ b/src/html/HTMLElement.res @@ -107,11 +107,32 @@ rather than relying on coercion. include Impl({type t = t}) +/** +`isInstanceOf(value)` + +Returns whether `value` is an `HTMLElement` created in the current JavaScript realm. + +This is a runtime check. It returns `false` when `globalThis.HTMLElement` is unavailable, such as in +some server or worker environments. +*/ let isInstanceOf = (_: 'value): bool => %raw(`typeof globalThis.HTMLElement === "function" && param instanceof globalThis.HTMLElement`) /** -Returns the value as an HTMLElement when it is an HTMLElement in the current realm, and None otherwise. +`classify(value)` + +Safely narrows a value from a broad DOM or library type to `HTMLElement.t`. + +Use this when an event target or element-returning API does not distinguish HTML elements from SVG +or other element kinds. Returns `Some(element)` for an `HTMLElement` in the current realm, and +`None` when the value is not an HTML element or when the `HTMLElement` constructor is unavailable. + +```res +switch value->HTMLElement.classify { +| Some(element) => element->HTMLElement.focus +| None => () +} +``` */ let classify = (value: 'value): option => if value->isInstanceOf { diff --git a/src/html/HTMLInputElement.res b/src/html/HTMLInputElement.res index 32e0e6a2..bee1478a 100644 --- a/src/html/HTMLInputElement.res +++ b/src/html/HTMLInputElement.res @@ -230,11 +230,32 @@ type t = { include HTMLElement.Impl({type t = t}) +/** +`isInstanceOf(value)` + +Returns whether `value` is an `HTMLInputElement` created in the current JavaScript realm. + +This is a runtime check. It returns `false` when `globalThis.HTMLInputElement` is unavailable, such +as in some server or worker environments. +*/ let isInstanceOf = (_: 'value): bool => %raw(`typeof globalThis.HTMLInputElement === "function" && param instanceof globalThis.HTMLInputElement`) /** -Returns the value as an HTMLInputElement when it is an HTMLInputElement in the current realm, and None otherwise. +`classify(value)` + +Safely narrows a value from a broad event target or element type to `HTMLInputElement.t`. + +Use this for values such as React form event targets, which are typed more broadly than the input +element that emitted the event. Returns `Some(input)` for an `HTMLInputElement` in the current realm, +and `None` when the value is not an input or when the `HTMLInputElement` constructor is unavailable. + +```res +switch target->HTMLInputElement.classify { +| Some(input) => Some(input.value) +| None => None +} +``` */ let classify = (value: 'value): option => if value->isInstanceOf { diff --git a/src/window/Document.res b/src/window/Document.res index 4908a402..c6b0315b 100644 --- a/src/window/Document.res +++ b/src/window/Document.res @@ -460,11 +460,29 @@ external hasStorageAccess: DOM.document => promise = "hasStorageAccess" @send external requestStorageAccess: DOM.document => promise = "requestStorageAccess" +/** +`isInstanceOf(value)` + +Returns whether `value` is a `Document` created in the current JavaScript realm. + +This is a runtime check. It returns `false` when `globalThis.Document` is unavailable, such as in +some server or worker environments. +*/ let isInstanceOf = (_: 'value): bool => %raw(`typeof globalThis.Document === "function" && param instanceof globalThis.Document`) /** -Returns the value as a Document when it is a Document in the current realm, and None otherwise. +`classify(value)` + +Safely narrows a value from a broad DOM or library type to `DOM.document`. + +Use this when a JavaScript binding or union-like API can contain a document but cannot express that +concrete type statically. Returns `Some(document)` when the value is a `Document` in the current +realm, and `None` when it is not or when the `Document` constructor is unavailable. + +```res +let document = value->Document.classify +``` */ let classify = (value: 'value): option => if value->isInstanceOf { From 670bba77b490ae3cde9b56c3ccee10c1b93be919 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 18 Sep 2026 09:53:35 -0400 Subject: [PATCH 3/3] fix(dom): promote example helpers to public APIs --- docs/content/docs/api-surface.mdx | 8 ++-- docs/pages/examples.astro | 4 +- src/canvas/CanvasRenderingContext2D.res | 25 ++++++++++ src/canvas/HTMLCanvasElement.res | 34 +++++++++++++ src/html/HTMLImageElement.res | 34 +++++++++++++ tests/DOMAPI/Classify__test.res | 15 +++++- tests/DOMAPI/HTMLCanvasElement__test.res | 61 ++++++++++++------------ tests/DOMAPI/HTMLInputElement__test.res | 9 ++-- 8 files changed, 148 insertions(+), 42 deletions(-) diff --git a/docs/content/docs/api-surface.mdx b/docs/content/docs/api-surface.mdx index 0cbcb022..798bc3ec 100644 --- a/docs/content/docs/api-surface.mdx +++ b/docs/content/docs/api-surface.mdx @@ -319,13 +319,15 @@ let readInputValue = target => The classifier parameter is polymorphic, so a library-owned target can be passed directly without an identity cast to a WebAPI type first. `Element.classify`, `Document.classify`, -`HTMLElement.classify`, and `HTMLInputElement.classify` follow the same contract. Their matching -`isInstanceOf` functions are available when only a boolean check is needed. +`HTMLElement.classify`, `HTMLInputElement.classify`, `HTMLImageElement.classify`, and +`HTMLCanvasElement.classify` follow the same contract. Their matching `isInstanceOf` functions are +available when only a boolean check is needed. Classification uses the corresponding constructor from `globalThis`. It returns `None` rather than raising when that constructor is unavailable in a server or worker environment. Like JavaScript `instanceof`, it only recognizes values created in the current realm; values from another window or -iframe may not match. Enable the `WebAPI.HTML` feature when using the HTML-specific classifiers. +iframe may not match. Enable the `WebAPI.HTML` feature for HTML-specific classifiers and +`WebAPI.Canvas` for `HTMLCanvasElement.classify`. ## Visual Viewport diff --git a/docs/pages/examples.astro b/docs/pages/examples.astro index 871e59cc..53ec1320 100644 --- a/docs/pages/examples.astro +++ b/docs/pages/examples.astro @@ -19,8 +19,8 @@ const headings = testFiles.map(({ name }) => ({ The best example of usage of these bindings are the tests.
- These typically contain tweaks made to the generated bindings to make them more - ergonomic. + These exercise the public bindings directly. Reusable Web API bindings and conversion + helpers belong in the library rather than in individual examples.

{ testFiles.map(({ source, output, name }) => { diff --git a/src/canvas/CanvasRenderingContext2D.res b/src/canvas/CanvasRenderingContext2D.res index aa649758..0bde4367 100644 --- a/src/canvas/CanvasRenderingContext2D.res +++ b/src/canvas/CanvasRenderingContext2D.res @@ -1,3 +1,28 @@ +/** +[Read more on MDN](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/fillStyle) +*/ +@get +external getFillStyle: DOM.canvasRenderingContext2D => CanvasTypes.fillStyle = "fillStyle" + +/** +[Read more on MDN](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/fillStyle) +*/ +@set +external setFillStyle: (DOM.canvasRenderingContext2D, CanvasTypes.fillStyle) => unit = "fillStyle" + +/** +[Read more on MDN](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/font) +*/ +@set +external setFont: (DOM.canvasRenderingContext2D, string) => unit = "font" + +/** +[Read more on MDN](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/textBaseline) +*/ +@set +external setTextBaseline: (DOM.canvasRenderingContext2D, CanvasTypes.canvasTextBaseline) => unit = + "textBaseline" + /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/save) */ diff --git a/src/canvas/HTMLCanvasElement.res b/src/canvas/HTMLCanvasElement.res index 9288e87b..885e3f40 100644 --- a/src/canvas/HTMLCanvasElement.res +++ b/src/canvas/HTMLCanvasElement.res @@ -19,6 +19,40 @@ type t = { include HTMLElement.Impl({type t = t}) +/** +`isInstanceOf(value)` + +Returns whether `value` is an `HTMLCanvasElement` created in the current JavaScript realm. + +This is a runtime check. It returns `false` when `globalThis.HTMLCanvasElement` is unavailable, such +as in some server or worker environments. +*/ +let isInstanceOf = (_: 'value): bool => + %raw(`typeof globalThis.HTMLCanvasElement === "function" && param instanceof globalThis.HTMLCanvasElement`) + +/** +`classify(value)` + +Safely narrows a value from a broad DOM element type to `HTMLCanvasElement.t`. + +Use this for values returned by APIs such as `Document.getElementById`. Returns `Some(canvas)` for +an `HTMLCanvasElement` in the current realm, and `None` when the value is not a canvas or when the +`HTMLCanvasElement` constructor is unavailable. + +```res +switch element->HTMLCanvasElement.classify { +| Some(canvas) => canvas->HTMLCanvasElement.getContext2D +| None => Null +} +``` +*/ +let classify = (value: 'value): option => + if value->isInstanceOf { + Some(Obj.magic(value)) + } else { + None + } + /** Returns an object that provides methods and properties for drawing and manipulating images and graphics on a canvas element in a document. A context object includes information about colors, line widths, fonts, and other graphic parameters that can be drawn on a canvas. Creates a CanvasRenderingContext2D object representing a two-dimensional rendering context. diff --git a/src/html/HTMLImageElement.res b/src/html/HTMLImageElement.res index 859a0c54..7e08a6bf 100644 --- a/src/html/HTMLImageElement.res +++ b/src/html/HTMLImageElement.res @@ -342,6 +342,40 @@ type t = { include HTMLElement.Impl({type t = t}) +/** +`isInstanceOf(value)` + +Returns whether `value` is an `HTMLImageElement` created in the current JavaScript realm. + +This is a runtime check. It returns `false` when `globalThis.HTMLImageElement` is unavailable, such +as in some server or worker environments. +*/ +let isInstanceOf = (_: 'value): bool => + %raw(`typeof globalThis.HTMLImageElement === "function" && param instanceof globalThis.HTMLImageElement`) + +/** +`classify(value)` + +Safely narrows a value from a broad DOM element type to `HTMLImageElement.t`. + +Use this for values returned by element-creating or selector APIs. Returns `Some(image)` for an +`HTMLImageElement` in the current realm, and `None` when the value is not an image or when the +`HTMLImageElement` constructor is unavailable. + +```res +switch element->HTMLImageElement.classify { +| Some(image) => Some(image.src) +| None => None +} +``` +*/ +let classify = (value: 'value): option => + if value->isInstanceOf { + Some(Obj.magic(value)) + } else { + None + } + /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/decode) */ diff --git a/tests/DOMAPI/Classify__test.res b/tests/DOMAPI/Classify__test.res index 296edcc9..3a4799ad 100644 --- a/tests/DOMAPI/Classify__test.res +++ b/tests/DOMAPI/Classify__test.res @@ -2,6 +2,8 @@ globalThis.Element = class Element {} globalThis.Document = class Document {} globalThis.HTMLElement = class HTMLElement extends globalThis.Element {} +globalThis.HTMLCanvasElement = class HTMLCanvasElement extends globalThis.HTMLElement {} +globalThis.HTMLImageElement = class HTMLImageElement extends globalThis.HTMLElement {} globalThis.HTMLInputElement = class HTMLInputElement extends globalThis.HTMLElement { checkValidity() { return true @@ -12,22 +14,33 @@ globalThis.HTMLInputElement = class HTMLInputElement extends globalThis.HTMLElem let element: unknown = %raw(`new globalThis.Element()`) let document: unknown = %raw(`new globalThis.Document()`) let htmlElement: unknown = %raw(`new globalThis.HTMLElement()`) +let canvas: unknown = %raw(`new globalThis.HTMLCanvasElement()`) +let image: unknown = %raw(`new globalThis.HTMLImageElement()`) let input: unknown = %raw(`new globalThis.HTMLInputElement()`) assert(element->Element.classify->Option.isSome) assert(document->Document.classify->Option.isSome) assert(htmlElement->HTMLElement.classify->Option.isSome) +assert(canvas->HTMLCanvasElement.classify->Option.isSome) +assert(image->HTMLImageElement.classify->Option.isSome) assert(input->HTMLInputElement.classify->Option.isSome) assert(input->Element.classify->Option.isSome) assert(input->HTMLElement.classify->Option.isSome) assert(document->Element.classify->Option.isNone) +assert(image->HTMLCanvasElement.classify->Option.isNone) assert(htmlElement->HTMLInputElement.classify->Option.isNone) input ->HTMLInputElement.classify ->Option.forEach(input => assert(input->HTMLInputElement.checkValidity)) -%%raw(`delete globalThis.HTMLInputElement`) +%%raw(` +delete globalThis.HTMLCanvasElement +delete globalThis.HTMLImageElement +delete globalThis.HTMLInputElement +`) +assert(canvas->HTMLCanvasElement.classify->Option.isNone) +assert(image->HTMLImageElement.classify->Option.isNone) assert(input->HTMLInputElement.classify->Option.isNone) diff --git a/tests/DOMAPI/HTMLCanvasElement__test.res b/tests/DOMAPI/HTMLCanvasElement__test.res index 4ac1156a..c074557b 100644 --- a/tests/DOMAPI/HTMLCanvasElement__test.res +++ b/tests/DOMAPI/HTMLCanvasElement__test.res @@ -1,37 +1,36 @@ -external toHTMLCanvasElement: null => HTMLCanvasElement.t = "%identity" -@set -external setFillStyle: (DOM.canvasRenderingContext2D, CanvasTypes.fillStyle) => unit = "fillStyle" -@get -external getFillStyle: DOM.canvasRenderingContext2D => CanvasTypes.fillStyle = "fillStyle" -@set -external setFont: (DOM.canvasRenderingContext2D, string) => unit = "font" -@set -external setTextBaseline: (DOM.canvasRenderingContext2D, CanvasTypes.canvasTextBaseline) => unit = - "textBaseline" +let draw = (ctx: DOM.canvasRenderingContext2D) => { + ctx->CanvasRenderingContext2D.setFillStyle(FillStyle.fromString("red")) + ctx->CanvasRenderingContext2D.fillRect(~x=50., ~y=50., ~w=200., ~h=200.) -let myCanvas: HTMLCanvasElement.t = - DomGlobal.document->Document.getElementById("myCanvas")->toHTMLCanvasElement -let ctx = myCanvas->HTMLCanvasElement.getContext2D->Null.getOrThrow + ctx->CanvasRenderingContext2D.setFillStyle(FillStyle.fromString("black")) + ctx->CanvasRenderingContext2D.setFont("2px Tahoma") + ctx->CanvasRenderingContext2D.setTextBaseline(CanvasTypes.Top) + ctx->CanvasRenderingContext2D.fillText(~text="MY TEXT", ~x=60., ~y=60.) -ctx->setFillStyle(FillStyle.fromString("red")) -ctx->CanvasRenderingContext2D.fillRect(~x=50., ~y=50., ~w=200., ~h=200.) + switch ctx->CanvasRenderingContext2D.getFillStyle->FillStyle.decode { + | FillStyle.String(color) => Console.log(`Color: ${color}`) + | FillStyle.CanvasGradient(_) => Console.log("CanvasGradient") + | FillStyle.CanvasPattern(_) => Console.log("CanvasPattern") + } -ctx->setFillStyle(FillStyle.fromString("black")) -ctx->setFont("2px Tahoma") -ctx->setTextBaseline(CanvasTypes.Top) -ctx->CanvasRenderingContext2D.fillText(~text="MY TEXT", ~x=60., ~y=60.) - -switch ctx->getFillStyle->FillStyle.decode { -| FillStyle.String(color) => Console.log(`Color: ${color}`) -| FillStyle.CanvasGradient(_) => Console.log("CanvasGradient") -| FillStyle.CanvasPattern(_) => Console.log("CanvasPattern") + DomGlobal.document + ->Document.createElement("img") + ->HTMLImageElement.classify + ->Option.forEach(image => + ctx->CanvasRenderingContext2D.drawImageWithDimensions( + ~image, + ~dx=0., + ~dy=0., + ~dw=200., + ~dh=200., + ) + ) } -let img: HTMLImageElement.t = DomGlobal.document->Document.createElement("img")->Obj.magic -ctx->CanvasRenderingContext2D.drawImageWithDimensions( - ~image=img, - ~dx=0., - ~dy=0., - ~dw=200., - ~dh=200., +DomGlobal.document +->Document.getElementById("myCanvas") +->Null.toOption +->Option.flatMap(element => element->HTMLCanvasElement.classify) +->Option.forEach(canvas => + canvas->HTMLCanvasElement.getContext2D->Null.toOption->Option.forEach(draw) ) diff --git a/tests/DOMAPI/HTMLInputElement__test.res b/tests/DOMAPI/HTMLInputElement__test.res index 41e76ce0..35758f5e 100644 --- a/tests/DOMAPI/HTMLInputElement__test.res +++ b/tests/DOMAPI/HTMLInputElement__test.res @@ -1,5 +1,4 @@ -external toHTMLInputElement: DOMTree.element => HTMLInputElement.t = "%identity" - -let input: HTMLInputElement.t = - DomGlobal.document->Document.createElement("input")->toHTMLInputElement -let value = input.value +let value = switch DomGlobal.document->Document.createElement("input")->HTMLInputElement.classify { +| Some(input) => Some(input.value) +| None => None +}