package bonsai

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file size_tracker.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
open! Core
open! Import
open! Bonsai_web
open! Js_of_ocaml

module Dimensions = struct
  type t =
    { width : float
    ; height : float
    }
end

module T = struct
  module State = struct
    type t =
      { mutable callback : width:float -> height:float -> unit
      ; mutable observer : ResizeObserver.resizeObserver Js.t option
      ; mutable last_width : float
      ; mutable last_height : float
      }
  end

  let observe node ~(state : State.t) =
    (* We take the whole state here so that we can mutate the callback in it and
       witness the change in the observer *)
    let on_resize_observed entries _observer =
      let open Option.Let_syntax in
      let size =
        let%bind first_entry = Js.array_get entries 0 |> Js.Optdef.to_option in
        let border_box_arr = first_entry##.borderBoxSize in
        let%map border_box = Js.array_get border_box_arr 0 |> Js.Optdef.to_option in
        (* This assumes writing-mode:horizontal.
           https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/borderBoxSize *)
        border_box##.inlineSize, border_box##.blockSize
      in
      Option.iter size ~f:(fun (width, height) ->
        state.last_width <- width;
        state.last_height <- height;
        state.callback ~width ~height)
    in
    ResizeObserver.observe () ~node ~f:on_resize_observed
  ;;

  open State

  module Input = struct
    type t = Dimensions.t -> unit Vdom.Effect.t [@@deriving sexp_of]

    let combine left right dimensions =
      Vdom.Effect.sequence_as_sibling (left dimensions) ~unless_stopped:(fun () ->
        right dimensions)
    ;;
  end

  let wrap_with_handle ~width ~height ~f =
    Vdom.Effect.Expert.handle_non_dom_event_exn (f { Dimensions.width; height })
  ;;

  let init input _ =
    { State.observer = None
    ; callback = wrap_with_handle ~f:input
    ; last_width = 0.0
    ; last_height = 0.0
    }
  ;;

  let on_mount _ state element = state.observer <- Some (observe ~state element)

  let update ~old_input:_ ~new_input state _ =
    state.callback <- wrap_with_handle ~f:new_input;
    state.callback ~width:state.last_width ~height:state.last_height
  ;;

  let destroy _ { State.observer; _ } _ =
    Option.iter observer ~f:(fun observer -> observer##disconnect)
  [@@ocaml.warning
     "-68"]
  ;;
end

module Hook = Vdom.Attr.Hooks.Make (T)

let on_change f =
  let f { Dimensions.width; height } = f ~width ~height in
  Vdom.Attr.create_hook "size_tracker" (Hook.create f)
;;

module For_testing = struct
  module Dimensions = Dimensions
  include Hook.For_testing
end
OCaml

Innovation. Community. Security.