Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file bonsai_web_ui_query_box.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346open!CoreopenBonsai_webopenBonsai.Let_syntaxopenVdommoduleModel=structtype'ksuggestion_list_state=|Selectedof'k|First_item|Closed[@@derivingequal,sexp]type'kt={query:string;suggestion_list_state:'ksuggestion_list_state}[@@derivingequal,sexp]endmoduleAction=structtypet=|Set_queryofstring|Move_next|Move_prev|Close_suggestions|Open_suggestions[@@derivingsexp]endmoduleSuggestion_list_kind=structtypet=|Transient_overlay|Permanent_fixture[@@derivingsexp,compare,enumerate,equal]endmoduleExpand_direction=structtypet=|Down|Up[@@derivingsexp,compare,enumerate,equal]endletselect_key~first_try~then_try~else_use=matchfirst_trywith|Some(key,_)->Model.Selectedkey|None->(matchthen_trywith|(lazy(Some(key,_)))->Model.Selectedkey|(lazyNone)->else_use);;letcreate(typekcmp)(moduleKey:Bonsai.Comparatorwithtypet=kandtypecomparator_witness=cmp)?(initial_query="")?(max_visible_items=Value.return10)?(suggestion_list_kind=Value.returnSuggestion_list_kind.Transient_overlay)?(expand_direction=Value.returnExpand_direction.Down)?(selected_item_attr=Value.returnAttr.empty)?(extra_list_container_attr=Value.returnAttr.empty)?(extra_input_attr=Value.returnAttr.empty)?(extra_attr=Value.returnAttr.empty)~f~on_select()=let%sub{Model.query;suggestion_list_state},inject,items=Bonsai.wrap(modulestructtypet=Key.tModel.t[@@derivingsexp]letequalab=Model.equal(funab->Key.comparator.compareab=0)abend)~default_model:{Model.query=initial_query;suggestion_list_state=Closed}~apply_action:(fun~inject:_~schedule_event:_(_,_,items)modelaction->letsuggestion_list_state=(* We normalize which item is selected in case the list has changed
since the last action. Normalization just means setting the
selected key to the closest thing that actually exists. *)matchmodel.suggestion_list_statewith|Selectedkey->select_key~first_try:(Map.closest_keyitems`Less_or_equal_tokey)~then_try:(lazy(Map.closest_keyitems`Greater_or_equal_tokey))~else_use:First_item|First_item->First_item|Closed->Closedinmatchactionwith|Action.Set_queryquery->letsuggestion_list_state=matchsuggestion_list_statewith|Selectedkey->Model.Selectedkey|First_item|Closed->First_itemin{Model.query;suggestion_list_state}|Open_suggestions->{modelwithsuggestion_list_state=First_item}|Close_suggestions->{modelwithsuggestion_list_state=Closed}|Move_next->letsuggestion_list_state=matchsuggestion_list_statewith|Selectedkey->select_key~first_try:(Map.closest_keyitems`Greater_thankey)~then_try:(lazy(Map.min_eltitems))~else_use:(Selectedkey)|First_item->(matchMap.min_eltitemswith|None->First_item|Some(first_key,_)->(matchMap.closest_keyitems`Greater_thanfirst_keywith|None->Selectedfirst_key|Some(second_key,_)->Selectedsecond_key))|Closed->First_itemin{modelwithsuggestion_list_state}|Move_prev->letsuggestion_list_state=matchmodel.suggestion_list_statewith|Selectedkey->select_key~first_try:(Map.closest_keyitems`Less_thankey)~then_try:(lazy(Map.max_eltitems))~else_use:(Selectedkey)|First_item|Closed->(matchMap.max_eltitemswith|None->First_item|Some(last_key,_)->Selectedlast_key)in{modelwithsuggestion_list_state})~f:(funmodelinject->let%sub{Model.query;_}=returnmodelinlet%subitems=fqueryinlet%arrmodel=modelandinject=injectanditems=itemsinmodel,inject,items)inlet%subselected_key=match%subsuggestion_list_statewith|Selectedkey->let%arrkey=keyanditems=itemsin(matchMap.closest_keyitems`Less_or_equal_tokeywith|Some(key,_)->Somekey|None->(matchMap.closest_keyitems`Greater_or_equal_tokeywith|Some(key,_)->Somekey|None->None))|First_item->let%arritems=itemsin(matchMap.min_eltitemswith|Some(key,_)->Somekey|None->None)|Closed->Bonsai.constNoneinlet%subitems=Bonsai.assoc(moduleKey)items~f:(funkeyitem->let%arrkey=keyanditem=itemandselected_key=selected_keyandselected_item_attr=selected_item_attrinletattr=matchselected_keywith|Someselected_keywhenKey.comparator.comparekeyselected_key=0->selected_item_attr|_->Attr.emptyinNode.div~attr[item])inlet%subrestricted_items=let%arritems=itemsandmax_visible_items=max_visible_itemsandselected_key=selected_keyinmatchselected_keywith|Someselected_key->letlast_length=ref(-1)inletlength=ref0inletitems=refitemsinletresult=ref(Map.empty(moduleKey))in(* We alternate between taking something larger and smaller than the
selected key until we have taken [max_visible_items] or have exhausted
the source list. This is probably not done in the most efficient
manner, but it's O(max_visible_items * log(number_of_items)), which is
probably acceptable if [max_visible_items] is small. *)while!last_length<!lengthdolast_length:=!length;if!length<max_visible_itemsthen(matchMap.closest_key!items`Less_or_equal_toselected_keywith|Some(key,data)->result:=Map.set!result~key~data;items:=Map.remove!itemskey;incrlength|None->());if!length<max_visible_itemsthen(matchMap.closest_key!items`Greater_or_equal_toselected_keywith|Some(key,data)->result:=Map.set!result~key~data;items:=Map.remove!itemskey;incrlength|None->())done;Map.data!result|None->Sequence.take(Map.to_sequenceitems)(maxmax_visible_items0)|>Sequence.map~f:snd|>Sequence.to_listinlet%subhandle_keydown=let%arrinject=injectandselected_key=selected_keyandon_select=on_selectandexpand_direction=expand_directioninletopenVdominletopenJs_of_ocamlinfunev->letmove_next=Effect.Many[injectMove_next;Effect.Prevent_default]inletmove_prev=Effect.Many[injectMove_prev;Effect.Prevent_default]inletup,down=matchexpand_directionwith|Up->move_next,move_prev|Down->move_prev,move_nextinmatchDom_html.Keyboard_code.of_eventevwith|ArrowUp->up|TabwhenJs.to_boolev##.shiftKey->(matchselected_keywith|Some_->up|None->Effect.Ignore)|ArrowDown->down|Tab->(matchselected_keywith|Some_->down|None->Effect.Ignore)|Escape->injectAction.Close_suggestions|Enter->(matchselected_keywith|Somekey->Effect.Many[on_selectkey;inject(Set_query"");injectClose_suggestions;Effect.Prevent_default]|None->injectOpen_suggestions)|_->Effect.Ignoreinlet%arrquery=queryandselected_key=selected_keyandinject=injectandhandle_keydown=handle_keydownandsuggestion_list_kind=suggestion_list_kindandexpand_direction=expand_directionandrestricted_items=restricted_itemsandextra_list_container_attr=extra_list_container_attrandextra_input_attr=extra_input_attrandextra_attr=extra_attrinletcontainer_position,suggestions_position,is_open=matchsuggestion_list_kindwith|Suggestion_list_kind.Transient_overlay->letis_open=Option.is_someselected_keyin(Attr.style(Css_gen.position`Relative),Attr.style(Css_gen.position`Absolute),is_open)|Permanent_fixture->Attr.empty,Attr.empty,trueinletinput=Node.input~attr:Attr.(string_property"value"query@on_keydownhandle_keydown@on_input(fun_query->inject(Set_queryquery))@on_focus(fun_->injectOpen_suggestions)@on_blur(fun_->injectClose_suggestions)@extra_input_attr)[]inletsuggestions=matchis_openwith|false->Node.div[]|true->letposition_above_or_below,directed_items=matchexpand_directionwith|Up->Attr.style(Css_gen.bottom(`Px0)),List.revrestricted_items|Down->Attr.empty,restricted_itemsinletattr=Attr.(suggestions_position@position_above_or_below@extra_list_container_attr)inNode.div~attrdirected_itemsinletsuggestions_container=Node.div~attr:container_position[suggestions]inNode.div~attr:extra_attr(matchexpand_directionwith|Up->[suggestions_container;input]|Down->[input;suggestions_container]);;letstringable(typekcmp)(moduleKey:Bonsai.Comparatorwithtypet=kandtypecomparator_witness=cmp)?initial_query?max_visible_items?suggestion_list_kind?expand_direction?selected_item_attr?extra_list_container_attr?extra_input_attr?extra_attr?(to_view=fun_string->Vdom.Node.textstring)~on_selectinput=create(moduleKey)?initial_query?max_visible_items?suggestion_list_kind?expand_direction?selected_item_attr?extra_list_container_attr?extra_input_attr?extra_attr~on_select~f:(funquery->Bonsai.Incr.compute(Value.bothqueryinput)~f:(funincr->let%pattern_bind.Incrquery,input=incrinIncr_map.filter_mapi'input~f:(fun~key~data:string->let%map.Incrstring=stringandquery=queryinifFuzzy_match.is_match~char_equal:Char.Caseless.equal~pattern:querystringthenSome(to_viewkeystring)elseNone)))();;