@yuneta/gobj-ui 7.23.157 → 7.23.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -543,8 +543,9 @@ let PRIVATE_DATA = {
543
543
  // it exists to diff the repaint)
544
544
  _pending_focus_topic: null, // focus requested before data was loaded
545
545
  _pending_focus_all: false, // ...and whether it reveals the whole topic
546
- _find_hidden_matches: 0, // matches of the last find in HIDDEN topics
547
- _pending_find: null, // find requested before data was loaded
546
+ _find_term: "", // the find box's term, repainted after every reconcile
547
+ _find_current: "", // node id the last Enter of the find centred on
548
+ _find_found: null, // ...and its last count (see find_matches)
548
549
  _layout_asked: "", // layout the host asked for at create (see mt_create)
549
550
  _nodes_total: 0, // records of the treedb (auto_layout)
550
551
  _nodes_placed: 0, // ...of which carry a saved position
@@ -3654,6 +3655,18 @@ function graph_focus_topic(gobj, topic, reveal_all)
3654
3655
  let model = priv._fold_model;
3655
3656
  let state = priv._fold_state;
3656
3657
 
3658
+ /* The two share the highlight slot. A topic takes it from the
3659
+ * find; an EMPTY topic (nothing focused) leaves a live find
3660
+ * alone, or clearing the legend's mark would wipe the find. */
3661
+ if(empty_string(topic) && priv._find_term) {
3662
+ priv._focus_topic = null;
3663
+ return;
3664
+ }
3665
+ if(!empty_string(topic)) {
3666
+ priv._find_term = "";
3667
+ priv._find_current = "";
3668
+ }
3669
+
3657
3670
  /* "Show me these": the ones on screen are highlighted, and the
3658
3671
  * hidden ones are opened up to. How many depends on who asks.
3659
3672
  * A click on the LEGEND (`reveal_all`) means every record of the
@@ -3689,18 +3702,18 @@ function graph_focus_topic(gobj, topic, reveal_all)
3689
3702
  priv._focus_topic = topic || null;
3690
3703
 
3691
3704
  reconcile_fold(gobj, {}).then(() => {
3692
- apply_focus_ids(gobj, keys, "focus topic");
3705
+ apply_focus_ids(gobj, keys, "focus topic", true);
3693
3706
  });
3694
3707
  }
3695
3708
 
3696
3709
  /************************************************************
3697
- * Paint the highlight on these records (by model key) and put
3698
- * the first of them in the middle. Shared by the find and the
3699
- * topic focus, after the reconcile that brought them on screen.
3700
- * Keys that are still off screen are skipped: a match beyond the
3701
- * page a reveal opened is counted, not painted.
3710
+ * Paint the highlight on these records (by model key) and, with
3711
+ * `center`, put the first of them in the middle. Shared by the
3712
+ * find and the topic focus. Keys that are not on screen are
3713
+ * skipped: a match beyond the page a reveal opened is counted,
3714
+ * not painted.
3702
3715
  ************************************************************/
3703
- function apply_focus_ids(gobj, keys, what)
3716
+ function apply_focus_ids(gobj, keys, what, center)
3704
3717
  {
3705
3718
  let priv = gobj.priv;
3706
3719
  let graph = priv.graph;
@@ -3734,7 +3747,7 @@ function apply_focus_ids(gobj, keys, what)
3734
3747
  graph.setElementState(states);
3735
3748
  }
3736
3749
  apply_node_highlight(gobj, prev_ids, ids);
3737
- if(ids.length) {
3750
+ if(center && ids.length) {
3738
3751
  yui_graph_center_on(graph, ids[0]);
3739
3752
  }
3740
3753
  } catch(e) {
@@ -3753,94 +3766,186 @@ function node_id_of_key(gobj, key)
3753
3766
  }
3754
3767
 
3755
3768
  /************************************************************
3756
- * Find the nodes whose name, id or topic contains `term`, highlight
3757
- * them with the same amber 'active' state the topic focus uses, and
3758
- * centre the viewport on them. Returns how many matched, which is the
3759
- * half the toolbar needs: a search that finds nothing must SAY so,
3760
- * because a graph that did not move is also what a graph looks like
3761
- * when the match is off-screen.
3769
+ * The find box: highlight the cards ON SCREEN whose name, id or
3770
+ * topic contains `term`, and change nothing else -- no group is
3771
+ * opened, no layout runs, the camera does not move. The graph the
3772
+ * reader arranged is the graph they are searching; emptying the
3773
+ * box takes the amber off and leaves it exactly as it was.
3774
+ *
3775
+ * It used to search the whole treedb and unfold a page of what it
3776
+ * found, which re-laid the graph out on every keystroke and left
3777
+ * the unfolded groups behind when the box was cleared. Opening up
3778
+ * to a topic is the legend's focus (graph_focus_topic); this only
3779
+ * LOOKS.
3780
+ *
3781
+ * The term is kept and repainted after every reconcile (see
3782
+ * reconcile_fold_now): a group opened by hand shows its matches
3783
+ * already lit, and the count follows what is on screen.
3762
3784
  *
3763
3785
  * It shares the highlight slot with graph_focus_topic on purpose —
3764
- * two amber sets at once would say nothing about either. An empty
3765
- * term clears it. Matching reads the node's LABEL and not only its
3766
- * id: on a topic keyed by rowid or by a qualified path, the id is a
3767
- * counter and the name a human knows is elsewhere (see node_label).
3786
+ * two amber sets at once would say nothing about either. Matching
3787
+ * reads the node's LABEL and not only its id: on a topic keyed by
3788
+ * rowid or by a qualified path, the id is a counter and the name a
3789
+ * human knows is elsewhere (see node_label).
3768
3790
  ************************************************************/
3769
3791
  function graph_find_nodes(gobj, term)
3770
3792
  {
3771
3793
  let priv = gobj.priv;
3772
- let graph = priv.graph;
3773
- if(!graph || !priv.graph_rendered || !priv._fold_model) {
3774
- priv._pending_find = term;
3775
- return 0;
3794
+ let next = empty_string(term)? "" : String(term);
3795
+ let was = priv._find_term;
3796
+
3797
+ priv._find_term = next;
3798
+ priv._find_current = "";
3799
+ priv._find_found = null;
3800
+
3801
+ let ready = priv.graph && priv.graph_rendered && priv._fold_model;
3802
+ if(!next) {
3803
+ /* Only a find that HELD the slot is taken off: an empty box
3804
+ * that was already empty must not wipe a topic focus. */
3805
+ if(was && ready && !priv._focus_topic) {
3806
+ apply_focus_ids(gobj, [], "find", false);
3807
+ }
3808
+ publish_find_result(gobj);
3809
+ return;
3810
+ }
3811
+
3812
+ priv._focus_topic = null; /* a find takes over the highlight */
3813
+ if(!ready) {
3814
+ /* Painted by the first reconcile, when the data arrives. */
3815
+ return;
3816
+ }
3817
+ refresh_find(gobj, fold_visible_set(priv._fold_model, priv._fold_state));
3818
+ }
3819
+
3820
+ /************************************************************
3821
+ * Repaint the find's highlight over the `visible` set and publish
3822
+ * the count. A no-op while no term is live, so the topic focus
3823
+ * keeps the slot.
3824
+ ************************************************************/
3825
+ function refresh_find(gobj, visible)
3826
+ {
3827
+ let priv = gobj.priv;
3828
+ if(!priv._find_term) {
3829
+ return;
3830
+ }
3831
+
3832
+ let found = find_matches(gobj, priv._find_term, visible);
3833
+ apply_focus_ids(gobj, found.keys, "find", false);
3834
+ if(priv._find_current && (priv._focus_ids || []).indexOf(priv._find_current) < 0) {
3835
+ priv._find_current = "";
3776
3836
  }
3837
+ priv._find_found = found;
3838
+ publish_find_result(gobj);
3839
+ }
3777
3840
 
3841
+ /************************************************************
3842
+ * The records matching `term`: the keys ON SCREEN, and how many
3843
+ * more match that are NOT -- folded away in the fold model, or in
3844
+ * a hidden topic. Those two are only counted: "0" alone would read
3845
+ * as "does not exist" when it means "is not on screen".
3846
+ ************************************************************/
3847
+ function find_matches(gobj, term, visible)
3848
+ {
3849
+ let priv = gobj.priv;
3778
3850
  let model = priv._fold_model;
3779
- let state = priv._fold_state;
3851
+ let found = {keys: [], folded: 0, hidden: 0};
3852
+ if(!model || empty_string(term)) {
3853
+ return found;
3854
+ }
3780
3855
 
3781
- /* Searched over the RECORDS, not the cards: most of the treedb is
3782
- * folded away, and a find that only read what was on screen would
3783
- * answer "nothing" for a device three levels down. */
3784
- let keys = [];
3785
- let hidden_matches = 0;
3786
- if(!empty_string(term)) {
3787
- let needle = String(term).toLowerCase();
3788
- let matches = (topic_name, record) => {
3789
- let label = "";
3790
- try {
3791
- label = node_label(priv.descs[topic_name] || {}, record) || "";
3792
- } catch(e) {
3793
- label = "";
3794
- }
3795
- let haystack = [
3796
- label,
3797
- record.id,
3798
- topic_name
3799
- ].filter((v) => typeof v === "string").join(" ").toLowerCase();
3800
- return haystack.includes(needle);
3801
- };
3802
- for(let node of model.nodes.values()) {
3803
- if(matches(node.topic_name, node.record || {})) {
3804
- keys.push(node.key);
3856
+ let needle = String(term).toLowerCase();
3857
+ let matches = (topic_name, record) => {
3858
+ let label = "";
3859
+ try {
3860
+ label = node_label(priv.descs[topic_name] || {}, record) || "";
3861
+ } catch(e) {
3862
+ label = "";
3863
+ }
3864
+ let haystack = [
3865
+ label,
3866
+ record.id,
3867
+ topic_name
3868
+ ].filter((v) => typeof v === "string").join(" ").toLowerCase();
3869
+ return haystack.includes(needle);
3870
+ };
3871
+
3872
+ for(let node of model.nodes.values()) {
3873
+ if(matches(node.topic_name, node.record || {})) {
3874
+ if(visible.has(node.key)) {
3875
+ found.keys.push(node.key);
3876
+ } else {
3877
+ found.folded++;
3805
3878
  }
3806
3879
  }
3807
- /* A hidden topic is searched too, and COUNTED apart: a term
3808
- * that only matches there would otherwise answer "0", which
3809
- * reads as "does not exist" when it means "is hidden". */
3810
- for(let topic_name of model.hidden) {
3811
- for(let record of (priv.records[topic_name] || [])) {
3812
- if(is_object(record) && matches(topic_name, record)) {
3813
- hidden_matches++;
3814
- }
3880
+ }
3881
+ for(let topic_name of model.hidden) {
3882
+ for(let record of (priv.records[topic_name] || [])) {
3883
+ if(is_object(record) && matches(topic_name, record)) {
3884
+ found.hidden++;
3815
3885
  }
3816
3886
  }
3817
3887
  }
3818
- priv._find_hidden_matches = hidden_matches;
3888
+ return found;
3889
+ }
3819
3890
 
3820
- /* Hidden matches are opened up to, one page of them at most: a
3821
- * single letter matches half the treedb, and unfolding half the
3822
- * treedb on a keystroke is the pile this whole thing exists to
3823
- * avoid. The count reported is of all the matches. */
3824
- let visible = fold_visible_set(model, state);
3825
- let revealed = 0;
3826
- for(let key of keys) {
3827
- if(visible.has(key)) {
3828
- continue;
3829
- }
3830
- if(revealed >= state.page_size) {
3831
- break;
3832
- }
3833
- fold_reveal(model, state, key);
3834
- revealed++;
3891
+ /************************************************************
3892
+ * The lit cards in reading order: top to bottom, then left to
3893
+ * right. The order Enter walks, and the one the "k/N" counts in.
3894
+ ************************************************************/
3895
+ function find_order(gobj)
3896
+ {
3897
+ let priv = gobj.priv;
3898
+ let graph = priv.graph;
3899
+ let ids = (priv._focus_ids || []).slice();
3900
+ if(!graph) {
3901
+ return ids;
3835
3902
  }
3836
3903
 
3837
- priv._focus_topic = null; /* a find takes over the highlight */
3904
+ let pos = {};
3905
+ for(let id of ids) {
3906
+ let p = null;
3907
+ try {
3908
+ p = graph.getElementPosition(id);
3909
+ } catch(e) {
3910
+ p = null;
3911
+ }
3912
+ pos[id] = p || [0, 0];
3913
+ }
3914
+ ids.sort((a, b) => (pos[a][1] - pos[b][1]) || (pos[a][0] - pos[b][0]));
3915
+ return ids;
3916
+ }
3838
3917
 
3839
- reconcile_fold(gobj, {}).then(() => {
3840
- apply_focus_ids(gobj, keys, "find");
3841
- });
3918
+ /************************************************************
3919
+ * Enter in the find box: centre the next lit card (`back`: the
3920
+ * previous one). The one camera move of the find, and the reader
3921
+ * asks for it: the highlight alone cannot take anybody to a match
3922
+ * that is outside the frame.
3923
+ ************************************************************/
3924
+ function graph_find_step(gobj, back)
3925
+ {
3926
+ let priv = gobj.priv;
3927
+ let graph = priv.graph;
3928
+ if(!graph || !priv._find_term) {
3929
+ return;
3930
+ }
3931
+ let ids = find_order(gobj);
3932
+ if(ids.length === 0) {
3933
+ return;
3934
+ }
3842
3935
 
3843
- return keys.length;
3936
+ let i = ids.indexOf(priv._find_current);
3937
+ if(i < 0) {
3938
+ i = back? ids.length - 1 : 0;
3939
+ } else {
3940
+ i = (i + (back? ids.length - 1 : 1)) % ids.length;
3941
+ }
3942
+ priv._find_current = ids[i];
3943
+ try {
3944
+ yui_graph_center_on(graph, ids[i]);
3945
+ } catch(e) {
3946
+ log_error(`${gobj_short_name(gobj)}: find step failed: ${e}`);
3947
+ }
3948
+ publish_find_result(gobj);
3844
3949
  }
3845
3950
 
3846
3951
  async function graph_zoom_in(gobj)
@@ -9648,6 +9753,7 @@ async function reconcile_fold_now(gobj, opts)
9648
9753
  history_resume(gobj);
9649
9754
  }
9650
9755
  publish_legend_state(gobj, visible);
9756
+ refresh_find(gobj, visible);
9651
9757
  return changed;
9652
9758
  }
9653
9759
 
@@ -10223,11 +10329,6 @@ function ac_load_data(gobj, event, kw, src)
10223
10329
  priv._pending_focus_all = false;
10224
10330
  graph_focus_topic(gobj, ft, all);
10225
10331
  }
10226
- if(priv._pending_find !== null) {
10227
- let term = priv._pending_find;
10228
- priv._pending_find = null;
10229
- publish_find_result(gobj, term, graph_find_nodes(gobj, term));
10230
- }
10231
10332
  });
10232
10333
  }
10233
10334
 
@@ -11054,16 +11155,30 @@ function ac_node_dblclick(gobj, event, kw, src)
11054
11155
  }
11055
11156
 
11056
11157
  /************************************************************
11057
- * Tell whoever asked how many nodes the term matched. The count is
11058
- * the answer to the question the box asks; without it "nothing moved"
11059
- * and "nothing matched" look the same.
11158
+ * Tell whoever asked how many cards the term lit. The count is the
11159
+ * answer to the question the box asks; without it "nothing lit" and
11160
+ * "nothing matched" look the same.
11161
+ *
11162
+ * matches lit cards (on screen)
11163
+ * folded_matches matches folded away, not on screen
11164
+ * hidden_matches matches in hidden topics
11165
+ * current 1-based place of the card Enter centred on
11166
+ * among the lit ones (reading order), 0 = none
11060
11167
  ************************************************************/
11061
- function publish_find_result(gobj, term, matches)
11168
+ function publish_find_result(gobj)
11062
11169
  {
11170
+ let priv = gobj.priv;
11171
+ let found = priv._find_found || {keys: [], folded: 0, hidden: 0};
11172
+ let current = 0;
11173
+ if(priv._find_term && priv._find_current) {
11174
+ current = find_order(gobj).indexOf(priv._find_current) + 1;
11175
+ }
11063
11176
  gobj_publish_event(gobj, "EV_FIND_RESULT", {
11064
- term: term,
11065
- matches: matches,
11066
- hidden_matches: gobj.priv._find_hidden_matches || 0,
11177
+ term: priv._find_term,
11178
+ matches: priv._find_term? (priv._focus_ids || []).length : 0,
11179
+ folded_matches: found.folded,
11180
+ hidden_matches: found.hidden,
11181
+ current: current,
11067
11182
  });
11068
11183
  }
11069
11184
 
@@ -11072,9 +11187,16 @@ function publish_find_result(gobj, term, matches)
11072
11187
  ************************************************************/
11073
11188
  function ac_find_nodes(gobj, event, kw, src)
11074
11189
  {
11075
- let term = (kw && kw.text) || "";
11076
- let matches = graph_find_nodes(gobj, term);
11077
- publish_find_result(gobj, term, matches);
11190
+ graph_find_nodes(gobj, (kw && kw.text) || "");
11191
+ return 0;
11192
+ }
11193
+
11194
+ /************************************************************
11195
+ * Enter in the find box: {back: true} for Shift+Enter.
11196
+ ************************************************************/
11197
+ function ac_find_next(gobj, event, kw, src)
11198
+ {
11199
+ graph_find_step(gobj, !!(kw && kw.back));
11078
11200
  return 0;
11079
11201
  }
11080
11202
 
@@ -11636,6 +11758,7 @@ function create_gclass(gclass_name)
11636
11758
  ["EV_ZOOM_SELECTION", ac_zoom_selection, null],
11637
11759
  ["EV_FOCUS_TOPIC", ac_focus_topic, null],
11638
11760
  ["EV_FIND_NODES", ac_find_nodes, null],
11761
+ ["EV_FIND_NEXT", ac_find_next, null],
11639
11762
  ["EV_TOGGLE_FOLD", ac_toggle_fold, null],
11640
11763
  ["EV_SHOW_MORE", ac_show_more, null],
11641
11764
  ["EV_SET_FOLD_LEVEL", ac_set_fold_level, null],
@@ -11699,6 +11822,7 @@ function create_gclass(gclass_name)
11699
11822
  ["EV_ZOOM_SELECTION", 0],
11700
11823
  ["EV_FOCUS_TOPIC", 0],
11701
11824
  ["EV_FIND_NODES", 0],
11825
+ ["EV_FIND_NEXT", 0],
11702
11826
  ["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
11703
11827
  ["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
11704
11828
  /* Optional: a host that does not persist the viewport is not
@@ -80,7 +80,7 @@ import {
80
80
  } from "@yuneta/gobj-js";
81
81
 
82
82
 
83
- import {attach_clear} from "./yui_inputs.js";
83
+ import {attach_clear, refresh_clear} from "./yui_inputs.js";
84
84
  import {register_c_g6_nodes_tree} from "./c_g6_nodes_tree.js";
85
85
  import {yui_toolbar, yui_toolbar_icon, YUI_TOOLBAR_ICON_SIZE} from "./yui_toolbar.js";
86
86
  import {
@@ -982,11 +982,13 @@ function make_toolbar(gobj)
982
982
  * Right: find a node.
983
983
  *
984
984
  * A graph of a few hundred records has no other way in: the only way
985
- * to locate one was to read every card. The box highlights every
986
- * match with the same amber the topic focus uses and centres the
987
- * viewport on them, and it SAYS how many it found — a graph that did
988
- * not move looks the same whether nothing matched or the match was
989
- * already on screen.
985
+ * to locate one was to read every card. The box lights every match
986
+ * ON SCREEN with the same amber the topic focus uses and changes
987
+ * nothing else (no unfold, no layout, no camera); Enter centres the
988
+ * next lit card, Shift+Enter the previous. It SAYS how many it found,
989
+ * and how many more are folded away or in hidden topics — a graph
990
+ * with no amber looks the same whether nothing matched or the matches
991
+ * are not on screen.
990
992
  */
991
993
  /* Materialised, not a spec: attach_clear() hangs the NORM clear (✕)
992
994
  * on a real element. Clearing dispatches a synthetic `input`, which
@@ -1000,8 +1002,12 @@ function make_toolbar(gobj)
1000
1002
  * cannot reach it: it needs its own key. */
1001
1003
  placeholder: t('search'),
1002
1004
  'data-i18n-placeholder': 'search',
1003
- 'aria-label': t('search'),
1004
- 'data-i18n-aria-label': 'search'
1005
+ /* The title is where Enter is told: nothing else on the
1006
+ * box says it can step through the matches. */
1007
+ title: t('find on screen'),
1008
+ 'data-i18n-title': 'find on screen',
1009
+ 'aria-label': t('find on screen'),
1010
+ 'data-i18n-aria-label': 'find on screen'
1005
1011
  }, [], {
1006
1012
  /* Rate-limited, not delayed for effect: a match repaints the
1007
1013
  * cards it lands on, and on a large treedb the first letter
@@ -1018,6 +1024,24 @@ function make_toolbar(gobj)
1018
1024
  priv.find_timer = null;
1019
1025
  gobj_send_event(gobj, "EV_FIND_NODES", {text: text}, gobj);
1020
1026
  }, 250);
1027
+ },
1028
+ /* Enter steps through the lit cards. A term still waiting
1029
+ * on the rate limit is sent first, or Enter would step
1030
+ * through the matches of the term before it. */
1031
+ keydown: (evt) => {
1032
+ if(evt.key !== 'Enter' || evt.isComposing) {
1033
+ return;
1034
+ }
1035
+ evt.preventDefault();
1036
+ evt.stopPropagation();
1037
+ if(priv.find_timer) {
1038
+ clearTimeout(priv.find_timer);
1039
+ priv.find_timer = null;
1040
+ gobj_send_event(
1041
+ gobj, "EV_FIND_NODES", {text: evt.target.value.trim()}, gobj
1042
+ );
1043
+ }
1044
+ gobj_send_event(gobj, "EV_FIND_NEXT", {back: evt.shiftKey}, gobj);
1021
1045
  }
1022
1046
  }]);
1023
1047
 
@@ -3188,6 +3212,23 @@ function ac_find_nodes(gobj, event, kw, src)
3188
3212
  return 0;
3189
3213
  }
3190
3214
 
3215
+ /************************************************************
3216
+ * Enter in the find box: forward the step down to the graph child.
3217
+ ************************************************************/
3218
+ function ac_find_next(gobj, event, kw, src)
3219
+ {
3220
+ let priv = gobj.priv;
3221
+ if(priv.gobj_nodes_tree) {
3222
+ gobj_send_event(
3223
+ priv.gobj_nodes_tree,
3224
+ "EV_FIND_NEXT",
3225
+ {back: !!(kw && kw.back)},
3226
+ gobj
3227
+ );
3228
+ }
3229
+ return 0;
3230
+ }
3231
+
3191
3232
  /************************************************************
3192
3233
  * One press of the fold stepper (EV_EXPAND_LEVEL /
3193
3234
  * EV_COLLAPSE_LEVEL): the level moves by one, is SAVED (the
@@ -3307,17 +3348,47 @@ function refresh_find_result(gobj)
3307
3348
  }
3308
3349
 
3309
3350
  let text = t("matches", {count: (kw && kw.matches) || 0});
3310
- /* Matches in HIDDEN topics, counted apart: "0" alone reads as
3311
- * "does not exist" when it means "is hidden". */
3351
+ /* Where Enter stands among the lit cards: "3/12 matches". */
3352
+ let current = (kw && kw.current) || 0;
3353
+ if(current > 0) {
3354
+ text = `${current}/${text}`;
3355
+ }
3356
+ /* Matches NOT lit, counted apart: "0" alone reads as "does not
3357
+ * exist" when it means "is folded away" or "is hidden". */
3358
+ let extra = [];
3359
+ let folded = (kw && kw.folded_matches) || 0;
3360
+ if(folded > 0) {
3361
+ extra.push(`+${folded} ${t("not shown")}`);
3362
+ }
3312
3363
  let hidden = (kw && kw.hidden_matches) || 0;
3313
3364
  if(hidden > 0) {
3314
- text += ` (+${hidden} ${t("hidden topics")})`;
3365
+ extra.push(`+${hidden} ${t("hidden topics")}`);
3366
+ }
3367
+ if(extra.length) {
3368
+ text += ` (${extra.join(", ")})`;
3315
3369
  }
3316
3370
  $count.textContent = text;
3317
3371
  $result.classList.remove("is-hidden");
3318
3372
  return 0;
3319
3373
  }
3320
3374
 
3375
+ function clear_find_box(gobj)
3376
+ {
3377
+ let priv = gobj.priv;
3378
+ if(priv.find_timer) {
3379
+ clearTimeout(priv.find_timer);
3380
+ priv.find_timer = null;
3381
+ }
3382
+ let $container = gobj_read_attr(gobj, "$container");
3383
+ let $input = $container? $container.querySelector(".GRAPH_FIND_INPUT") : null;
3384
+ if($input) {
3385
+ $input.value = "";
3386
+ refresh_clear($input);
3387
+ }
3388
+ priv._find_result = null;
3389
+ refresh_find_result(gobj);
3390
+ }
3391
+
3321
3392
  function ac_set_focus_topic(gobj, event, kw, src)
3322
3393
  {
3323
3394
  let priv = gobj.priv;
@@ -3334,6 +3405,13 @@ function ac_set_focus_topic(gobj, event, kw, src)
3334
3405
  }
3335
3406
  }
3336
3407
 
3408
+ /* A topic focus takes the highlight from the find (the engine
3409
+ * drops its term), so the box must not go on showing a term
3410
+ * nothing is lit for. */
3411
+ if(priv.focus_topic) {
3412
+ clear_find_box(gobj);
3413
+ }
3414
+
3337
3415
  if(priv.gobj_nodes_tree) {
3338
3416
  gobj_send_event(
3339
3417
  priv.gobj_nodes_tree,
@@ -3435,6 +3513,7 @@ function create_gclass(gclass_name)
3435
3513
  ["EV_SET_OPERATION_MODE", ac_set_operation_mode, null],
3436
3514
  ["EV_SET_FOCUS_TOPIC", ac_set_focus_topic, null],
3437
3515
  ["EV_FIND_NODES", ac_find_nodes, null],
3516
+ ["EV_FIND_NEXT", ac_find_next, null],
3438
3517
  ["EV_EXPAND_LEVEL", ac_fold_level_step, null],
3439
3518
  ["EV_COLLAPSE_LEVEL", ac_fold_level_step, null],
3440
3519
  ["EV_SET_NODE_MODE", ac_set_node_mode, null],
@@ -3482,6 +3561,7 @@ function create_gclass(gclass_name)
3482
3561
  ["EV_SET_OPERATION_MODE", 0],
3483
3562
  ["EV_SET_FOCUS_TOPIC", 0],
3484
3563
  ["EV_FIND_NODES", 0],
3564
+ ["EV_FIND_NEXT", 0],
3485
3565
  ["EV_EXPAND_LEVEL", 0],
3486
3566
  ["EV_COLLAPSE_LEVEL", 0],
3487
3567
  ["EV_SET_NODE_MODE", 0],