123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273open!Coreopen!ImportopenMulti_factor_intfmoduletypeS=SmoduletypeKey=KeymoduleMake(Item:Single_factor.Item)(Key:Key)=structmoduleSingle_factor=Single_factor.Make(Item)moduleRing_focus=structmoduleAction=structtypet=|Cycle_focused_subwidgetof[`Next|`Prev]|Set_focused_subwidgetofKey.t[@@derivingsexp_of]endmoduleModel=structtypet=Key.tFocus_ring.t[@@derivingcompare,equal,sexp]endmoduleResult=structtypet=Key.t*(Action.t->unitBonsai_web.Effect.t)endmoduleInput=Unitletapply_action~inject:_~schedule_event:_()modelaction=match(action:Action.t)with|Cycle_focused_subwidget`Next->Focus_ring.nextmodel|Cycle_focused_subwidget`Prev->Focus_ring.prevmodel|Set_focused_subwidgetkey->Focus_ring.setmodel~f:(funkey'->[%compare.equal:Key.t]keykey')|>Option.value~default:model;;letcompute~inject()model=Focus_ring.current_focusmodel,injectletname=Source_code_position.to_string[%here]endmoduleAction=structtypet=|Cycle_focused_subwidgetof[`Next|`Prev]|Set_focused_subwidgetofKey.t|Subwidget_actionofKey.t*Single_factor.Action.t|Select_on_all_subwidgetsof[`All|`None][@@derivingsexp_of]endtypeper_subwidget={default_selection_status:Single_factor.Selection_status.t;all_items:Item.Set.t}[@@derivingfields]moduleResult=structtypet={view:Vdom.Node.t;view_for_testing:stringLazy.t;key_handler:Vdom_keyboard.Keyboard_event_handler.t;inject:Action.t->unitVdom.Effect.t;selection:Item.Set.tKey.Map.t}[@@derivingfields]letview_with_keydown_handlert=letopenVdominleton_keydown=Attr.on_keydown(funev->Vdom_keyboard.Keyboard_event_handler.handle_or_ignore_eventt.key_handlerev)inNode.div~attr:on_keydown[t.view];;endletsearch_box_idkey~id_prefix=sprintf!"%s-search-box-%{Key}"id_prefixkeyletview~inject~focus~subwidgets~id_prefix=letopenVdominletcross_subwidget_actions=letlink~text~(action:Action.t)=Node.a~attr:(Attr.many_without_merge[Attr.href"about:blank";Attr.on_click(fun_ev->Effect.Many[injectaction;Effect.Prevent_default])])[Node.texttext]inNode.div[Node.text(sprintf"Select on all %s: "Key.name_plural);link~text:"all"~action:(Select_on_all_subwidgets`All);Node.text"; ";link~text:"none"~action:(Select_on_all_subwidgets`None)]inletsubwidgets=Map.mapisubwidgets~f:(fun~key~data:result->letis_focused=[%compare.equal:Key.t]focuskeyinNode.div~attr:(Attr.many_without_merge[Attr.classes["multi-factor-subwidget";(ifis_focusedthen"multi-factor-focused-subwidget"else"multi-factor-unfocused-subwidget")];Attr.on_click(fun_ev->inject(Set_focused_subwidgetkey));Attr.id(sprintf!"%s-%{Key}"id_prefixkey)])[result.Single_factor.Result.view])inVdom_layout.as_vboxNode.div[cross_subwidget_actions;Vdom_layout.as_hboxNode.div(Map.datasubwidgets)];;letkey_handler~inject~focus~subwidgets=letopenVdom_keyboardinletmy_key_handler=letcommand?cond~keys~descriptionf=lethandler=letopenKeyboard_event_handler.Handlerinmatchcondwith|None->with_prevent_defaultf|Somecond->only_handle_ifcondf~prevent_default:()in{Keyboard_event_handler.Command.keys;description;group=None;handler}inletkey=Keystroke.create'inKeyboard_event_handler.of_command_list_exn[command~keys:[keyTab]~description:(sprintf"Focus next %s"Key.name_singular)(fun_ev->inject(Action.Cycle_focused_subwidget`Next));command~keys:[key~shift:()Tab]~description:(sprintf"Focus prev %s"Key.name_singular)(fun_ev->inject(Cycle_focused_subwidget`Prev))]inletfocused_subwidget_key_handler=letresult=Map.find_exnsubwidgetsfocusinresult.Single_factor.Result.key_handlerinKeyboard_event_handler.mergefocused_subwidget_key_handlermy_key_handler~on_dup:`Override_with_right;;letinject~subwidgets~inject_ring_focus_action=function|Action.Cycle_focused_subwidgetdir->inject_ring_focus_action(Ring_focus.Action.Cycle_focused_subwidgetdir)|Set_focused_subwidgetkey->inject_ring_focus_action(Set_focused_subwidgetkey)|Subwidget_action(key,a)->(matchMap.findsubwidgetskeywith|None->Bonsai.Effect.Ignore|Some{Single_factor.Result.inject;_}->injecta)|Select_on_all_subwidgetswhat->Bonsai.Effect.Many(List.map(Map.datasubwidgets)~f:(funsubwidget->subwidget.inject(matchwhatwith|`All->Select_all|`None->Select_none)));;letview_for_testing~subwidgets~focus=lazy(letcolumns=List.map(Map.keyssubwidgets)~f:(funkey->letname=sprintf!"%s %{Key}"(if[%compare.equal:Key.t]focuskeythen"*"else" ")keyinAscii_table_kernel.Column.createname(fun()->letsubwidget=Map.find_exnsubwidgetskeyinLazy.forcesubwidget.Single_factor.Result.view_for_testing))inAscii_table_kernel.drawcolumns[()]~limit_width_to:2000~prefer_split_on_spaces:false|>Option.value_exn|>Ascii_table_kernel.Screen.to_string~bars:`Unicode~string_with_attr:(fun_attrsstr->str));;letfocus_eltid=letopenJs_of_ocamlin(* In tests, there is no [document] object, so we can't focus elements. *)ifJs.Optdef.test(Js.defDom_html.document)thenOption.iter(Dom_html.getElementById_coerceidDom_html.CoerceTo.input)~f:(funelt->elt##focus;elt##select);;letfocus_elt=letf=Effect.of_sync_funfocus_eltinfun~id->fid;;letbonsai?(initial_model_settings=Key.Map.empty)~all_keys~id_prefixsubwidgets=letopenBonsai.Let_syntaxinletsingle_factorkeyinput=letdefault_selection_status=input>>|default_selection_statusinletinitial_model_settings=Map.findinitial_model_settingskey|>Option.value~default:(Single_factor.Initial_model_settings.create())inletview_config=let%mapid_prefix=id_prefixinSingle_factor.View_config.create~id:(search_box_idkey~id_prefix)~header:(Vdom.Node.text(Key.to_stringkey))()inSingle_factor.bonsai~initial_model_settings~default_selection_status~view_config(input>>|all_items)inlet%subsingle_factors=all_keys|>Set.to_map~f:(funkey->match%subsubwidgets>>|Fn.flipMap.findkeywith|Someinput->Computation.map(single_factorkeyinput)~f:Option.some|None->Bonsai.constNone)|>Computation.all_map|>Computation.map~f:(Map.filter_map~f:Fn.id)inlet%subfocus,inject_focus_action=Bonsai.of_module0(moduleRing_focus)~default_model:(Focus_ring.of_nonempty_list_exn(Set.to_listall_keys))inlet%sub()=letcallback=let%mapid_prefix=id_prefixinfunprevnew_focus->matchprevwith|Someprev_focuswhenKey.equalprev_focusnew_focus->Effect.Ignore|None|Some_->focus_elt~id:(search_box_idnew_focus~id_prefix)inBonsai.Edge.on_change'[%here](moduleKey)focus~callbackinreturn(let%mapsubwidgets=single_factorsandfocus=focusandinject_ring_focus_action=inject_focus_actionandid_prefix=id_prefixinletinject=inject~subwidgets~inject_ring_focus_actioninletselection=Map.mapsubwidgets~f:(funresult->result.Single_factor.Result.selected_items)inletview=view~inject~subwidgets~focus~id_prefixinletview_for_testing=view_for_testing~subwidgets~focusinletkey_handler=key_handler~inject~subwidgets~focusin{Result.selection;view;view_for_testing;key_handler;inject});;end