@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.
package/README.md CHANGED
@@ -439,6 +439,29 @@ the order of arrival: the viewer opens on the **tree** (`view_mode` default).
439
439
  | `tree` | *where is this value, and what is around it* | the lazy view; the only one that can drill; the default |
440
440
  | `graph` | *what shape is this* | a hosted `C_YUI_JSON_GRAPH` child (AntV/G6) |
441
441
 
442
+ The graph carries **its own** toolbar, and since `7.22.0` that toolbar has the
443
+ same two facilities the tree has: a **find box** (rate-limited, `EV_FIND_NODES`)
444
+ that highlights matching rows and outlines the cards they are in and says how
445
+ many matched, and **expand-all / collapse-all** (`EV_EXPAND_ALL` /
446
+ `EV_COLLAPSE_ALL`) that fold every card but the root, marking each cut with
447
+ `▸ N` so the shape stays legible and you can see where the rest went. Since
448
+ `7.23.0` each card that HAS a branch also carries its own handle in its header
449
+ (`▾` open, `▸ N` folded, `EV_TOGGLE_FOLD {path}`); a leaf gets a spacer instead,
450
+ because a handle that does nothing is worse than none.
451
+
452
+ The handle is delegated from the canvas mount in the **capture** phase, over
453
+ `pointerdown`/`pointerup`/`mousedown`/`click`: the card is an `innerHTML`
454
+ string so nothing can be bound to it directly, G6 binds on the node element
455
+ below, and G6 builds its click from the POINTER sequence — swallowing `click`
456
+ alone leaves the fold working and the card also reporting an item click. A find
457
+ does not move the camera — a viewport that jumps on every keystroke is
458
+ unusable — which is why the count is there.
459
+
460
+ Note **where the highlight lives**: baked into the card's own markup, never set
461
+ as a G6 node state. The key shape of an `html` node is a DOM element and G6
462
+ paints no state style on it, so setting `active` selects correctly and shows
463
+ nothing.
464
+
442
465
  - **Neither text nor graph is lazy.** Both show what the client currently
443
466
  holds, `__collapsed__` sentinels included, because that is honestly what it
444
467
  has. Drill in the tree and they grow with it.
@@ -477,11 +500,13 @@ Two layout facts the browser taught this component, both worth keeping:
477
500
  `"text"` / `"graph"`; **no mode advances** to the next view, which is what
478
501
  the two-view toggle did when the list was two long), plus `EV_REFRESH` /
479
502
  `EV_SHOW` / `EV_HIDE` / `EV_LANGUAGE_CHANGED`.
480
- - Output events: `EV_EXPAND_PATH {path, size}` (`EVF_OUTPUT_EVENT`) — the parent
481
- must declare it in its own FSM (CHILD subscription model) — and
482
- `EV_JSON_ITEM_CLICKED`, republished from the graph child so the host has one
483
- contract and never has to know that child exists (`EVF_NO_WARN_SUBS`: most
484
- hosts do not care).
503
+ - Output event: `EV_EXPAND_PATH {path, size}` (`EVF_OUTPUT_EVENT`) — the parent
504
+ must declare it in its own FSM (CHILD subscription model). That is the ONLY
505
+ one, on purpose: this viewer is a child of its host and subscribes it to
506
+ everything it publishes, so every output event is a mandatory declaration in
507
+ every host's FSM. The graph child's `EV_JSON_ITEM_CLICKED` stops here
508
+ (`7.21.0` forwarded it and broke exactly that way); a host that wants node
509
+ clicks mounts `C_YUI_JSON_GRAPH` itself.
485
510
  - Internal (DOM → FSM): `EV_TOGGLE_NODE`, `EV_EXPAND_COLLAPSED`, `EV_SEARCH`,
486
511
  `EV_EXPAND_ALL`, `EV_COLLAPSE_ALL`, `EV_COPY_ALL`. Every kw carries only a
487
512
  `path` string — never a DOM node or gobj.
@@ -9473,6 +9473,12 @@ function ensure_drag_canvas_patch() {
9473
9473
  * Constants
9474
9474
  ***************************************************************/
9475
9475
  var GCLASS_NAME$11 = "C_YUI_JSON_GRAPH";
9476
+ var FOLD_SWALLOWED_EVENTS = [
9477
+ "pointerdown",
9478
+ "pointerup",
9479
+ "mousedown",
9480
+ "click"
9481
+ ];
9476
9482
  /***************************************************************
9477
9483
  * Data
9478
9484
  ***************************************************************/
@@ -9492,7 +9498,15 @@ var PRIVATE_DATA$11 = {
9492
9498
  theme: null,
9493
9499
  theme_observer: null,
9494
9500
  resize_observer: null,
9495
- _resize_raf: 0
9501
+ _resize_raf: 0,
9502
+ search: "",
9503
+ collapsed: null,
9504
+ match_count: 0,
9505
+ find_timer: null,
9506
+ fold_listener: null,
9507
+ $find_input: null,
9508
+ $find_result: null,
9509
+ $find_count: null
9496
9510
  };
9497
9511
  var __gclass__$11 = null;
9498
9512
  var __layout_registered__$1 = false;
@@ -9561,6 +9575,8 @@ function type_color(type, dark) {
9561
9575
  ***************************************************************/
9562
9576
  function mt_create$11(gobj) {
9563
9577
  let priv = gobj.priv;
9578
+ priv.collapsed = /* @__PURE__ */ new Set();
9579
+ priv.search = "";
9564
9580
  priv.canvas_id = "json-canvas-" + (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj));
9565
9581
  (0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "canvas_id", priv.canvas_id);
9566
9582
  let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
@@ -9590,6 +9606,10 @@ function mt_stop$11(gobj) {}
9590
9606
  * Framework Method: Destroy
9591
9607
  ***************************************************************/
9592
9608
  function mt_destroy$11(gobj) {
9609
+ if (gobj.priv.find_timer) {
9610
+ clearTimeout(gobj.priv.find_timer);
9611
+ gobj.priv.find_timer = null;
9612
+ }
9593
9613
  let priv = gobj.priv;
9594
9614
  if (priv.theme_observer) {
9595
9615
  priv.theme_observer.disconnect();
@@ -9646,25 +9666,118 @@ function build_ui$10(gobj) {
9646
9666
  ]]
9647
9667
  ]);
9648
9668
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
9669
+ let $mount = $container.querySelector("#" + priv.canvas_id);
9670
+ if ($mount) {
9671
+ priv.fold_listener = function(evt) {
9672
+ let $handle = evt.target && evt.target.closest ? evt.target.closest("[data-fold-path]") : null;
9673
+ if (!$handle) return;
9674
+ evt.stopPropagation();
9675
+ evt.preventDefault();
9676
+ if (evt.type !== "click") return;
9677
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_FOLD", { path: $handle.getAttribute("data-fold-path") }, gobj);
9678
+ };
9679
+ for (let type of FOLD_SWALLOWED_EVENTS) $mount.addEventListener(type, priv.fold_listener, true);
9680
+ } else (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no canvas mount, fold handles are dead`);
9681
+ priv.$find_input = $container.querySelector(".JSON_GRAPH_FIND_INPUT");
9682
+ priv.$find_result = $container.querySelector(".JSON_GRAPH_FIND_RESULT");
9683
+ priv.$find_count = $container.querySelector(".JSON_GRAPH_FIND_COUNT");
9649
9684
  (0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
9650
9685
  }
9651
9686
  /************************************************************
9652
9687
  * Destroy UI
9653
9688
  ************************************************************/
9654
9689
  function destroy_ui$6(gobj) {
9690
+ let priv = gobj.priv;
9655
9691
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
9692
+ if (priv.fold_listener) {
9693
+ let $mount = $container ? $container.querySelector("#" + priv.canvas_id) : null;
9694
+ if ($mount) for (let type of FOLD_SWALLOWED_EVENTS) $mount.removeEventListener(type, priv.fold_listener, true);
9695
+ priv.fold_listener = null;
9696
+ }
9656
9697
  if ($container) {
9657
9698
  if ($container.parentNode) $container.parentNode.removeChild($container);
9658
9699
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", null);
9659
9700
  }
9660
9701
  }
9661
9702
  /************************************************************
9703
+ * One fold button. `open` rotates the chevron to the open
9704
+ * state, mirroring the per-node toggle of the lazy tree.
9705
+ ************************************************************/
9706
+ function graph_fold_button(gobj, event_name, label_key, open) {
9707
+ let icon_style = "font-size:1.5em; color:inherit;" + (open ? " transform: rotate(90deg);" : "");
9708
+ return [
9709
+ "button",
9710
+ {
9711
+ class: `button ${event_name}`,
9712
+ type: "button",
9713
+ style: {
9714
+ height: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "wide"),
9715
+ width: "2.5em"
9716
+ },
9717
+ title: (0, i18next.t)(label_key),
9718
+ "data-i18n-title": label_key,
9719
+ "aria-label": (0, i18next.t)(label_key),
9720
+ "data-i18n-aria-label": label_key
9721
+ },
9722
+ ["i", {
9723
+ style: icon_style,
9724
+ class: "yi-chevron-right"
9725
+ }],
9726
+ { click: (evt) => {
9727
+ evt.stopPropagation();
9728
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, {}, gobj);
9729
+ } }
9730
+ ];
9731
+ }
9732
+ /************************************************************
9662
9733
  * Toolbar
9663
9734
  ************************************************************/
9664
9735
  function make_toolbar$3(gobj) {
9665
- gobj.priv;
9736
+ let priv = gobj.priv;
9666
9737
  let toolbar_wide = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "wide");
9667
- let left_items = [];
9738
+ let left_items = [[
9739
+ "div",
9740
+ {
9741
+ class: "JSON_GRAPH_FIND control has-icons-left",
9742
+ style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
9743
+ },
9744
+ [["input", {
9745
+ class: "JSON_GRAPH_FIND_INPUT input",
9746
+ type: "text",
9747
+ placeholder: (0, i18next.t)("search"),
9748
+ "data-i18n-placeholder": "search",
9749
+ "aria-label": (0, i18next.t)("search"),
9750
+ "data-i18n-aria-label": "search"
9751
+ }], [
9752
+ "span",
9753
+ { class: "icon is-left" },
9754
+ [["i", { class: "yi-magnifying-glass" }]]
9755
+ ]],
9756
+ { input: (evt) => {
9757
+ evt.stopPropagation();
9758
+ let text = evt.target.value.trim();
9759
+ if (priv.find_timer) clearTimeout(priv.find_timer);
9760
+ priv.find_timer = setTimeout(function() {
9761
+ priv.find_timer = null;
9762
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_FIND_NODES", { text }, gobj);
9763
+ }, 250);
9764
+ } }
9765
+ ], [
9766
+ "div",
9767
+ {
9768
+ class: "JSON_GRAPH_FIND_RESULT is-hidden",
9769
+ style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
9770
+ },
9771
+ [[
9772
+ "span",
9773
+ { class: "JSON_GRAPH_FIND_COUNT" },
9774
+ ""
9775
+ ], [
9776
+ "span",
9777
+ { i18n: "matches" },
9778
+ "matches"
9779
+ ]]
9780
+ ]];
9668
9781
  let center_items = [];
9669
9782
  for (let item of [
9670
9783
  [
@@ -9722,6 +9835,7 @@ function make_toolbar$3(gobj) {
9722
9835
  } }
9723
9836
  ]);
9724
9837
  }
9838
+ let right_items = [graph_fold_button(gobj, "EV_EXPAND_ALL", "expand all", true), graph_fold_button(gobj, "EV_COLLAPSE_ALL", "collapse all", false)];
9725
9839
  const $toolbar = yui_toolbar({}, [
9726
9840
  [
9727
9841
  "div",
@@ -9736,7 +9850,7 @@ function make_toolbar$3(gobj) {
9736
9850
  [
9737
9851
  "div",
9738
9852
  { class: "yui-horizontal-toolbar-section right" },
9739
- []
9853
+ right_items
9740
9854
  ]
9741
9855
  ]);
9742
9856
  (0, _yuneta_gobj_js.refresh_language)($toolbar, i18next.t);
@@ -9787,7 +9901,7 @@ function build_graph$3(gobj) {
9787
9901
  /************************************************************
9788
9902
  * Build cell value HTML (matching old mx_json_viewer colors)
9789
9903
  ************************************************************/
9790
- function build_cell_html(key, value, type, dark) {
9904
+ function build_cell_html(key, value, type, dark, matched) {
9791
9905
  let color = type_color(type, dark);
9792
9906
  let key_color = json_card_style(GROUP_COLORS.dict, dark).key;
9793
9907
  let display_value = "";
@@ -9812,7 +9926,48 @@ function build_cell_html(key, value, type, dark) {
9812
9926
  break;
9813
9927
  default: display_value = String(value);
9814
9928
  }
9815
- return `<span style="color:${key_color}">• ${(0, _yuneta_gobj_js.escapeHtml)(String(key))}: </span><span style="color:${color}">${display_value}</span>`;
9929
+ let row = `<span style="color:${key_color}">• ${(0, _yuneta_gobj_js.escapeHtml)(String(key))}: </span><span style="color:${color}">${display_value}</span>`;
9930
+ if (matched) row = `<span style="background:${dark ? "#7a5d00" : "#ffe082"}; border-radius:2px; padding:0 2px;">${row}</span>`;
9931
+ return row;
9932
+ }
9933
+ /************************************************************
9934
+ * The per-node fold handle, drawn in the card's header.
9935
+ *
9936
+ * Only a card with a BRANCH gets one — a leaf has nothing to
9937
+ * fold, and a handle that does nothing is worse than none.
9938
+ * It carries the path in `data-fold-path`, which is what the
9939
+ * delegated listener in build_ui reads; the card is an
9940
+ * innerHTML string, so there is no other way to hang a
9941
+ * handler on it.
9942
+ *
9943
+ * Folded it shows the glyph AND the count of branches hidden
9944
+ * under it; open, just the glyph. No word, no title: a word
9945
+ * inside this string could carry no i18n key.
9946
+ ************************************************************/
9947
+ function fold_toggle_html(path, foldable, folded, branches) {
9948
+ if (!foldable) return `<span style="width:1.1em; flex:0 0 auto;"></span>`;
9949
+ let glyph = folded ? "&#9656;" : "&#9662;";
9950
+ let text = folded ? `${glyph} ${branches}` : glyph;
9951
+ return `<span data-fold-path="${(0, _yuneta_gobj_js.escapeHtml)(path)}" style="flex:0 0 auto; cursor:pointer; user-select:none; padding:0 .2em; min-width:1.1em; text-align:left;">${text}</span>`;
9952
+ }
9953
+ /************************************************************
9954
+ * Does this key/value pair match the find term?
9955
+ *
9956
+ * Scalars are matched on what the card SHOWS. A complex value
9957
+ * is not: its contents are a card of their own and match there,
9958
+ * so matching the `{3}` summary too would light up every
9959
+ * ancestor of every hit.
9960
+ ************************************************************/
9961
+ function cell_matches(key, value, type, needle) {
9962
+ if ((0, _yuneta_gobj_js.empty_string)(needle)) return false;
9963
+ if (String(key).toLowerCase().includes(needle)) return true;
9964
+ switch (type) {
9965
+ case "string":
9966
+ case "number":
9967
+ case "boolean": return String(value).toLowerCase().includes(needle);
9968
+ case "null": return "null".includes(needle);
9969
+ default: return false;
9970
+ }
9816
9971
  }
9817
9972
  /************************************************************
9818
9973
  * Get JSON type string
@@ -9858,12 +10013,21 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
9858
10013
  let is_dict = (0, _yuneta_gobj_js.is_object)(kw);
9859
10014
  let is_list = (0, _yuneta_gobj_js.is_array)(kw);
9860
10015
  if (!is_dict && !is_list) return null;
10016
+ let priv = gobj.priv;
10017
+ let needle = priv.search || "";
9861
10018
  let dark = yui_is_dark();
9862
10019
  let lines = [];
9863
10020
  let pending_complex = [];
10021
+ let card_matched = false;
9864
10022
  let entries = is_dict ? Object.entries(kw) : kw.map((v, i) => [i, v]);
9865
10023
  for (let [key, value] of entries) {
9866
- let html = build_cell_html(key, value, get_json_type(value), dark);
10024
+ let type = get_json_type(value);
10025
+ let matched = cell_matches(key, value, type, needle);
10026
+ if (matched) {
10027
+ card_matched = true;
10028
+ priv.match_count++;
10029
+ }
10030
+ let html = build_cell_html(key, value, type, dark, matched);
9867
10031
  lines.push(html);
9868
10032
  if ((0, _yuneta_gobj_js.is_object)(value) && (0, _yuneta_gobj_js.json_object_size)(value) > 0 || (0, _yuneta_gobj_js.is_array)(value) && value.length > 0) pending_complex.push({
9869
10033
  key,
@@ -9871,14 +10035,18 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
9871
10035
  });
9872
10036
  }
9873
10037
  let label = get_group_label(path) || (is_dict ? "{}" : "[]");
10038
+ let foldable = pending_complex.length > 0;
10039
+ let folded = foldable && priv.collapsed.has(path);
9874
10040
  let colors = json_card_style(is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
10041
+ let border_color = card_matched ? dark ? "#ffb300" : "#ff8f00" : colors.border;
10042
+ let border_width = card_matched ? 2 : 1;
9875
10043
  let content_html = lines.map((line) => `<div style="font-family:monospace; font-size:13px; padding:1px 6px; white-space:nowrap;">${line}</div>`).join("");
9876
10044
  let min_width = Math.max(180, label.length * 9);
9877
10045
  let node_html = `
9878
10046
  <div style="
9879
10047
  min-width: ${min_width}px;
9880
10048
  background: ${colors.bg};
9881
- border: 1px ${is_dict ? "dashed" : "solid"} ${colors.border};
10049
+ border: ${border_width}px ${is_dict ? "dashed" : "solid"} ${border_color};
9882
10050
  border-radius: ${is_dict ? "6px" : "0"};
9883
10051
  opacity: 0.9;
9884
10052
  overflow: hidden;
@@ -9889,8 +10057,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
9889
10057
  font-weight: bold;
9890
10058
  font-size: 12px;
9891
10059
  padding: 3px 8px;
9892
- text-align: center;
9893
- ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</div>
10060
+ display: flex;
10061
+ align-items: center;
10062
+ gap: 6px;
10063
+ ">${fold_toggle_html(path, foldable, folded, pending_complex.length)}<span style="flex:1 1 auto; text-align:center;">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span></div>
9894
10064
  ${content_html}
9895
10065
  </div>`;
9896
10066
  let node = {
@@ -9916,6 +10086,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
9916
10086
  lineWidth: 1
9917
10087
  }
9918
10088
  });
10089
+ if (folded) return group_id;
9919
10090
  for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, group_id);
9920
10091
  return group_id;
9921
10092
  }
@@ -9932,6 +10103,7 @@ function load_json(gobj) {
9932
10103
  let path = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "path") || "`";
9933
10104
  if ((0, _yuneta_gobj_js.empty_string)(path)) path = "`";
9934
10105
  priv.node_counter = 0;
10106
+ priv.match_count = 0;
9935
10107
  let nodes = [];
9936
10108
  let edges = [];
9937
10109
  build_json_nodes(gobj, path, json, nodes, edges, null);
@@ -9948,6 +10120,42 @@ function load_json(gobj) {
9948
10120
  }
9949
10121
  }
9950
10122
  /************************************************************
10123
+ * Every path that HAS a branch to fold, in the same recursion
10124
+ * build_json_nodes uses — so "collapse all" can never name a
10125
+ * card that does not exist. The root is excluded: folding it
10126
+ * would leave an empty canvas.
10127
+ ************************************************************/
10128
+ function collect_foldable_paths(value, path, out, is_root) {
10129
+ let is_dict = (0, _yuneta_gobj_js.is_object)(value);
10130
+ let is_list = (0, _yuneta_gobj_js.is_array)(value);
10131
+ if (!is_dict && !is_list) return false;
10132
+ let has_branch = false;
10133
+ let entries = is_dict ? Object.entries(value) : value.map((v, i) => [i, v]);
10134
+ for (let [key, child] of entries) if ((0, _yuneta_gobj_js.is_object)(child) && (0, _yuneta_gobj_js.json_object_size)(child) > 0 || (0, _yuneta_gobj_js.is_array)(child) && child.length > 0) {
10135
+ has_branch = true;
10136
+ collect_foldable_paths(child, add_segment(path, key), out, false);
10137
+ }
10138
+ if (has_branch && !is_root) out.push(path);
10139
+ return has_branch;
10140
+ }
10141
+ /************************************************************
10142
+ * Show the find count, or hide the chip when there is no
10143
+ * term. Zero is a RESULT and must show: a graph that did not
10144
+ * move looks the same whether nothing matched or the match
10145
+ * was already on screen.
10146
+ ************************************************************/
10147
+ function update_find_result(gobj) {
10148
+ let priv = gobj.priv;
10149
+ if (!priv.$find_result || !priv.$find_count) return;
10150
+ if ((0, _yuneta_gobj_js.empty_string)(priv.search)) {
10151
+ priv.$find_result.classList.add("is-hidden");
10152
+ priv.$find_count.textContent = "";
10153
+ return;
10154
+ }
10155
+ priv.$find_count.textContent = String(priv.match_count);
10156
+ priv.$find_result.classList.remove("is-hidden");
10157
+ }
10158
+ /************************************************************
9951
10159
  * Clear and reload.
9952
10160
  * {preserve_view: true}
9953
10161
  * keep the current zoom and translate (no fitView) —
@@ -10069,6 +10277,70 @@ function ac_refresh$5(gobj, event, kw, src) {
10069
10277
  }
10070
10278
  /************************************************************
10071
10279
  * {theme: "dark"|"light"} — the app switched theme.
10280
+ * EV_TOGGLE_FOLD { path } — fold or unfold ONE card.
10281
+ *
10282
+ * The camera does not move: folding one branch is a local
10283
+ * edit of the picture, and refitting the whole graph for it
10284
+ * would throw away where the reader was looking. The toolbar
10285
+ * pair, which changes everything at once, does refit.
10286
+ ************************************************************/
10287
+ function ac_toggle_fold(gobj, event, kw, src) {
10288
+ let priv = gobj.priv;
10289
+ let path = kw && kw.path || "";
10290
+ if ((0, _yuneta_gobj_js.empty_string)(path)) {
10291
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_TOGGLE_FOLD with no path`);
10292
+ return -1;
10293
+ }
10294
+ if (priv.collapsed.has(path)) priv.collapsed.delete(path);
10295
+ else priv.collapsed.add(path);
10296
+ refresh_json(gobj, { preserve_view: true });
10297
+ update_find_result(gobj);
10298
+ return 0;
10299
+ }
10300
+ /************************************************************
10301
+ * EV_FIND_NODES { text }
10302
+ *
10303
+ * The camera does NOT move: a find is a way of reading what is
10304
+ * already on screen, and a viewport that jumps on every
10305
+ * keystroke is unusable. The count says whether there is
10306
+ * anything to look for elsewhere.
10307
+ ************************************************************/
10308
+ function ac_find_nodes$2(gobj, event, kw, src) {
10309
+ let priv = gobj.priv;
10310
+ priv.search = (kw && kw.text || "").trim().toLowerCase();
10311
+ refresh_json(gobj, { preserve_view: true });
10312
+ update_find_result(gobj);
10313
+ return 0;
10314
+ }
10315
+ /************************************************************
10316
+ * EV_EXPAND_ALL
10317
+ ************************************************************/
10318
+ function ac_expand_all$2(gobj, event, kw, src) {
10319
+ let priv = gobj.priv;
10320
+ if (priv.collapsed.size === 0) return 0;
10321
+ priv.collapsed.clear();
10322
+ refresh_json(gobj);
10323
+ update_find_result(gobj);
10324
+ return 0;
10325
+ }
10326
+ /************************************************************
10327
+ * EV_COLLAPSE_ALL — fold every card but the root.
10328
+ ************************************************************/
10329
+ function ac_collapse_all$2(gobj, event, kw, src) {
10330
+ let priv = gobj.priv;
10331
+ let json_data = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "json_data");
10332
+ if (!json_data) return 0;
10333
+ let paths = [];
10334
+ let path = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "path") || "`";
10335
+ if ((0, _yuneta_gobj_js.empty_string)(path)) path = "`";
10336
+ collect_foldable_paths(json_data, path, paths, true);
10337
+ priv.collapsed = new Set(paths);
10338
+ refresh_json(gobj);
10339
+ update_find_result(gobj);
10340
+ return 0;
10341
+ }
10342
+ /************************************************************
10343
+ *
10072
10344
  ************************************************************/
10073
10345
  function ac_theme$3(gobj, event, kw, src) {
10074
10346
  let priv = gobj.priv;
@@ -10209,6 +10481,26 @@ function create_gclass$11(gclass_name) {
10209
10481
  ac_refresh$5,
10210
10482
  null
10211
10483
  ],
10484
+ [
10485
+ "EV_FIND_NODES",
10486
+ ac_find_nodes$2,
10487
+ null
10488
+ ],
10489
+ [
10490
+ "EV_TOGGLE_FOLD",
10491
+ ac_toggle_fold,
10492
+ null
10493
+ ],
10494
+ [
10495
+ "EV_EXPAND_ALL",
10496
+ ac_expand_all$2,
10497
+ null
10498
+ ],
10499
+ [
10500
+ "EV_COLLAPSE_ALL",
10501
+ ac_collapse_all$2,
10502
+ null
10503
+ ],
10212
10504
  [
10213
10505
  "EV_THEME",
10214
10506
  ac_theme$3,
@@ -10258,6 +10550,10 @@ function create_gclass$11(gclass_name) {
10258
10550
  const event_types = [
10259
10551
  ["EV_LOAD_DATA", 0],
10260
10552
  ["EV_REFRESH", 0],
10553
+ ["EV_FIND_NODES", 0],
10554
+ ["EV_TOGGLE_FOLD", 0],
10555
+ ["EV_EXPAND_ALL", 0],
10556
+ ["EV_COLLAPSE_ALL", 0],
10261
10557
  ["EV_THEME", 0],
10262
10558
  ["EV_ZOOM_IN", 0],
10263
10559
  ["EV_ZOOM_OUT", 0],
@@ -11459,11 +11755,20 @@ function ac_set_view_mode(gobj, event, kw, src) {
11459
11755
  /************************************************************
11460
11756
  * EV_JSON_ITEM_CLICKED — from the hosted graph child.
11461
11757
  *
11462
- * Republished under this gclass's own name so the host has ONE
11463
- * contract and never has to know the child exists.
11758
+ * It STOPS here, and that is deliberate. 7.21.0 republished
11759
+ * it "so the host has one contract", which got the framework
11760
+ * backwards: this viewer is a CHILD of its host and subscribes
11761
+ * it to everything it publishes, so a new output event is a
11762
+ * new MANDATORY declaration in every host's FSM. None of the
11763
+ * six that mount this viewer asked for node clicks, and every
11764
+ * one of them answered a click on a graph card with
11765
+ * "Event NOT DEFINED in state".
11766
+ *
11767
+ * A host that wants node clicks mounts C_YUI_JSON_GRAPH
11768
+ * itself — that gclass publishes them, and a host that mounts
11769
+ * it has declared them.
11464
11770
  ************************************************************/
11465
11771
  function ac_json_item_clicked(gobj, event, kw, src) {
11466
- (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_JSON_ITEM_CLICKED", kw);
11467
11772
  return 0;
11468
11773
  }
11469
11774
  /************************************************************
@@ -11636,7 +11941,7 @@ function create_gclass$10(gclass_name) {
11636
11941
  ["EV_COLLAPSE_ALL", 0],
11637
11942
  ["EV_COPY_ALL", 0],
11638
11943
  ["EV_SET_VIEW_MODE", 0],
11639
- ["EV_JSON_ITEM_CLICKED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
11944
+ ["EV_JSON_ITEM_CLICKED", 0],
11640
11945
  ["EV_LANGUAGE_CHANGED", 0],
11641
11946
  ["EV_REFRESH", 0],
11642
11947
  ["EV_SHOW", 0],