Source file hooks_intf.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
92
93
open! Core
open! Js_of_ocaml

module type Input = sig
  type t [@@deriving sexp_of]

  (* [combine first second] describes how more than one of the same hook should
     be merged. This function will only be used if the hooks are combined
     using [Attr.many]'s merge semantics. It is common for [t] to by
     a function type like ['a -> unit Ui_effect.t]; in this case, the proper
     implementation is probably the following:

     {[
       let combine f g event =
         Vdom.Effect.sequence_as_sibling
           (f event)
           ~unless_stopped:(fun () -> g event)
     ]} *)

  val combine : t -> t -> t
end

module type S = sig
  module State : T
  module Input : Input

  (** [init] is called the first time that this attribute is attached to
      a particular node.  It is particularly responsible for producing a value
      of type [State.t].  The element that it is being attached to is not
      necessarily attached to the rest of the DOM tree. *)
  val init : Input.t -> Dom_html.element Js.t -> State.t

  (** [on_mount] is called once, after the element is attached to the rest of the
      DOM tree. *)
  val on_mount : Input.t -> State.t -> Dom_html.element Js.t -> unit

  (** [update] is called when a previous attribute of the same kind existed on
      the vdom node.  You get access to the [Input.t] that the previous node was
      created with, as well as the State.t for that hook, which you can mutate if you
      like. There is no guarantee that [update] will be called instead of
      a sequence of [destroy] followed by [init], so [update] should behave the
      same as that sequence (except it might be faster). *)
  val update
    :  old_input:Input.t
    -> new_input:Input.t
    -> State.t
    -> Dom_html.element Js.t
    -> unit

  (** [destroy] is called when the previous vdom has this hook, but a newer
      vdom tree does not.  The last input and state are passed in alongside the
      element that it used to be attached to. *)
  val destroy : Input.t -> State.t -> Dom_html.element Js.t -> unit
end

module type Hooks = sig
  module type S = S
  module type Input = Input

  type t

  val combine : t -> t -> t
  val pack : t -> Js.Unsafe.any

  module Make (S : S) : sig
    (** [name] is a unique identifier that is treated like the names of regular
        attributes like "id" and "class" in <div id=... class=...> in that
        there can only be one attribute with the same name on an element, and
        that hooks are diffed only if the same hook has the same name between
        stabilizations. *)
    val create : S.Input.t -> t

    module For_testing : sig
      (** The type-id provided here can be used to pull out the input value for
          an instance of this hook for testing-purposes. *)

      val type_id : S.Input.t Type_equal.Id.t
    end
  end

  module For_testing : sig
    module Extra : sig
      type t =
        | T :
            { type_id : 'a Type_equal.Id.t
            ; value : 'a
            }
            -> t

      val sexp_of_t : t -> Sexp.t
    end
  end
end