@yuneta/gobj-ui 7.2.5 → 7.3.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
@@ -582,6 +582,15 @@ Two details that are not decoration:
582
582
  The find and the topic focus **share the highlight**: starting one clears the
583
583
  other. Two amber sets at once would say nothing about either.
584
584
 
585
+ The highlight is painted **into the card's own html**, not with G6's `active`
586
+ element state. That state is an amber `stroke` + `halo`, both properties of a
587
+ node's KEY SHAPE — and every node here is an `html` node, whose key shape is a
588
+ DOM element. There was nothing for either property to paint on, so the amber
589
+ had never appeared, for the topic focus either. Only the cards whose state
590
+ changes are repainted, and a theme switch carries the highlight across (it
591
+ rebuilds every card, and rebuilding them without it would clear what is on
592
+ screen).
593
+
585
594
  Wiring: the box sends `EV_FIND_NODES {text}` to the view, which forwards it to
586
595
  `C_G6_NODES_TREE`; the graph answers `EV_FIND_RESULT {term, matches}`, which the
587
596
  view declares like every other event its child publishes.
@@ -18594,6 +18594,7 @@ var PRIVATE_DATA$4 = {
18594
18594
  topics: [],
18595
18595
  records: {},
18596
18596
  gobj_nodes_tree: null,
18597
+ find_timer: null,
18597
18598
  gobj_treedb_tables: null,
18598
18599
  hook_data_viewer: null,
18599
18600
  json_gobj: null,
@@ -18668,6 +18669,11 @@ function mt_stop$4(gobj) {
18668
18669
  * Framework Method: Destroy
18669
18670
  ***************************************************************/
18670
18671
  function mt_destroy$4(gobj) {
18672
+ let priv = gobj.priv;
18673
+ if (priv.find_timer) {
18674
+ clearTimeout(priv.find_timer);
18675
+ priv.find_timer = null;
18676
+ }
18671
18677
  destroy_ui$1(gobj);
18672
18678
  }
18673
18679
  /***************************
@@ -18774,7 +18780,7 @@ function destroy_ui$1(gobj) {
18774
18780
  *
18775
18781
  ************************************************************/
18776
18782
  function make_toolbar(gobj) {
18777
- gobj.priv;
18783
+ let priv = gobj.priv;
18778
18784
  let modes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "operation_modes");
18779
18785
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) modes = modes.filter((mode) => mode !== "edition");
18780
18786
  let left_items = [
@@ -18883,7 +18889,12 @@ function make_toolbar(gobj) {
18883
18889
  ]],
18884
18890
  { input: (evt) => {
18885
18891
  evt.stopPropagation();
18886
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_FIND_NODES", { text: evt.target.value.trim() }, gobj);
18892
+ let text = evt.target.value.trim();
18893
+ if (priv.find_timer) clearTimeout(priv.find_timer);
18894
+ priv.find_timer = setTimeout(function() {
18895
+ priv.find_timer = null;
18896
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_FIND_NODES", { text }, gobj);
18897
+ }, 250);
18887
18898
  } }
18888
18899
  ], [
18889
18900
  "div",
@@ -52642,6 +52653,8 @@ ensure_drag_canvas_patch();
52642
52653
  * Constants
52643
52654
  ***************************************************************/
52644
52655
  var GCLASS_NAME$1 = "C_G6_NODES_TREE";
52656
+ var HIGHLIGHT_COLOR = "#f0a020";
52657
+ var HIGHLIGHT_HALO = "rgba(240,160,32,0.35)";
52645
52658
  /***************************************************************
52646
52659
  * Internal layout and operation mode definitions
52647
52660
  ***************************************************************/
@@ -53918,10 +53931,12 @@ function graph_focus_topic(gobj, topic) {
53918
53931
  if (ids.length === 0) (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: focus topic '${topic}' has no nodes`);
53919
53932
  for (let id of ids) states[id] = ["active"];
53920
53933
  }
53934
+ let prev_ids = priv._focus_ids || [];
53921
53935
  priv._focus_ids = ids;
53922
53936
  priv._focus_topic = topic || null;
53923
53937
  try {
53924
53938
  if (typeof graph.setElementState === "function") graph.setElementState(states);
53939
+ apply_node_highlight(gobj, prev_ids, ids);
53925
53940
  if (ids.length && typeof graph.focusElement === "function") graph.focusElement(ids);
53926
53941
  } catch (e) {
53927
53942
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: focus topic apply failed: ${e}`);
@@ -53978,10 +53993,12 @@ function graph_find_nodes(gobj, term) {
53978
53993
  }
53979
53994
  for (let id of ids) states[id] = ["active"];
53980
53995
  }
53996
+ let prev_ids = priv._focus_ids || [];
53981
53997
  priv._focus_ids = ids;
53982
53998
  priv._focus_topic = null;
53983
53999
  try {
53984
54000
  if (typeof graph.setElementState === "function") graph.setElementState(states);
54001
+ apply_node_highlight(gobj, prev_ids, ids);
53985
54002
  if (ids.length && typeof graph.focusElement === "function") graph.focusElement(ids);
53986
54003
  } catch (e) {
53987
54004
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: find apply failed: ${e}`);
@@ -55525,27 +55542,66 @@ function show_node_detail_popover(gobj, node_id) {
55525
55542
  * not re-themed by setTheme()) are regenerated; structural
55526
55543
  * junction diamonds get their neutral fill/stroke re-themed.
55527
55544
  ************************************************************/
55545
+ /************************************************************
55546
+ * The innerHTML of ONE node, in the given theme and highlight state.
55547
+ * The three treedb tiers (hierarchical / extended / child) each have
55548
+ * their own card, and both the theme refresh and the highlight need the
55549
+ * same three-way choice — it lived inline in the theme refresh and is
55550
+ * shared now. Returns null for a node that carries no desc.
55551
+ ************************************************************/
55552
+ function node_innerHTML_of(nd, theme, highlight) {
55553
+ if (!nd || !nd.data || !nd.data.desc) return null;
55554
+ let desc = nd.data.desc;
55555
+ let record = nd.data.record || {};
55556
+ let label = node_label(desc, record);
55557
+ switch (desc.node_treedb_type) {
55558
+ case "child": return build_chip_innerHTML(desc.color, theme, record.icon, label, record.id, highlight);
55559
+ case "extended": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, true, record.id, highlight);
55560
+ case "hierarchical": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, false, record.id, highlight);
55561
+ }
55562
+ return null;
55563
+ }
55564
+ /************************************************************
55565
+ * Repaint the cards whose highlight state CHANGES: the ones that had it
55566
+ * and lose it, plus the ones that gain it. Only those — a treedb graph
55567
+ * is redrawn per keystroke of the find box otherwise.
55568
+ ************************************************************/
55569
+ function apply_node_highlight(gobj, prev_ids, next_ids) {
55570
+ let priv = gobj.priv;
55571
+ let graph = priv.graph;
55572
+ if (!graph) return;
55573
+ let next = new Set(next_ids || []);
55574
+ let touched = /* @__PURE__ */ new Set([...prev_ids || [], ...next]);
55575
+ if (touched.size === 0) return;
55576
+ let updates = [];
55577
+ for (let id of touched) {
55578
+ let html = node_innerHTML_of(graph.getNodeData(id), priv.theme, next.has(id));
55579
+ if (html !== null) updates.push({
55580
+ id,
55581
+ style: { innerHTML: html }
55582
+ });
55583
+ }
55584
+ if (!updates.length) return;
55585
+ try {
55586
+ graph.updateNodeData(updates);
55587
+ graph.draw();
55588
+ } catch (e) {
55589
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot repaint the highlight: ${e}`);
55590
+ }
55591
+ }
55528
55592
  function refresh_html_nodes_theme(gobj, theme) {
55529
- let graph = gobj.priv.graph;
55593
+ let priv = gobj.priv;
55594
+ let graph = priv.graph;
55530
55595
  if (!graph) return;
55531
55596
  let nodes = graph.getData().nodes || [];
55597
+ let highlighted = new Set(priv._focus_ids || []);
55532
55598
  let updates = [];
55533
55599
  for (let i = 0; i < nodes.length; i++) {
55534
- let nd = graph.getNodeData(nodes[i].id);
55535
- if (!nd || !nd.data || !nd.data.desc) continue;
55536
- let tt = nd.data.desc.node_treedb_type;
55537
- let record = nd.data.record || {};
55538
- if (tt === "hierarchical") updates.push({
55539
- id: nodes[i].id,
55540
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id) }
55541
- });
55542
- else if (tt === "child") updates.push({
55543
- id: nodes[i].id,
55544
- style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), record.id) }
55545
- });
55546
- else if (tt === "extended") updates.push({
55547
- id: nodes[i].id,
55548
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, true, record.id) }
55600
+ let id = nodes[i].id;
55601
+ let html = node_innerHTML_of(graph.getNodeData(id), theme, highlighted.has(id));
55602
+ if (html !== null) updates.push({
55603
+ id,
55604
+ style: { innerHTML: html }
55549
55605
  });
55550
55606
  }
55551
55607
  if (updates.length > 0) graph.updateNodeData(updates);
@@ -55577,12 +55633,13 @@ function refresh_default_edges_theme(gobj, theme) {
55577
55633
  * but lighter (1px border, no shadow, single line). The name is
55578
55634
  * always legible (ellipsis + native title tooltip on overflow).
55579
55635
  ************************************************************/
55580
- function build_chip_innerHTML(color, theme, icon, label, key) {
55636
+ function build_chip_innerHTML(color, theme, icon, label, key, highlight) {
55581
55637
  let title = key || label;
55582
55638
  let dark = theme === "dark";
55583
55639
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
55584
55640
  let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
55585
55641
  let text_color = dark ? "#e8eaed" : "#0f172a";
55642
+ if (highlight) border = HIGHLIGHT_COLOR;
55586
55643
  let icon_html = "";
55587
55644
  if (icon) icon_html = `<img src="${(0, _yuneta_gobj_js.safeSrc)(icon)}" alt="" style="
55588
55645
  width: 16px; height: 16px; object-fit: contain;
@@ -55594,7 +55651,8 @@ function build_chip_innerHTML(color, theme, icon, label, key) {
55594
55651
  width: 100%;
55595
55652
  height: 100%;
55596
55653
  background: ${bg};
55597
- border: 1px solid ${border};
55654
+ border: ${highlight ? "3px" : "1px"} solid ${border};
55655
+ ${highlight ? `box-shadow: 0 0 0 4px ${HIGHLIGHT_HALO};` : ""}
55598
55656
  border-radius: 8px;
55599
55657
  color: ${text_color};
55600
55658
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
@@ -55620,7 +55678,7 @@ function build_chip_innerHTML(color, theme, icon, label, key) {
55620
55678
  * colour is kept (per-topic differentiation) but softened via
55621
55679
  * color-mix instead of a harsh saturated fill. Theme-aware.
55622
55680
  ************************************************************/
55623
- function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key) {
55681
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight) {
55624
55682
  let title = key || label;
55625
55683
  let dark = theme === "dark";
55626
55684
  let surface = dark ? "#1b2230" : "#ffffff";
@@ -55634,6 +55692,10 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
55634
55692
  border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
55635
55693
  border_style = "solid";
55636
55694
  }
55695
+ if (highlight) {
55696
+ border = HIGHLIGHT_COLOR;
55697
+ border_style = "solid";
55698
+ }
55637
55699
  let title_color = dark ? "#e8eaed" : "#0f172a";
55638
55700
  let sub_color = dark ? "#9aa4b2" : "#64748b";
55639
55701
  let shadow = dark ? "0 1px 3px rgba(0,0,0,0.45), 0 1px 2px rgba(0,0,0,0.30)" : "0 1px 3px rgba(15,23,42,0.12), 0 1px 2px rgba(15,23,42,0.06)";
@@ -55656,9 +55718,9 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
55656
55718
  width: 100%;
55657
55719
  height: 100%;
55658
55720
  background: ${bg};
55659
- border: 1.5px ${border_style} ${border};
55721
+ border: ${highlight ? "3px" : "1.5px"} ${border_style} ${border};
55660
55722
  border-radius: 10px;
55661
- box-shadow: ${shadow};
55723
+ box-shadow: ${highlight ? `0 0 0 4px ${HIGHLIGHT_HALO}, ${shadow}` : shadow};
55662
55724
  color: ${title_color};
55663
55725
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
55664
55726
  display: flex;
@@ -18581,6 +18581,7 @@ var PRIVATE_DATA$4 = {
18581
18581
  topics: [],
18582
18582
  records: {},
18583
18583
  gobj_nodes_tree: null,
18584
+ find_timer: null,
18584
18585
  gobj_treedb_tables: null,
18585
18586
  hook_data_viewer: null,
18586
18587
  json_gobj: null,
@@ -18655,6 +18656,11 @@ function mt_stop$4(gobj) {
18655
18656
  * Framework Method: Destroy
18656
18657
  ***************************************************************/
18657
18658
  function mt_destroy$4(gobj) {
18659
+ let priv = gobj.priv;
18660
+ if (priv.find_timer) {
18661
+ clearTimeout(priv.find_timer);
18662
+ priv.find_timer = null;
18663
+ }
18658
18664
  destroy_ui$1(gobj);
18659
18665
  }
18660
18666
  /***************************
@@ -18761,7 +18767,7 @@ function destroy_ui$1(gobj) {
18761
18767
  *
18762
18768
  ************************************************************/
18763
18769
  function make_toolbar(gobj) {
18764
- gobj.priv;
18770
+ let priv = gobj.priv;
18765
18771
  let modes = gobj_read_attr(gobj, "operation_modes");
18766
18772
  if (gobj_read_bool_attr(gobj, "readonly")) modes = modes.filter((mode) => mode !== "edition");
18767
18773
  let left_items = [
@@ -18870,7 +18876,12 @@ function make_toolbar(gobj) {
18870
18876
  ]],
18871
18877
  { input: (evt) => {
18872
18878
  evt.stopPropagation();
18873
- gobj_send_event(gobj, "EV_FIND_NODES", { text: evt.target.value.trim() }, gobj);
18879
+ let text = evt.target.value.trim();
18880
+ if (priv.find_timer) clearTimeout(priv.find_timer);
18881
+ priv.find_timer = setTimeout(function() {
18882
+ priv.find_timer = null;
18883
+ gobj_send_event(gobj, "EV_FIND_NODES", { text }, gobj);
18884
+ }, 250);
18874
18885
  } }
18875
18886
  ], [
18876
18887
  "div",
@@ -52629,6 +52640,8 @@ ensure_drag_canvas_patch();
52629
52640
  * Constants
52630
52641
  ***************************************************************/
52631
52642
  var GCLASS_NAME$1 = "C_G6_NODES_TREE";
52643
+ var HIGHLIGHT_COLOR = "#f0a020";
52644
+ var HIGHLIGHT_HALO = "rgba(240,160,32,0.35)";
52632
52645
  /***************************************************************
52633
52646
  * Internal layout and operation mode definitions
52634
52647
  ***************************************************************/
@@ -53905,10 +53918,12 @@ function graph_focus_topic(gobj, topic) {
53905
53918
  if (ids.length === 0) log_warning(`${gobj_short_name(gobj)}: focus topic '${topic}' has no nodes`);
53906
53919
  for (let id of ids) states[id] = ["active"];
53907
53920
  }
53921
+ let prev_ids = priv._focus_ids || [];
53908
53922
  priv._focus_ids = ids;
53909
53923
  priv._focus_topic = topic || null;
53910
53924
  try {
53911
53925
  if (typeof graph.setElementState === "function") graph.setElementState(states);
53926
+ apply_node_highlight(gobj, prev_ids, ids);
53912
53927
  if (ids.length && typeof graph.focusElement === "function") graph.focusElement(ids);
53913
53928
  } catch (e) {
53914
53929
  log_error(`${gobj_short_name(gobj)}: focus topic apply failed: ${e}`);
@@ -53965,10 +53980,12 @@ function graph_find_nodes(gobj, term) {
53965
53980
  }
53966
53981
  for (let id of ids) states[id] = ["active"];
53967
53982
  }
53983
+ let prev_ids = priv._focus_ids || [];
53968
53984
  priv._focus_ids = ids;
53969
53985
  priv._focus_topic = null;
53970
53986
  try {
53971
53987
  if (typeof graph.setElementState === "function") graph.setElementState(states);
53988
+ apply_node_highlight(gobj, prev_ids, ids);
53972
53989
  if (ids.length && typeof graph.focusElement === "function") graph.focusElement(ids);
53973
53990
  } catch (e) {
53974
53991
  log_error(`${gobj_short_name(gobj)}: find apply failed: ${e}`);
@@ -55512,27 +55529,66 @@ function show_node_detail_popover(gobj, node_id) {
55512
55529
  * not re-themed by setTheme()) are regenerated; structural
55513
55530
  * junction diamonds get their neutral fill/stroke re-themed.
55514
55531
  ************************************************************/
55532
+ /************************************************************
55533
+ * The innerHTML of ONE node, in the given theme and highlight state.
55534
+ * The three treedb tiers (hierarchical / extended / child) each have
55535
+ * their own card, and both the theme refresh and the highlight need the
55536
+ * same three-way choice — it lived inline in the theme refresh and is
55537
+ * shared now. Returns null for a node that carries no desc.
55538
+ ************************************************************/
55539
+ function node_innerHTML_of(nd, theme, highlight) {
55540
+ if (!nd || !nd.data || !nd.data.desc) return null;
55541
+ let desc = nd.data.desc;
55542
+ let record = nd.data.record || {};
55543
+ let label = node_label(desc, record);
55544
+ switch (desc.node_treedb_type) {
55545
+ case "child": return build_chip_innerHTML(desc.color, theme, record.icon, label, record.id, highlight);
55546
+ case "extended": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, true, record.id, highlight);
55547
+ case "hierarchical": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, false, record.id, highlight);
55548
+ }
55549
+ return null;
55550
+ }
55551
+ /************************************************************
55552
+ * Repaint the cards whose highlight state CHANGES: the ones that had it
55553
+ * and lose it, plus the ones that gain it. Only those — a treedb graph
55554
+ * is redrawn per keystroke of the find box otherwise.
55555
+ ************************************************************/
55556
+ function apply_node_highlight(gobj, prev_ids, next_ids) {
55557
+ let priv = gobj.priv;
55558
+ let graph = priv.graph;
55559
+ if (!graph) return;
55560
+ let next = new Set(next_ids || []);
55561
+ let touched = /* @__PURE__ */ new Set([...prev_ids || [], ...next]);
55562
+ if (touched.size === 0) return;
55563
+ let updates = [];
55564
+ for (let id of touched) {
55565
+ let html = node_innerHTML_of(graph.getNodeData(id), priv.theme, next.has(id));
55566
+ if (html !== null) updates.push({
55567
+ id,
55568
+ style: { innerHTML: html }
55569
+ });
55570
+ }
55571
+ if (!updates.length) return;
55572
+ try {
55573
+ graph.updateNodeData(updates);
55574
+ graph.draw();
55575
+ } catch (e) {
55576
+ log_error(`${gobj_short_name(gobj)}: cannot repaint the highlight: ${e}`);
55577
+ }
55578
+ }
55515
55579
  function refresh_html_nodes_theme(gobj, theme) {
55516
- let graph = gobj.priv.graph;
55580
+ let priv = gobj.priv;
55581
+ let graph = priv.graph;
55517
55582
  if (!graph) return;
55518
55583
  let nodes = graph.getData().nodes || [];
55584
+ let highlighted = new Set(priv._focus_ids || []);
55519
55585
  let updates = [];
55520
55586
  for (let i = 0; i < nodes.length; i++) {
55521
- let nd = graph.getNodeData(nodes[i].id);
55522
- if (!nd || !nd.data || !nd.data.desc) continue;
55523
- let tt = nd.data.desc.node_treedb_type;
55524
- let record = nd.data.record || {};
55525
- if (tt === "hierarchical") updates.push({
55526
- id: nodes[i].id,
55527
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id) }
55528
- });
55529
- else if (tt === "child") updates.push({
55530
- id: nodes[i].id,
55531
- style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), record.id) }
55532
- });
55533
- else if (tt === "extended") updates.push({
55534
- id: nodes[i].id,
55535
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, true, record.id) }
55587
+ let id = nodes[i].id;
55588
+ let html = node_innerHTML_of(graph.getNodeData(id), theme, highlighted.has(id));
55589
+ if (html !== null) updates.push({
55590
+ id,
55591
+ style: { innerHTML: html }
55536
55592
  });
55537
55593
  }
55538
55594
  if (updates.length > 0) graph.updateNodeData(updates);
@@ -55564,12 +55620,13 @@ function refresh_default_edges_theme(gobj, theme) {
55564
55620
  * but lighter (1px border, no shadow, single line). The name is
55565
55621
  * always legible (ellipsis + native title tooltip on overflow).
55566
55622
  ************************************************************/
55567
- function build_chip_innerHTML(color, theme, icon, label, key) {
55623
+ function build_chip_innerHTML(color, theme, icon, label, key, highlight) {
55568
55624
  let title = key || label;
55569
55625
  let dark = theme === "dark";
55570
55626
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
55571
55627
  let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
55572
55628
  let text_color = dark ? "#e8eaed" : "#0f172a";
55629
+ if (highlight) border = HIGHLIGHT_COLOR;
55573
55630
  let icon_html = "";
55574
55631
  if (icon) icon_html = `<img src="${safeSrc(icon)}" alt="" style="
55575
55632
  width: 16px; height: 16px; object-fit: contain;
@@ -55581,7 +55638,8 @@ function build_chip_innerHTML(color, theme, icon, label, key) {
55581
55638
  width: 100%;
55582
55639
  height: 100%;
55583
55640
  background: ${bg};
55584
- border: 1px solid ${border};
55641
+ border: ${highlight ? "3px" : "1px"} solid ${border};
55642
+ ${highlight ? `box-shadow: 0 0 0 4px ${HIGHLIGHT_HALO};` : ""}
55585
55643
  border-radius: 8px;
55586
55644
  color: ${text_color};
55587
55645
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
@@ -55607,7 +55665,7 @@ function build_chip_innerHTML(color, theme, icon, label, key) {
55607
55665
  * colour is kept (per-topic differentiation) but softened via
55608
55666
  * color-mix instead of a harsh saturated fill. Theme-aware.
55609
55667
  ************************************************************/
55610
- function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key) {
55668
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight) {
55611
55669
  let title = key || label;
55612
55670
  let dark = theme === "dark";
55613
55671
  let surface = dark ? "#1b2230" : "#ffffff";
@@ -55621,6 +55679,10 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
55621
55679
  border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
55622
55680
  border_style = "solid";
55623
55681
  }
55682
+ if (highlight) {
55683
+ border = HIGHLIGHT_COLOR;
55684
+ border_style = "solid";
55685
+ }
55624
55686
  let title_color = dark ? "#e8eaed" : "#0f172a";
55625
55687
  let sub_color = dark ? "#9aa4b2" : "#64748b";
55626
55688
  let shadow = dark ? "0 1px 3px rgba(0,0,0,0.45), 0 1px 2px rgba(0,0,0,0.30)" : "0 1px 3px rgba(15,23,42,0.12), 0 1px 2px rgba(15,23,42,0.06)";
@@ -55643,9 +55705,9 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
55643
55705
  width: 100%;
55644
55706
  height: 100%;
55645
55707
  background: ${bg};
55646
- border: 1.5px ${border_style} ${border};
55708
+ border: ${highlight ? "3px" : "1.5px"} ${border_style} ${border};
55647
55709
  border-radius: 10px;
55648
- box-shadow: ${shadow};
55710
+ box-shadow: ${highlight ? `0 0 0 4px ${HIGHLIGHT_HALO}, ${shadow}` : shadow};
55649
55711
  color: ${title_color};
55650
55712
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
55651
55713
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.2.5",
3
+ "version": "7.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -140,6 +140,14 @@ ensure_drag_canvas_patch();
140
140
  ***************************************************************/
141
141
  const GCLASS_NAME = "C_G6_NODES_TREE";
142
142
 
143
+ /* The highlight of a focused topic / a find match. It is drawn INTO the
144
+ * node's own html, not with G6's `active` element state: every node here
145
+ * is an `html` node, whose key shape is a DOM element — the state's
146
+ * `stroke` / `halo` have nothing to paint on and the amber never
147
+ * appeared, for the topic focus either. */
148
+ const HIGHLIGHT_COLOR = "#f0a020";
149
+ const HIGHLIGHT_HALO = "rgba(240,160,32,0.35)";
150
+
143
151
  /***************************************************************
144
152
  * Internal layout and operation mode definitions
145
153
  ***************************************************************/
@@ -2113,6 +2121,7 @@ function graph_focus_topic(gobj, topic)
2113
2121
  states[id] = ['active'];
2114
2122
  }
2115
2123
  }
2124
+ let prev_ids = priv._focus_ids || [];
2116
2125
  priv._focus_ids = ids;
2117
2126
  priv._focus_topic = topic || null;
2118
2127
 
@@ -2120,6 +2129,7 @@ function graph_focus_topic(gobj, topic)
2120
2129
  if(typeof graph.setElementState === "function") {
2121
2130
  graph.setElementState(states);
2122
2131
  }
2132
+ apply_node_highlight(gobj, prev_ids, ids);
2123
2133
  if(ids.length && typeof graph.focusElement === "function") {
2124
2134
  graph.focusElement(ids);
2125
2135
  }
@@ -2194,6 +2204,7 @@ function graph_find_nodes(gobj, term)
2194
2204
  }
2195
2205
  }
2196
2206
 
2207
+ let prev_ids = priv._focus_ids || [];
2197
2208
  priv._focus_ids = ids;
2198
2209
  priv._focus_topic = null; /* a find takes over the highlight */
2199
2210
 
@@ -2201,6 +2212,7 @@ function graph_find_nodes(gobj, term)
2201
2212
  if(typeof graph.setElementState === "function") {
2202
2213
  graph.setElementState(states);
2203
2214
  }
2215
+ apply_node_highlight(gobj, prev_ids, ids);
2204
2216
  if(ids.length && typeof graph.focusElement === "function") {
2205
2217
  graph.focusElement(ids);
2206
2218
  }
@@ -4469,6 +4481,79 @@ function show_node_detail_popover(gobj, node_id)
4469
4481
  * not re-themed by setTheme()) are regenerated; structural
4470
4482
  * junction diamonds get their neutral fill/stroke re-themed.
4471
4483
  ************************************************************/
4484
+ /************************************************************
4485
+ * The innerHTML of ONE node, in the given theme and highlight state.
4486
+ * The three treedb tiers (hierarchical / extended / child) each have
4487
+ * their own card, and both the theme refresh and the highlight need the
4488
+ * same three-way choice — it lived inline in the theme refresh and is
4489
+ * shared now. Returns null for a node that carries no desc.
4490
+ ************************************************************/
4491
+ function node_innerHTML_of(nd, theme, highlight)
4492
+ {
4493
+ if(!nd || !nd.data || !nd.data.desc) {
4494
+ return null;
4495
+ }
4496
+ let desc = nd.data.desc;
4497
+ let record = nd.data.record || {};
4498
+ let label = node_label(desc, record);
4499
+
4500
+ switch(desc.node_treedb_type) {
4501
+ case 'child':
4502
+ return build_chip_innerHTML(
4503
+ desc.color, theme, record.icon, label, record.id, highlight
4504
+ );
4505
+ case 'extended':
4506
+ return build_node_innerHTML(
4507
+ desc.color, theme, record.icon, label,
4508
+ desc.topic_name, true, record.id, highlight
4509
+ );
4510
+ case 'hierarchical':
4511
+ return build_node_innerHTML(
4512
+ desc.color, theme, record.icon, label,
4513
+ desc.topic_name, false, record.id, highlight
4514
+ );
4515
+ }
4516
+ return null;
4517
+ }
4518
+
4519
+ /************************************************************
4520
+ * Repaint the cards whose highlight state CHANGES: the ones that had it
4521
+ * and lose it, plus the ones that gain it. Only those — a treedb graph
4522
+ * is redrawn per keystroke of the find box otherwise.
4523
+ ************************************************************/
4524
+ function apply_node_highlight(gobj, prev_ids, next_ids)
4525
+ {
4526
+ let priv = gobj.priv;
4527
+ let graph = priv.graph;
4528
+ if(!graph) {
4529
+ return;
4530
+ }
4531
+
4532
+ let next = new Set(next_ids || []);
4533
+ let touched = new Set([...(prev_ids || []), ...next]);
4534
+ if(touched.size === 0) {
4535
+ return;
4536
+ }
4537
+
4538
+ let updates = [];
4539
+ for(let id of touched) {
4540
+ let nd = graph.getNodeData(id);
4541
+ let html = node_innerHTML_of(nd, priv.theme, next.has(id));
4542
+ if(html !== null) {
4543
+ updates.push({id: id, style: {innerHTML: html}});
4544
+ }
4545
+ }
4546
+ if(!updates.length) {
4547
+ return;
4548
+ }
4549
+ try {
4550
+ graph.updateNodeData(updates);
4551
+ graph.draw();
4552
+ } catch(e) {
4553
+ log_error(`${gobj_short_name(gobj)}: cannot repaint the highlight: ${e}`);
4554
+ }
4555
+ }
4556
+
4472
4557
  function refresh_html_nodes_theme(gobj, theme)
4473
4558
  {
4474
4559
  let priv = gobj.priv;
@@ -4477,46 +4562,16 @@ function refresh_html_nodes_theme(gobj, theme)
4477
4562
  return;
4478
4563
  }
4479
4564
  let nodes = graph.getData().nodes || [];
4565
+ /* The highlight is part of the card's html, so a theme switch that
4566
+ * rebuilt every card without it would silently CLEAR the focus or the
4567
+ * find that is on screen. Carry it across. */
4568
+ let highlighted = new Set(priv._focus_ids || []);
4480
4569
  let updates = [];
4481
4570
  for(let i = 0; i < nodes.length; i++) {
4482
- let nd = graph.getNodeData(nodes[i].id);
4483
- if(!nd || !nd.data || !nd.data.desc) {
4484
- continue;
4485
- }
4486
- let tt = nd.data.desc.node_treedb_type;
4487
- let record = nd.data.record || {};
4488
- if(tt === 'hierarchical') {
4489
- updates.push({
4490
- id: nodes[i].id,
4491
- style: {
4492
- innerHTML: build_node_innerHTML(
4493
- nd.data.desc.color, theme, record.icon,
4494
- node_label(nd.data.desc, record),
4495
- nd.data.desc.topic_name, false, record.id
4496
- )
4497
- }
4498
- });
4499
- } else if(tt === 'child') {
4500
- updates.push({
4501
- id: nodes[i].id,
4502
- style: {
4503
- innerHTML: build_chip_innerHTML(
4504
- nd.data.desc.color, theme, record.icon,
4505
- node_label(nd.data.desc, record), record.id
4506
- )
4507
- }
4508
- });
4509
- } else if(tt === 'extended') {
4510
- updates.push({
4511
- id: nodes[i].id,
4512
- style: {
4513
- innerHTML: build_node_innerHTML(
4514
- nd.data.desc.color, theme, record.icon,
4515
- node_label(nd.data.desc, record),
4516
- nd.data.desc.topic_name, true, record.id
4517
- )
4518
- }
4519
- });
4571
+ let id = nodes[i].id;
4572
+ let html = node_innerHTML_of(graph.getNodeData(id), theme, highlighted.has(id));
4573
+ if(html !== null) {
4574
+ updates.push({id: id, style: {innerHTML: html}});
4520
4575
  }
4521
4576
  }
4522
4577
  if(updates.length > 0) {
@@ -4559,7 +4614,7 @@ function refresh_default_edges_theme(gobj, theme)
4559
4614
  * but lighter (1px border, no shadow, single line). The name is
4560
4615
  * always legible (ellipsis + native title tooltip on overflow).
4561
4616
  ************************************************************/
4562
- function build_chip_innerHTML(color, theme, icon, label, key)
4617
+ function build_chip_innerHTML(color, theme, icon, label, key, highlight)
4563
4618
  {
4564
4619
  let title = key || label;
4565
4620
  let dark = (theme === "dark");
@@ -4571,6 +4626,9 @@ function build_chip_innerHTML(color, theme, icon, label, key)
4571
4626
  ? `color-mix(in srgb, ${color} 85%, #ffffff)`
4572
4627
  : color;
4573
4628
  let text_color = dark ? "#e8eaed" : "#0f172a";
4629
+ if(highlight) {
4630
+ border = HIGHLIGHT_COLOR;
4631
+ }
4574
4632
 
4575
4633
  let icon_html = "";
4576
4634
  if(icon) {
@@ -4586,7 +4644,8 @@ function build_chip_innerHTML(color, theme, icon, label, key)
4586
4644
  width: 100%;
4587
4645
  height: 100%;
4588
4646
  background: ${bg};
4589
- border: 1px solid ${border};
4647
+ border: ${highlight? "3px" : "1px"} solid ${border};
4648
+ ${highlight? `box-shadow: 0 0 0 4px ${HIGHLIGHT_HALO};` : ""}
4590
4649
  border-radius: 8px;
4591
4650
  color: ${text_color};
4592
4651
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
@@ -4613,7 +4672,7 @@ function build_chip_innerHTML(color, theme, icon, label, key)
4613
4672
  * colour is kept (per-topic differentiation) but softened via
4614
4673
  * color-mix instead of a harsh saturated fill. Theme-aware.
4615
4674
  ************************************************************/
4616
- function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key)
4675
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight)
4617
4676
  {
4618
4677
  let title = key || label;
4619
4678
  let dark = (theme === "dark");
@@ -4639,6 +4698,10 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
4639
4698
  : color;
4640
4699
  border_style = "solid";
4641
4700
  }
4701
+ if(highlight) {
4702
+ border = HIGHLIGHT_COLOR;
4703
+ border_style = "solid";
4704
+ }
4642
4705
  let title_color = dark ? "#e8eaed" : "#0f172a";
4643
4706
  let sub_color = dark ? "#9aa4b2" : "#64748b";
4644
4707
  let shadow = dark
@@ -4670,9 +4733,9 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
4670
4733
  width: 100%;
4671
4734
  height: 100%;
4672
4735
  background: ${bg};
4673
- border: 1.5px ${border_style} ${border};
4736
+ border: ${highlight? "3px" : "1.5px"} ${border_style} ${border};
4674
4737
  border-radius: 10px;
4675
- box-shadow: ${shadow};
4738
+ box-shadow: ${highlight? `0 0 0 4px ${HIGHLIGHT_HALO}, ${shadow}` : shadow};
4676
4739
  color: ${title_color};
4677
4740
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
4678
4741
  display: flex;
@@ -147,6 +147,7 @@ let PRIVATE_DATA = {
147
147
  topics: [],
148
148
  records: {},
149
149
  gobj_nodes_tree: null,
150
+ find_timer: null, // rate-limits the find box (see make_toolbar)
150
151
  gobj_treedb_tables: null,
151
152
  hook_data_viewer: null,
152
153
  json_gobj: null, /* C_YUI_JSON viewer of the raw tranger (or null) */
@@ -310,6 +311,14 @@ function mt_stop(gobj)
310
311
  ***************************************************************/
311
312
  function mt_destroy(gobj)
312
313
  {
314
+ let priv = gobj.priv;
315
+ /* The find box rate-limits itself with a timer; a view torn down
316
+ * between the keystroke and the send would fire an event into a
317
+ * destroyed gobj. */
318
+ if(priv.find_timer) {
319
+ clearTimeout(priv.find_timer);
320
+ priv.find_timer = null;
321
+ }
313
322
  destroy_ui(gobj);
314
323
  }
315
324
 
@@ -530,14 +539,21 @@ function make_toolbar(gobj)
530
539
  ['i', {class: 'yi-magnifying-glass'}]
531
540
  ]]
532
541
  ], {
542
+ /* Rate-limited, not delayed for effect: a match repaints the
543
+ * cards it lands on, and on a large treedb the first letter
544
+ * typed can match hundreds. This is input plumbing — the
545
+ * event still carries every action to the FSM, just not one
546
+ * per keystroke. */
533
547
  input: (evt) => {
534
548
  evt.stopPropagation();
535
- gobj_send_event(
536
- gobj,
537
- "EV_FIND_NODES",
538
- {text: evt.target.value.trim()},
539
- gobj
540
- );
549
+ let text = evt.target.value.trim();
550
+ if(priv.find_timer) {
551
+ clearTimeout(priv.find_timer);
552
+ }
553
+ priv.find_timer = setTimeout(function() {
554
+ priv.find_timer = null;
555
+ gobj_send_event(gobj, "EV_FIND_NODES", {text: text}, gobj);
556
+ }, 250);
541
557
  }
542
558
  }],
543
559
  /* Two spans, not one string: the number is DATA and "matches" is