From 19f70194d8a1dd7341b79ce5f62ab6b9c25323d5 Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Fri, 17 Jul 2026 16:31:08 +0200 Subject: [PATCH 1/8] Add "sibling insert" API for brr lwd --- lib/brr-lwd/elwd.ml | 26 ++++++++++++++++++++++++++ lib/brr-lwd/elwd.mli | 6 ++++++ 2 files changed, 32 insertions(+) diff --git a/lib/brr-lwd/elwd.ml b/lib/brr-lwd/elwd.ml index 4b09c03..c41558f 100644 --- a/lib/brr-lwd/elwd.ml +++ b/lib/brr-lwd/elwd.ml @@ -409,3 +409,29 @@ let ul = cons Name.ul let var = cons Name.var let video = cons Name.video let wbr = void_cons Name.wbr + +type token = t Lwd.root + +let insert_sibling where anchor reactive = + let root = Lwd.observe reactive in + let last_element = ref (Lwd.quick_sample root) in + El.insert_siblings where anchor [ !last_element ]; + let on_invalidate _ = + let _ : int = + G.request_animation_frame @@ fun _ -> + let new_element = Lwd.quick_sample root in + if !last_element = new_element then () + else ( + El.insert_siblings `Replace !last_element [ new_element ]; + last_element := new_element + ) + in + () + in + Lwd.set_on_invalidate root on_invalidate; + root + +let remove_sibling root = + let element = Lwd.quick_sample root in + El.remove element; + Lwd.quick_release root diff --git a/lib/brr-lwd/elwd.mli b/lib/brr-lwd/elwd.mli index 38fc4ea..ae4f818 100644 --- a/lib/brr-lwd/elwd.mli +++ b/lib/brr-lwd/elwd.mli @@ -16,6 +16,12 @@ type 'a col = [ type handler (* An event handler *) val handler : ?opts:Ev.listen_opts -> 'a Ev.type' -> ('a Ev.t -> unit) -> handler +type token + +val insert_sibling : [ `Before | `After | `Replace ] -> t -> t Lwd.t -> token + +val remove_sibling : token -> unit + val v : ?d:document -> ?at:At.t col -> ?ev:handler col -> tag_name -> t col -> t Lwd.t (** [v ?d ?at name cs] is an element [name] with attribute [at] (defaults to [[]]) and children [cs]. If [at] specifies an From fd969e41ec9c99d95e346a07261cd11028fc45d6 Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sat, 18 Jul 2026 15:47:00 +0200 Subject: [PATCH 2/8] Update brr examples to use new API --- examples/cssclasstest-brr/index.html | 13 +++++++++---- examples/cssclasstest-brr/main.ml | 21 +++++++++++---------- examples/focustest-brr/main.ml | 27 ++++++++++----------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/examples/cssclasstest-brr/index.html b/examples/cssclasstest-brr/index.html index 41b943c..3501093 100644 --- a/examples/cssclasstest-brr/index.html +++ b/examples/cssclasstest-brr/index.html @@ -3,14 +3,14 @@ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> - Minesweeper + Minesweeper embryo diff --git a/examples/cssclasstest-brr/main.ml b/examples/cssclasstest-brr/main.ml index 0175ef6..839926e 100644 --- a/examples/cssclasstest-brr/main.ml +++ b/examples/cssclasstest-brr/main.ml @@ -44,14 +44,15 @@ let ui = `S (Lwd_seq.lift board) ] +let defer_after_dom_loading f = + let on_load _ = f () in + ignore (Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window)); + () + let () = - let ui = Lwd.observe ui in - let on_invalidate _ = - ignore @@ G.request_animation_frame @@ fun _ -> - ignore @@ Lwd.quick_sample ui - in - let on_load _ = - El.append_children (Document.body G.document) [ Lwd.quick_sample ui ]; - Lwd.set_on_invalidate ui on_invalidate - in - ignore @@ Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window) + defer_after_dom_loading @@ fun () -> + match El.find_first_by_selector (Jstr.v "#main") with + | None -> failwith "#main could not be found, check your html" + | Some main -> + let _remove_token = Elwd.insert_sibling `Replace main ui in + () diff --git a/examples/focustest-brr/main.ml b/examples/focustest-brr/main.ml index 4b81ab8..267bdb8 100644 --- a/examples/focustest-brr/main.ml +++ b/examples/focustest-brr/main.ml @@ -46,22 +46,15 @@ let ui = `S (Lwd_seq.map El.txt' values); ] -let () = - let ui = Lwd.observe ui in - let on_invalidate _ = - Console.(log [str "on invalidate"]); - let _ : int = - G.request_animation_frame @@ fun _ -> - let _ui = Lwd.quick_sample ui in - (*El.set_children (Document.body G.document) [ui]*) - () - in - () - in - let on_load _ = - Console.(log [str "onload"]); - El.append_children (Document.body G.document) [Lwd.quick_sample ui]; - Lwd.set_on_invalidate ui on_invalidate - in +let defer_after_dom_loading f = + let on_load _ = f () in ignore (Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window)); () + +let () = + defer_after_dom_loading @@ fun () -> + match El.find_first_by_selector (Jstr.v "#main") with + | None -> failwith "#main could not be found, check your html" + | Some main -> + let _remove_token = Elwd.insert_sibling `Replace main ui in + () From 703c49f32ff60c55f76c6742b1212b76113b43d2 Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sat, 18 Jul 2026 16:05:25 +0200 Subject: [PATCH 3/8] Lwd brr: Add `set_children` API --- examples/cssclasstest-brr/index.html | 2 +- examples/cssclasstest-brr/main.ml | 8 ++++---- lib/brr-lwd/elwd.ml | 21 +++++++++++++++++++++ lib/brr-lwd/elwd.mli | 1 + 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/examples/cssclasstest-brr/index.html b/examples/cssclasstest-brr/index.html index 3501093..4e64b15 100644 --- a/examples/cssclasstest-brr/index.html +++ b/examples/cssclasstest-brr/index.html @@ -27,6 +27,6 @@ -
+
diff --git a/examples/cssclasstest-brr/main.ml b/examples/cssclasstest-brr/main.ml index 839926e..0bdd847 100644 --- a/examples/cssclasstest-brr/main.ml +++ b/examples/cssclasstest-brr/main.ml @@ -40,7 +40,7 @@ let ui = Lwd_seq.monoid squares in - Elwd.div ~at:[ `P (At.class' (Jstr.v "game-board")) ] [ + [ `S (Lwd_seq.lift board) ] @@ -51,8 +51,8 @@ let defer_after_dom_loading f = let () = defer_after_dom_loading @@ fun () -> - match El.find_first_by_selector (Jstr.v "#main") with - | None -> failwith "#main could not be found, check your html" + match El.find_first_by_selector (Jstr.v ".game-board") with + | None -> failwith ".game-board could not be found, check your html" | Some main -> - let _remove_token = Elwd.insert_sibling `Replace main ui in + let _remove_token = Elwd.set_children main ui in () diff --git a/lib/brr-lwd/elwd.ml b/lib/brr-lwd/elwd.ml index c41558f..93eecb9 100644 --- a/lib/brr-lwd/elwd.ml +++ b/lib/brr-lwd/elwd.ml @@ -431,6 +431,27 @@ let insert_sibling where anchor reactive = Lwd.set_on_invalidate root on_invalidate; root +let set_children el children = + let reactive = + let children, impure_children = consume_children children in + match impure_children with + | None -> El.set_children el children; Lwd.pure el + | Some children -> + update_children el children + in + let root = Lwd.observe reactive in + let _el = Lwd.quick_sample root in + let on_invalidate _ = + let _ : int = + G.request_animation_frame @@ fun _ -> + let _el = Lwd.quick_sample root in + () + in + () + in + Lwd.set_on_invalidate root on_invalidate; + root + let remove_sibling root = let element = Lwd.quick_sample root in El.remove element; diff --git a/lib/brr-lwd/elwd.mli b/lib/brr-lwd/elwd.mli index ae4f818..3398074 100644 --- a/lib/brr-lwd/elwd.mli +++ b/lib/brr-lwd/elwd.mli @@ -19,6 +19,7 @@ val handler : ?opts:Ev.listen_opts -> 'a Ev.type' -> ('a Ev.t -> unit) -> handle type token val insert_sibling : [ `Before | `After | `Replace ] -> t -> t Lwd.t -> token +val set_children : t -> t col -> token val remove_sibling : token -> unit From 911e7e951bca2f9f44f368671511bec4736fce5d Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sat, 18 Jul 2026 16:12:15 +0200 Subject: [PATCH 4/8] Lwd brr: Update the docs for entry-points in the DOM --- lib/brr-lwd/elwd.ml | 4 ++-- lib/brr-lwd/elwd.mli | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/brr-lwd/elwd.ml b/lib/brr-lwd/elwd.ml index 93eecb9..17ca5d5 100644 --- a/lib/brr-lwd/elwd.ml +++ b/lib/brr-lwd/elwd.ml @@ -410,7 +410,7 @@ let var = cons Name.var let video = cons Name.video let wbr = void_cons Name.wbr -type token = t Lwd.root +type root = t Lwd.root let insert_sibling where anchor reactive = let root = Lwd.observe reactive in @@ -452,7 +452,7 @@ let set_children el children = Lwd.set_on_invalidate root on_invalidate; root -let remove_sibling root = +let release root = let element = Lwd.quick_sample root in El.remove element; Lwd.quick_release root diff --git a/lib/brr-lwd/elwd.mli b/lib/brr-lwd/elwd.mli index 3398074..5ea2afd 100644 --- a/lib/brr-lwd/elwd.mli +++ b/lib/brr-lwd/elwd.mli @@ -16,13 +16,6 @@ type 'a col = [ type handler (* An event handler *) val handler : ?opts:Ev.listen_opts -> 'a Ev.type' -> ('a Ev.t -> unit) -> handler -type token - -val insert_sibling : [ `Before | `After | `Replace ] -> t -> t Lwd.t -> token -val set_children : t -> t col -> token - -val remove_sibling : token -> unit - val v : ?d:document -> ?at:At.t col -> ?ev:handler col -> tag_name -> t col -> t Lwd.t (** [v ?d ?at name cs] is an element [name] with attribute [at] (defaults to [[]]) and children [cs]. If [at] specifies an @@ -31,6 +24,22 @@ val v : ?d:document -> ?at:At.t col -> ?ev:handler col -> tag_name -> t col -> t the final value. [d] is the document on which the element is defined it defaults {!Brr.G.document}. *) + +(** {1 Inserting reactive elements in the DOM} *) + +type root + +val insert_sibling : [ `Before | `After | `Replace ] -> t -> t Lwd.t -> root +(** Inserts a reactive element in the DOM, right before/after/instead of the + given element. The element is updated until the return value is passed to + {!release}. *) + +val set_children : t -> t col -> root +(** Sets a set of reactive elements as the children of the given element. The + set is updated until the return value is passed to {!release}. *) + +val release : root -> unit + (** {1:els Element constructors} *) type cons = ?d:document -> ?at:At.t col -> ?ev:handler col -> t col -> t Lwd.t From 7cf9eb28d61693b279d365cb637c507fa97c626f Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sat, 18 Jul 2026 16:52:52 +0200 Subject: [PATCH 5/8] Fix set_children initial value --- lib/brr-lwd/elwd.ml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/brr-lwd/elwd.ml b/lib/brr-lwd/elwd.ml index 17ca5d5..296d0d6 100644 --- a/lib/brr-lwd/elwd.ml +++ b/lib/brr-lwd/elwd.ml @@ -437,6 +437,10 @@ let set_children el children = match impure_children with | None -> El.set_children el children; Lwd.pure el | Some children -> + let root = Lwd.observe children in + let c = Lwd.quick_sample root in + El.set_children el (Lwd_seq.to_list c); + Lwd.quick_release root; update_children el children in let root = Lwd.observe reactive in From a53c4aef94c391d5b183e7bff309eff0e893578e Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sat, 18 Jul 2026 17:13:31 +0200 Subject: [PATCH 6/8] Brr: Fix release behavior and doc --- lib/brr-lwd/elwd.ml | 5 +---- lib/brr-lwd/elwd.mli | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/brr-lwd/elwd.ml b/lib/brr-lwd/elwd.ml index 296d0d6..832359a 100644 --- a/lib/brr-lwd/elwd.ml +++ b/lib/brr-lwd/elwd.ml @@ -456,7 +456,4 @@ let set_children el children = Lwd.set_on_invalidate root on_invalidate; root -let release root = - let element = Lwd.quick_sample root in - El.remove element; - Lwd.quick_release root +let release root = Lwd.quick_release root diff --git a/lib/brr-lwd/elwd.mli b/lib/brr-lwd/elwd.mli index 5ea2afd..1500f26 100644 --- a/lib/brr-lwd/elwd.mli +++ b/lib/brr-lwd/elwd.mli @@ -39,6 +39,7 @@ val set_children : t -> t col -> root set is updated until the return value is passed to {!release}. *) val release : root -> unit +(** Stop updating the reactive elements, leaving what is currently there. *) (** {1:els Element constructors} *) From 576abc0c8879cc41c7c9c76febaab218075d8e1c Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sun, 19 Jul 2026 08:47:03 +0200 Subject: [PATCH 7/8] Brr: Add `append_child` and `prepend_child` --- lib/brr-lwd/elwd.ml | 14 ++++++++++++-- lib/brr-lwd/elwd.mli | 10 ++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/brr-lwd/elwd.ml b/lib/brr-lwd/elwd.ml index 832359a..ba89973 100644 --- a/lib/brr-lwd/elwd.ml +++ b/lib/brr-lwd/elwd.ml @@ -412,10 +412,10 @@ let wbr = void_cons Name.wbr type root = t Lwd.root -let insert_sibling where anchor reactive = +let insert first_insert anchor reactive = let root = Lwd.observe reactive in let last_element = ref (Lwd.quick_sample root) in - El.insert_siblings where anchor [ !last_element ]; + first_insert anchor [!last_element]; let on_invalidate _ = let _ : int = G.request_animation_frame @@ fun _ -> @@ -431,6 +431,16 @@ let insert_sibling where anchor reactive = Lwd.set_on_invalidate root on_invalidate; root +let insert_sibling where anchor reactive = + let first_insert = El.insert_siblings where in + insert first_insert anchor reactive + +let append_child anchor reactive = + insert El.append_children anchor reactive + +let prepend_child anchor reactive = + insert El.prepend_children anchor reactive + let set_children el children = let reactive = let children, impure_children = consume_children children in diff --git a/lib/brr-lwd/elwd.mli b/lib/brr-lwd/elwd.mli index 1500f26..2d89172 100644 --- a/lib/brr-lwd/elwd.mli +++ b/lib/brr-lwd/elwd.mli @@ -38,6 +38,16 @@ val set_children : t -> t col -> root (** Sets a set of reactive elements as the children of the given element. The set is updated until the return value is passed to {!release}. *) +val append_child : t -> t Lwd.t -> root +(** Inserts a reactive element in the DOM, as last child of the given + element. The element is updated until the return value is passed to + {!release}. *) + +val prepend_child : t -> t Lwd.t -> root +(** Inserts a reactive element in the DOM, as first child of the given + element. The element is updated until the return value is passed to + {!release}. *) + val release : root -> unit (** Stop updating the reactive elements, leaving what is currently there. *) From 2c0f56cad86dcce72e15aafe0d989251d4b0c8f0 Mon Sep 17 00:00:00 2001 From: Paul-Elliot Date: Sun, 19 Jul 2026 12:14:25 +0200 Subject: [PATCH 8/8] Avoid `ignore` in examples --- examples/cssclasstest-brr/main.ml | 4 +++- examples/focustest-brr/main.ml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/cssclasstest-brr/main.ml b/examples/cssclasstest-brr/main.ml index 0bdd847..bf9ddfc 100644 --- a/examples/cssclasstest-brr/main.ml +++ b/examples/cssclasstest-brr/main.ml @@ -46,7 +46,9 @@ let ui = let defer_after_dom_loading f = let on_load _ = f () in - ignore (Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window)); + let _listen_id = + Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window) + in () let () = diff --git a/examples/focustest-brr/main.ml b/examples/focustest-brr/main.ml index 267bdb8..eb1ee20 100644 --- a/examples/focustest-brr/main.ml +++ b/examples/focustest-brr/main.ml @@ -48,7 +48,9 @@ let ui = let defer_after_dom_loading f = let on_load _ = f () in - ignore (Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window)); + let _listen_id = + Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window) + in () let () =