@yuneta/gobj-ui 7.21.1 → 7.23.0

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.
@@ -67,6 +67,13 @@ import { yui_is_dark, yui_theme_now, yui_watch_theme } from "./yui_theme.js";
67
67
  ***************************************************************/
68
68
  const GCLASS_NAME = "C_YUI_JSON_GRAPH";
69
69
 
70
+ /*
71
+ * What a click on a fold handle has to swallow so G6 never turns it
72
+ * into a node click. `click` is the one that ACTS; the rest are cut
73
+ * because G6 assembles its own click from the pointer sequence.
74
+ */
75
+ const FOLD_SWALLOWED_EVENTS = ["pointerdown", "pointerup", "mousedown", "click"];
76
+
70
77
  /***************************************************************
71
78
  * Data
72
79
  ***************************************************************/
@@ -96,6 +103,16 @@ let PRIVATE_DATA = {
96
103
  theme_observer: null, // MutationObserver on <html data-theme>
97
104
  resize_observer: null, // ResizeObserver on the canvas mount element
98
105
  _resize_raf: 0, // rAF id debouncing resize -> EV_RESIZE
106
+
107
+ /*---------------- find + collapse ----------------*/
108
+ search: "", // current find term, lower-cased
109
+ collapsed: null, // Set<string> of paths whose children are hidden
110
+ match_count: 0, // matches of the last find, for the count chip
111
+ find_timer: null, // rate-limits the find box
112
+ fold_listener: null, // delegated click on the card fold handles
113
+ $find_input: null,
114
+ $find_result: null,
115
+ $find_count: null,
99
116
  };
100
117
 
101
118
  let __gclass__ = null;
@@ -180,6 +197,9 @@ function mt_create(gobj)
180
197
  {
181
198
  let priv = gobj.priv;
182
199
 
200
+ priv.collapsed = new Set();
201
+ priv.search = "";
202
+
183
203
  let name = clean_name(gobj_name(gobj));
184
204
  priv.canvas_id = "json-canvas-" + name;
185
205
  gobj_write_str_attr(gobj, "canvas_id", priv.canvas_id);
@@ -233,6 +253,10 @@ function mt_stop(gobj)
233
253
  ***************************************************************/
234
254
  function mt_destroy(gobj)
235
255
  {
256
+ if(gobj.priv.find_timer) {
257
+ clearTimeout(gobj.priv.find_timer);
258
+ gobj.priv.find_timer = null;
259
+ }
236
260
  let priv = gobj.priv;
237
261
 
238
262
  if(priv.theme_observer) {
@@ -286,6 +310,51 @@ function build_ui(gobj)
286
310
  );
287
311
 
288
312
  gobj_write_attr(gobj, "$container", $container);
313
+
314
+ /*
315
+ * The fold handles live inside an `innerHTML` string, so no handler
316
+ * can be attached to them: the click is delegated from the canvas
317
+ * mount, in the CAPTURE phase, over FOUR event types.
318
+ *
319
+ * Capture because G6 binds on the html node ELEMENT, a descendant
320
+ * of this one: in the bubble phase G6 has already run by the time
321
+ * this listener sees the event, so stopping it there stops nothing.
322
+ *
323
+ * Four types because G6 does not use the DOM `click` at all — it
324
+ * builds its own from the POINTER sequence. Swallowing `click`
325
+ * alone left the fold working and the card ALSO reporting an item
326
+ * click; `pointerdown`/`pointerup` are the pair that has to be cut,
327
+ * and `mousedown` goes with them for a browser that still sends it.
328
+ * Measured by instrumenting the mount, not guessed.
329
+ */
330
+ let $mount = $container.querySelector('#' + priv.canvas_id);
331
+ if($mount) {
332
+ priv.fold_listener = function(evt) {
333
+ let $handle = evt.target && evt.target.closest
334
+ ? evt.target.closest('[data-fold-path]')
335
+ : null;
336
+ if(!$handle) {
337
+ return;
338
+ }
339
+ evt.stopPropagation();
340
+ evt.preventDefault();
341
+ if(evt.type !== "click") {
342
+ return; /* swallowed so G6 never sees a node click */
343
+ }
344
+ gobj_send_event(gobj, "EV_TOGGLE_FOLD",
345
+ {path: $handle.getAttribute('data-fold-path')}, gobj);
346
+ };
347
+ for(let type of FOLD_SWALLOWED_EVENTS) {
348
+ $mount.addEventListener(type, priv.fold_listener, true);
349
+ }
350
+ } else {
351
+ log_error(`${gobj_short_name(gobj)}: no canvas mount, fold handles are dead`);
352
+ }
353
+
354
+ priv.$find_input = $container.querySelector('.JSON_GRAPH_FIND_INPUT');
355
+ priv.$find_result = $container.querySelector('.JSON_GRAPH_FIND_RESULT');
356
+ priv.$find_count = $container.querySelector('.JSON_GRAPH_FIND_COUNT');
357
+
289
358
  refresh_language($container, t);
290
359
  }
291
360
 
@@ -294,7 +363,19 @@ function build_ui(gobj)
294
363
  ************************************************************/
295
364
  function destroy_ui(gobj)
296
365
  {
366
+ let priv = gobj.priv;
297
367
  let $container = gobj_read_attr(gobj, "$container");
368
+
369
+ if(priv.fold_listener) {
370
+ let $mount = $container? $container.querySelector('#' + priv.canvas_id): null;
371
+ if($mount) {
372
+ for(let type of FOLD_SWALLOWED_EVENTS) {
373
+ $mount.removeEventListener(type, priv.fold_listener, true);
374
+ }
375
+ }
376
+ priv.fold_listener = null;
377
+ }
378
+
298
379
  if($container) {
299
380
  if($container.parentNode) {
300
381
  $container.parentNode.removeChild($container);
@@ -303,6 +384,30 @@ function destroy_ui(gobj)
303
384
  }
304
385
  }
305
386
 
387
+ /************************************************************
388
+ * One fold button. `open` rotates the chevron to the open
389
+ * state, mirroring the per-node toggle of the lazy tree.
390
+ ************************************************************/
391
+ function graph_fold_button(gobj, event_name, label_key, open)
392
+ {
393
+ let icon_style = 'font-size:1.5em; color:inherit;' +
394
+ (open? ' transform: rotate(90deg);': '');
395
+
396
+ return ['button', {class: `button ${event_name}`,
397
+ type: 'button',
398
+ style: {height: gobj_read_attr(gobj, "wide"), width: '2.5em'},
399
+ title: t(label_key), 'data-i18n-title': label_key,
400
+ 'aria-label': t(label_key), 'data-i18n-aria-label': label_key},
401
+ ['i', {style: icon_style, class: 'yi-chevron-right'}],
402
+ {
403
+ click: (evt) => {
404
+ evt.stopPropagation();
405
+ gobj_send_event(gobj, event_name, {}, gobj);
406
+ }
407
+ }
408
+ ];
409
+ }
410
+
306
411
  /************************************************************
307
412
  * Toolbar
308
413
  ************************************************************/
@@ -311,7 +416,62 @@ function make_toolbar(gobj)
311
416
  let priv = gobj.priv;
312
417
  let toolbar_wide = gobj_read_attr(gobj, "wide");
313
418
 
314
- let left_items = [];
419
+ /*
420
+ * Left: find a node.
421
+ *
422
+ * A graph of a large document has no other way in — the only way to
423
+ * locate a key was to read every card. Same shape as the treedb
424
+ * graph's box: a rate-limited input into EV_FIND_NODES, and a count
425
+ * that SAYS how many matched, because a graph that did not move
426
+ * looks the same whether nothing matched or the match was already
427
+ * on screen.
428
+ */
429
+ let left_items = [
430
+ ['div', {class: 'JSON_GRAPH_FIND control has-icons-left',
431
+ style: 'margin-right:.5rem; max-width:12rem; min-width:7rem;'}, [
432
+ ['input', {
433
+ class: 'JSON_GRAPH_FIND_INPUT input',
434
+ type: 'text',
435
+ /* A placeholder is not a text node, so the data-i18n
436
+ * walk cannot reach it: it needs its own key. */
437
+ placeholder: t('search'),
438
+ 'data-i18n-placeholder': 'search',
439
+ 'aria-label': t('search'),
440
+ 'data-i18n-aria-label': 'search'
441
+ }],
442
+ ['span', {class: 'icon is-left'}, [['i', {class: 'yi-magnifying-glass'}]]]
443
+ ], {
444
+ /* Rate-limited, not delayed for effect: every keystroke
445
+ * REBUILDS the cards — an html node paints no G6 state, so
446
+ * the highlight has to live in the card's own markup — and
447
+ * the first letter typed can match most of a document.
448
+ * Input plumbing: the action still crosses the FSM, just
449
+ * not once per keystroke. */
450
+ input: (evt) => {
451
+ evt.stopPropagation();
452
+ let text = evt.target.value.trim();
453
+ if(priv.find_timer) {
454
+ clearTimeout(priv.find_timer);
455
+ }
456
+ priv.find_timer = setTimeout(function() {
457
+ priv.find_timer = null;
458
+ gobj_send_event(gobj, "EV_FIND_NODES", {text: text}, gobj);
459
+ }, 250);
460
+ }
461
+ }],
462
+ /* Two spans, not one string: the number is DATA and "matches"
463
+ * is the word, so a language switch re-translates the half that
464
+ * is a word. Spaced with CSS because createElement2 trims text
465
+ * nodes. `display:flex` inline and NOT the `is-flex` helper —
466
+ * both Bulma helpers carry !important, so `is-hidden is-flex` on
467
+ * one element is decided by stylesheet order. */
468
+ ['div', {class: 'JSON_GRAPH_FIND_RESULT is-hidden',
469
+ style: 'display:flex; align-items:center; gap:.3rem; ' +
470
+ 'margin-right:.5rem; font-size:.85rem;'}, [
471
+ ['span', {class: 'JSON_GRAPH_FIND_COUNT'}, ''],
472
+ ['span', {i18n: 'matches'}, 'matches']
473
+ ]]
474
+ ];
315
475
  let center_items = [];
316
476
 
317
477
  let c_icons = [
@@ -347,10 +507,24 @@ function make_toolbar(gobj)
347
507
  );
348
508
  }
349
509
 
510
+ /*
511
+ * Right: fold the tree.
512
+ *
513
+ * A JSON graph of anything real is mostly cards nobody is looking
514
+ * at. "Collapse all" leaves the root and marks every cut with a
515
+ * count, so the shape stays legible and you can see WHERE the rest
516
+ * went; "expand all" puts it back. Same chevron as the lazy tree,
517
+ * rotated to point down for the open state.
518
+ */
519
+ let right_items = [
520
+ graph_fold_button(gobj, "EV_EXPAND_ALL", "expand all", true),
521
+ graph_fold_button(gobj, "EV_COLLAPSE_ALL", "collapse all", false),
522
+ ];
523
+
350
524
  const $toolbar = yui_toolbar({}, [
351
525
  ['div', {class: 'yui-horizontal-toolbar-section left'}, left_items],
352
526
  ['div', {class: 'yui-horizontal-toolbar-section center'}, center_items],
353
- ['div', {class: 'yui-horizontal-toolbar-section right'}, []],
527
+ ['div', {class: 'yui-horizontal-toolbar-section right'}, right_items],
354
528
  ]);
355
529
 
356
530
  refresh_language($toolbar, t);
@@ -440,7 +614,7 @@ function build_graph(gobj)
440
614
  /************************************************************
441
615
  * Build cell value HTML (matching old mx_json_viewer colors)
442
616
  ************************************************************/
443
- function build_cell_html(key, value, type, dark)
617
+ function build_cell_html(key, value, type, dark, matched)
444
618
  {
445
619
  let color = type_color(type, dark);
446
620
  let key_color = json_card_style(GROUP_COLORS.dict, dark).key;
@@ -470,7 +644,76 @@ function build_cell_html(key, value, type, dark)
470
644
  break;
471
645
  }
472
646
 
473
- return `<span style="color:${key_color}">• ${escapeHtml(String(key))}: </span><span style="color:${color}">${display_value}</span>`;
647
+ let row = `<span style="color:${key_color}">• ${escapeHtml(String(key))}: </span>` +
648
+ `<span style="color:${color}">${display_value}</span>`;
649
+
650
+ if(matched) {
651
+ /* The highlight is baked into the card's MARKUP and not set as
652
+ * a G6 node state: the key shape of an `html` node is a DOM
653
+ * element, and G6 paints no state style on it. Setting
654
+ * 'active' here would select correctly and show nothing. */
655
+ row = `<span style="background:${dark? "#7a5d00": "#ffe082"}; ` +
656
+ `border-radius:2px; padding:0 2px;">${row}</span>`;
657
+ }
658
+ return row;
659
+ }
660
+
661
+ /************************************************************
662
+ * The per-node fold handle, drawn in the card's header.
663
+ *
664
+ * Only a card with a BRANCH gets one — a leaf has nothing to
665
+ * fold, and a handle that does nothing is worse than none.
666
+ * It carries the path in `data-fold-path`, which is what the
667
+ * delegated listener in build_ui reads; the card is an
668
+ * innerHTML string, so there is no other way to hang a
669
+ * handler on it.
670
+ *
671
+ * Folded it shows the glyph AND the count of branches hidden
672
+ * under it; open, just the glyph. No word, no title: a word
673
+ * inside this string could carry no i18n key.
674
+ ************************************************************/
675
+ function fold_toggle_html(path, foldable, folded, branches)
676
+ {
677
+ if(!foldable) {
678
+ /* A spacer, so the label of a leaf card stays centred on the
679
+ * same axis as the label of a foldable one. */
680
+ return `<span style="width:1.1em; flex:0 0 auto;"></span>`;
681
+ }
682
+
683
+ let glyph = folded? "&#9656;": "&#9662;"; /* right / down */
684
+ let text = folded? `${glyph} ${branches}`: glyph;
685
+
686
+ return `<span data-fold-path="${escapeHtml(path)}" ` +
687
+ `style="flex:0 0 auto; cursor:pointer; user-select:none; ` +
688
+ `padding:0 .2em; min-width:1.1em; text-align:left;">${text}</span>`;
689
+ }
690
+
691
+ /************************************************************
692
+ * Does this key/value pair match the find term?
693
+ *
694
+ * Scalars are matched on what the card SHOWS. A complex value
695
+ * is not: its contents are a card of their own and match there,
696
+ * so matching the `{3}` summary too would light up every
697
+ * ancestor of every hit.
698
+ ************************************************************/
699
+ function cell_matches(key, value, type, needle)
700
+ {
701
+ if(empty_string(needle)) {
702
+ return false;
703
+ }
704
+ if(String(key).toLowerCase().includes(needle)) {
705
+ return true;
706
+ }
707
+ switch(type) {
708
+ case "string":
709
+ case "number":
710
+ case "boolean":
711
+ return String(value).toLowerCase().includes(needle);
712
+ case "null":
713
+ return "null".includes(needle);
714
+ default:
715
+ return false;
716
+ }
474
717
  }
475
718
 
476
719
  /************************************************************
@@ -541,15 +784,23 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
541
784
  * The theme is read live as the card is drawn (like the gobj-tree's
542
785
  * cards): ac_theme rebuilds on a switch, so this is always current.
543
786
  */
787
+ let priv = gobj.priv;
788
+ let needle = priv.search || "";
544
789
  let dark = yui_is_dark();
545
790
  let lines = [];
546
791
  let pending_complex = [];
792
+ let card_matched = false;
547
793
 
548
794
  let entries = is_dict ? Object.entries(kw) : kw.map((v, i) => [i, v]);
549
795
 
550
796
  for(let [key, value] of entries) {
551
797
  let type = get_json_type(value);
552
- let html = build_cell_html(key, value, type, dark);
798
+ let matched = cell_matches(key, value, type, needle);
799
+ if(matched) {
800
+ card_matched = true;
801
+ priv.match_count++;
802
+ }
803
+ let html = build_cell_html(key, value, type, dark, matched);
553
804
  lines.push(html);
554
805
 
555
806
  if((is_object(value) && json_object_size(value) > 0) ||
@@ -559,9 +810,22 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
559
810
  }
560
811
 
561
812
  let label = get_group_label(path) || (is_dict ? "{}" : "[]");
813
+
814
+ /*
815
+ * Folded: keep the card, drop the branch, and SAY how much went
816
+ * with it. A cut that is not marked reads as a document that ends
817
+ * there — the same rule the lazy tree follows for a `__collapsed__`
818
+ * sentinel. The mark is a glyph and a number on purpose: the card
819
+ * is an innerHTML string, where a word could carry no i18n key and
820
+ * would sit there in English forever.
821
+ */
822
+ let foldable = pending_complex.length > 0;
823
+ let folded = foldable && priv.collapsed.has(path);
562
824
  let colors = json_card_style(
563
825
  is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark
564
826
  );
827
+ let border_color = card_matched? (dark? "#ffb300": "#ff8f00"): colors.border;
828
+ let border_width = card_matched? 2: 1;
565
829
 
566
830
  /*
567
831
  * Build node HTML content
@@ -576,7 +840,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
576
840
  <div style="
577
841
  min-width: ${min_width}px;
578
842
  background: ${colors.bg};
579
- border: 1px ${is_dict ? 'dashed' : 'solid'} ${colors.border};
843
+ border: ${border_width}px ${is_dict ? 'dashed' : 'solid'} ${border_color};
580
844
  border-radius: ${is_dict ? '6px' : '0'};
581
845
  opacity: 0.9;
582
846
  overflow: hidden;
@@ -587,8 +851,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
587
851
  font-weight: bold;
588
852
  font-size: 12px;
589
853
  padding: 3px 8px;
590
- text-align: center;
591
- ">${escapeHtml(label)}</div>
854
+ display: flex;
855
+ align-items: center;
856
+ gap: 6px;
857
+ ">${fold_toggle_html(path, foldable, folded, pending_complex.length)}<span style="flex:1 1 auto; text-align:center;">${escapeHtml(label)}</span></div>
592
858
  ${content_html}
593
859
  </div>`;
594
860
 
@@ -623,8 +889,11 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
623
889
  }
624
890
 
625
891
  /*
626
- * Recurse into complex children
892
+ * Recurse into complex children — unless this card is folded.
627
893
  */
894
+ if(folded) {
895
+ return group_id;
896
+ }
628
897
  for(let pending of pending_complex) {
629
898
  build_json_nodes(
630
899
  gobj,
@@ -663,6 +932,7 @@ function load_json(gobj)
663
932
  }
664
933
 
665
934
  priv.node_counter = 0;
935
+ priv.match_count = 0;
666
936
 
667
937
  let nodes = [];
668
938
  let edges = [];
@@ -682,6 +952,57 @@ function load_json(gobj)
682
952
  }
683
953
  }
684
954
 
955
+ /************************************************************
956
+ * Every path that HAS a branch to fold, in the same recursion
957
+ * build_json_nodes uses — so "collapse all" can never name a
958
+ * card that does not exist. The root is excluded: folding it
959
+ * would leave an empty canvas.
960
+ ************************************************************/
961
+ function collect_foldable_paths(value, path, out, is_root)
962
+ {
963
+ let is_dict = is_object(value);
964
+ let is_list = is_array(value);
965
+ if(!is_dict && !is_list) {
966
+ return false;
967
+ }
968
+
969
+ let has_branch = false;
970
+ let entries = is_dict ? Object.entries(value) : value.map((v, i) => [i, v]);
971
+ for(let [key, child] of entries) {
972
+ if((is_object(child) && json_object_size(child) > 0) ||
973
+ (is_array(child) && child.length > 0)) {
974
+ has_branch = true;
975
+ collect_foldable_paths(child, add_segment(path, key), out, false);
976
+ }
977
+ }
978
+
979
+ if(has_branch && !is_root) {
980
+ out.push(path);
981
+ }
982
+ return has_branch;
983
+ }
984
+
985
+ /************************************************************
986
+ * Show the find count, or hide the chip when there is no
987
+ * term. Zero is a RESULT and must show: a graph that did not
988
+ * move looks the same whether nothing matched or the match
989
+ * was already on screen.
990
+ ************************************************************/
991
+ function update_find_result(gobj)
992
+ {
993
+ let priv = gobj.priv;
994
+ if(!priv.$find_result || !priv.$find_count) {
995
+ return;
996
+ }
997
+ if(empty_string(priv.search)) {
998
+ priv.$find_result.classList.add("is-hidden");
999
+ priv.$find_count.textContent = "";
1000
+ return;
1001
+ }
1002
+ priv.$find_count.textContent = String(priv.match_count);
1003
+ priv.$find_result.classList.remove("is-hidden");
1004
+ }
1005
+
685
1006
  /************************************************************
686
1007
  * Clear and reload.
687
1008
  * {preserve_view: true}
@@ -863,6 +1184,95 @@ function ac_refresh(gobj, event, kw, src)
863
1184
 
864
1185
  /************************************************************
865
1186
  * {theme: "dark"|"light"} — the app switched theme.
1187
+ * EV_TOGGLE_FOLD { path } — fold or unfold ONE card.
1188
+ *
1189
+ * The camera does not move: folding one branch is a local
1190
+ * edit of the picture, and refitting the whole graph for it
1191
+ * would throw away where the reader was looking. The toolbar
1192
+ * pair, which changes everything at once, does refit.
1193
+ ************************************************************/
1194
+ function ac_toggle_fold(gobj, event, kw, src)
1195
+ {
1196
+ let priv = gobj.priv;
1197
+ let path = (kw && kw.path) || "";
1198
+
1199
+ if(empty_string(path)) {
1200
+ log_error(`${gobj_short_name(gobj)}: EV_TOGGLE_FOLD with no path`);
1201
+ return -1;
1202
+ }
1203
+
1204
+ if(priv.collapsed.has(path)) {
1205
+ priv.collapsed.delete(path);
1206
+ } else {
1207
+ priv.collapsed.add(path);
1208
+ }
1209
+
1210
+ refresh_json(gobj, {preserve_view: true});
1211
+ update_find_result(gobj);
1212
+ return 0;
1213
+ }
1214
+
1215
+ /************************************************************
1216
+ * EV_FIND_NODES { text }
1217
+ *
1218
+ * The camera does NOT move: a find is a way of reading what is
1219
+ * already on screen, and a viewport that jumps on every
1220
+ * keystroke is unusable. The count says whether there is
1221
+ * anything to look for elsewhere.
1222
+ ************************************************************/
1223
+ function ac_find_nodes(gobj, event, kw, src)
1224
+ {
1225
+ let priv = gobj.priv;
1226
+
1227
+ priv.search = ((kw && kw.text) || "").trim().toLowerCase();
1228
+ refresh_json(gobj, {preserve_view: true});
1229
+ update_find_result(gobj);
1230
+ return 0;
1231
+ }
1232
+
1233
+ /************************************************************
1234
+ * EV_EXPAND_ALL
1235
+ ************************************************************/
1236
+ function ac_expand_all(gobj, event, kw, src)
1237
+ {
1238
+ let priv = gobj.priv;
1239
+
1240
+ if(priv.collapsed.size === 0) {
1241
+ return 0; // nothing folded; a relayout would only move the camera
1242
+ }
1243
+ priv.collapsed.clear();
1244
+ refresh_json(gobj);
1245
+ update_find_result(gobj);
1246
+ return 0;
1247
+ }
1248
+
1249
+ /************************************************************
1250
+ * EV_COLLAPSE_ALL — fold every card but the root.
1251
+ ************************************************************/
1252
+ function ac_collapse_all(gobj, event, kw, src)
1253
+ {
1254
+ let priv = gobj.priv;
1255
+
1256
+ let json_data = gobj_read_attr(gobj, "json_data");
1257
+ if(!json_data) {
1258
+ return 0;
1259
+ }
1260
+
1261
+ let paths = [];
1262
+ let path = gobj_read_str_attr(gobj, "path") || "`";
1263
+ if(empty_string(path)) {
1264
+ path = "`";
1265
+ }
1266
+ collect_foldable_paths(json_data, path, paths, true);
1267
+
1268
+ priv.collapsed = new Set(paths);
1269
+ refresh_json(gobj);
1270
+ update_find_result(gobj);
1271
+ return 0;
1272
+ }
1273
+
1274
+ /************************************************************
1275
+ *
866
1276
  ************************************************************/
867
1277
  function ac_theme(gobj, event, kw, src)
868
1278
  {
@@ -1054,6 +1464,10 @@ function create_gclass(gclass_name)
1054
1464
  ["ST_IDLE", [
1055
1465
  ["EV_LOAD_DATA", ac_load_data, null],
1056
1466
  ["EV_REFRESH", ac_refresh, null],
1467
+ ["EV_FIND_NODES", ac_find_nodes, null],
1468
+ ["EV_TOGGLE_FOLD", ac_toggle_fold, null],
1469
+ ["EV_EXPAND_ALL", ac_expand_all, null],
1470
+ ["EV_COLLAPSE_ALL", ac_collapse_all, null],
1057
1471
  ["EV_THEME", ac_theme, null],
1058
1472
  ["EV_ZOOM_IN", ac_zoom_in, null],
1059
1473
  ["EV_ZOOM_OUT", ac_zoom_out, null],
@@ -1072,6 +1486,10 @@ function create_gclass(gclass_name)
1072
1486
  const event_types = [
1073
1487
  ["EV_LOAD_DATA", 0],
1074
1488
  ["EV_REFRESH", 0],
1489
+ ["EV_FIND_NODES", 0],
1490
+ ["EV_TOGGLE_FOLD", 0],
1491
+ ["EV_EXPAND_ALL", 0],
1492
+ ["EV_COLLAPSE_ALL", 0],
1075
1493
  ["EV_THEME", 0],
1076
1494
  ["EV_ZOOM_IN", 0],
1077
1495
  ["EV_ZOOM_OUT", 0],