Source file freeform_multiselect.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
94
95
96
97
98
99
100
open! Core
open! Bonsai_web
open! Bonsai.Let_syntax
(** This control is similar to the typeahead control, differing in the fact that it
doesn't aim to complete your input. Think of this control as a multi-select that
you're free to add random values to. *)
let input ~placeholder:placeholder_ ~value:value_ ~ ~id:id_ ~on_input:on_input_ =
Vdom.Node.input
~attrs:
[ Vdom.Attr.(
extra_attr
@ type_ "text"
@ create "list" id_
@ placeholder placeholder_
@ value value_
@ value_prop value_
@ on_change (fun _ input -> on_input_ input))
]
()
;;
let pills ~selected_options ~on_set_change ~inject_selected_options =
let pill option =
let remove_option _ =
let selected_options = Set.remove selected_options option in
Effect.Many
[ on_set_change selected_options; inject_selected_options selected_options ]
in
Vdom.Node.span
~attrs:
[ Vdom.Attr.(
tabindex 0
@ create "data-value" option
@ on_click remove_option
@ on_keyup (fun ev ->
match Js_of_ocaml.Dom_html.Keyboard_code.of_event ev with
| Space | Enter | NumpadEnter | Backspace | Delete -> remove_option ev
| _ -> Effect.Ignore))
]
[ Vdom.Node.text (option ^ " ×") ]
in
if Set.is_empty selected_options
then Vdom.Node.none
else
Vdom.Node.div
~attrs:[ Vdom.Attr.class_ "bonsai-web-ui-freeform-multiselect-pills" ]
(Set.to_list selected_options |> List.map ~f:pill)
;;
let input ~placeholder ~ ~split ~id ~selected_options ~on_set_change =
let%sub select = Bonsai.state (module String) ~default_model:"" in
let%arr select, inject_select = select
and selected_options, inject_selected_options = selected_options
and = extra_attr
and id = id
and on_set_change = on_set_change in
let on_input user_input =
let maybe_changed_options =
split user_input
|> String.Set.of_list
|> Set.filter ~f:(fun item -> not (String.strip item |> String.is_empty))
|> Set.union selected_options
in
let ui_events =
if Set.equal maybe_changed_options selected_options
then [ inject_select user_input ]
else [ inject_select ""; on_set_change maybe_changed_options ]
in
Effect.Many (inject_selected_options maybe_changed_options :: ui_events)
in
input ~extra_attr ~value:select ~placeholder ~id ~on_input
;;
let create
?( = Value.return Vdom.Attr.empty)
?(placeholder = "")
?(on_set_change = Value.return (const Effect.Ignore))
?(split = List.return)
()
=
let%sub selected_options =
Bonsai.state (module String.Set) ~default_model:String.Set.empty
in
let%sub id = Bonsai.path_id in
let%sub input =
input ~placeholder ~extra_attr ~id ~on_set_change ~split ~selected_options
in
let%arr selected_options, inject_selected_options = selected_options
and input = input
and on_set_change = on_set_change in
let pills = pills ~selected_options ~on_set_change ~inject_selected_options in
selected_options, Vdom.Node.div [ input; pills ], inject_selected_options
;;