@yuneta/gobj-ui 7.23.24 → 7.23.26

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.
@@ -9679,7 +9679,7 @@ function yui_graph_update_anchor($container, state) {
9679
9679
  if (!$container) return;
9680
9680
  let $btn = $container.querySelector(".EV_TOGGLE_ANCHOR");
9681
9681
  if (!$btn) return;
9682
- set_pressed_state($container, ".EV_TOGGLE_ANCHOR", state !== "off");
9682
+ set_pressed_state($container, ".EV_TOGGLE_ANCHOR", state === "on");
9683
9683
  $btn.classList.toggle("color_pending_state", state === "arming");
9684
9684
  $btn.setAttribute("aria-pressed", state === "on" ? "true" : "false");
9685
9685
  let key = state === "arming" ? "click the element to centre on" : state === "on" ? "centred: click to release" : "anchor view";
@@ -9695,21 +9695,55 @@ function yui_graph_update_anchor($container, state) {
9695
9695
  * the zoom is what the reader asked for, the centring is what
9696
9696
  * keeps the thing they are reading under their eyes.
9697
9697
  *
9698
+ * The translate is computed and applied HERE rather than
9699
+ * delegated to `graph.focusElement()`, which is the obvious
9700
+ * call and does NOT work on these graphs. Measured: it
9701
+ * resolves, having computed the very offset wanted -- `[0,
9702
+ * 306]` for a card 306px above centre -- and the camera does
9703
+ * not move. `getPosition()` reads the same before and after,
9704
+ * and so does the card's box on screen. Three public calls do
9705
+ * move it, so that is what is used.
9706
+ *
9698
9707
  * Guarded on every side because it runs on every wheel notch:
9699
9708
  * a graph mid-rebuild, an anchor whose node went away with a
9700
- * fold, a G6 version without `focusElement` -- none of those is
9709
+ * fold, a G6 missing one of these calls -- none of those is
9701
9710
  * an error worth a log line per notch, they are all "there is
9702
9711
  * nothing to centre right now".
9703
9712
  ************************************************************/
9704
- function yui_graph_center_on(graph, node_id) {
9713
+ var __centring__ = /* @__PURE__ */ new WeakMap();
9714
+ async function yui_graph_center_on(graph, node_id) {
9705
9715
  if (!graph || !node_id) return false;
9716
+ let st = __centring__.get(graph);
9717
+ if (st && st.running) {
9718
+ st.again = node_id;
9719
+ return true;
9720
+ }
9721
+ __centring__.set(graph, {
9722
+ running: true,
9723
+ again: null
9724
+ });
9706
9725
  try {
9707
9726
  if (typeof graph.getElementData === "function" && !graph.getElementData(node_id)) return false;
9708
- if (typeof graph.focusElement !== "function") return false;
9709
- graph.focusElement(node_id);
9727
+ if (typeof graph.getElementPosition !== "function" || typeof graph.getViewportByCanvas !== "function" || typeof graph.getCanvasCenter !== "function" || typeof graph.translateTo !== "function") return false;
9728
+ for (let i = 0; i < 3; i++) {
9729
+ let at = graph.getViewportByCanvas(graph.getElementPosition(node_id));
9730
+ let want = graph.getCanvasCenter();
9731
+ let dx = want[0] - at[0];
9732
+ let dy = want[1] - at[1];
9733
+ if (Math.abs(dx) < 1 && Math.abs(dy) < 1) break;
9734
+ let pos = graph.getPosition();
9735
+ await graph.translateTo([pos[0] + dx, pos[1] + dy]);
9736
+ }
9710
9737
  return true;
9711
9738
  } catch (e) {
9712
9739
  return false;
9740
+ } finally {
9741
+ let pending = __centring__.get(graph);
9742
+ __centring__.set(graph, {
9743
+ running: false,
9744
+ again: null
9745
+ });
9746
+ if (pending && pending.again) yui_graph_center_on(graph, pending.again);
9713
9747
  }
9714
9748
  }
9715
9749
  /************************************************************
@@ -10924,11 +10958,7 @@ function build_graph$3(gobj) {
10924
10958
  }
10925
10959
  },
10926
10960
  layout: layout_cfg.g6_layout,
10927
- behaviors: [
10928
- "drag-canvas",
10929
- "zoom-canvas",
10930
- "drag-element"
10931
- ]
10961
+ behaviors: BEHAVIORS_READING$1.slice()
10932
10962
  });
10933
10963
  if (priv.theme) graph.setTheme(priv.theme);
10934
10964
  graph.on(_antv_g6.NodeEvent.CLICK, (evt) => {
@@ -11035,6 +11065,12 @@ function build_cell_html(key, value, type, dark, matched, fold) {
11035
11065
  * forever. `+3` and `−` need no language.
11036
11066
  ************************************************************/
11037
11067
  var FOLD_SPACER = `<span style="width:24px; flex:0 0 auto;"></span>`;
11068
+ var BEHAVIORS_READING$1 = [
11069
+ "drag-canvas",
11070
+ "zoom-canvas",
11071
+ "drag-element"
11072
+ ];
11073
+ var BEHAVIORS_PICKING$1 = ["drag-canvas", "zoom-canvas"];
11038
11074
  var CARD_HEADER_H$1 = 24;
11039
11075
  var CARD_ROW_H$1 = 22;
11040
11076
  function fold_toggle_html(path, foldable, folded, branches, colors) {
@@ -11171,7 +11207,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
11171
11207
  let min_width = Math.max(180, label.length * 9);
11172
11208
  let card_height = CARD_HEADER_H$1 + lines.length * CARD_ROW_H$1;
11173
11209
  let node_html = `
11174
- <div style="
11210
+ <div class="JSON_CARD" data-json-path="${(0, _yuneta_gobj_js.escapeHtml)(path)}" style="
11175
11211
  min-width: ${min_width}px;
11176
11212
  background: ${colors.bg};
11177
11213
  border: ${border_width}px ${is_dict ? "dashed" : "solid"} ${border_color};
@@ -11275,6 +11311,7 @@ function load_json(gobj) {
11275
11311
  });
11276
11312
  graph.render().then(() => {
11277
11313
  reanchor$1(gobj);
11314
+ paint_anchor_mark(gobj);
11278
11315
  if (preserve_view) {
11279
11316
  yui_graph_update_zoom((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.graph);
11280
11317
  return;
@@ -11596,6 +11633,52 @@ function reanchor$1(gobj) {
11596
11633
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
11597
11634
  }
11598
11635
  /************************************************************
11636
+ * Reading, or picking the node to anchor.
11637
+ *
11638
+ * Two visible consequences and one invisible: the cursor says
11639
+ * a click is expected, the mount says which mode it is in for
11640
+ * anyone reading the DOM, and node dragging stands aside so a
11641
+ * click that drifts is still a click.
11642
+ ************************************************************/
11643
+ function set_picking_mode$1(gobj, picking) {
11644
+ let graph = gobj.priv.graph;
11645
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING$1 : BEHAVIORS_READING$1).slice());
11646
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
11647
+ if ($container) {
11648
+ let $mount = $container.querySelector(".json-graph-container");
11649
+ if ($mount) $mount.classList.toggle("JSON_GRAPH_PICKING", !!picking);
11650
+ }
11651
+ }
11652
+ /************************************************************
11653
+ * Mark the card the camera is holding.
11654
+ *
11655
+ * A class on the card's own element and not a G6 node state:
11656
+ * G6 paints no state style on an html node, so `setElementState`
11657
+ * would select it correctly and show nothing.
11658
+ *
11659
+ * Re-applied after every rebuild, because the cards are new DOM
11660
+ * each time and the mark would otherwise survive only until the
11661
+ * next fold.
11662
+ ************************************************************/
11663
+ function paint_anchor_mark(gobj) {
11664
+ let priv = gobj.priv;
11665
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
11666
+ if (!$container) return;
11667
+ for (let $card of $container.querySelectorAll(".JSON_CARD_ANCHORED")) $card.classList.remove("JSON_CARD_ANCHORED");
11668
+ if (priv.anchor_state !== "on" || !priv.anchor_path) return;
11669
+ let $it = $container.querySelector(".JSON_CARD[data-json-path=\"" + CSS.escape(priv.anchor_path) + "\"]");
11670
+ if ($it) $it.classList.add("JSON_CARD_ANCHORED");
11671
+ }
11672
+ /************************************************************
11673
+ * Centre on the anchor, one cycle after it was chosen.
11674
+ ************************************************************/
11675
+ function ac_center_anchor$2(gobj, event, kw, src) {
11676
+ let priv = gobj.priv;
11677
+ paint_anchor_mark(gobj);
11678
+ if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
11679
+ return 0;
11680
+ }
11681
+ /************************************************************
11599
11682
  * Arm the anchor, or let it go.
11600
11683
  *
11601
11684
  * Three states and one button, so a press ADVANCES: with no
@@ -11612,6 +11695,8 @@ function ac_toggle_anchor$2(gobj, event, kw, src) {
11612
11695
  priv.anchor_path = "";
11613
11696
  }
11614
11697
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
11698
+ paint_anchor_mark(gobj);
11699
+ set_picking_mode$1(gobj, priv.anchor_state === "arming");
11615
11700
  return 0;
11616
11701
  }
11617
11702
  /************************************************************
@@ -11678,7 +11763,8 @@ function ac_node_click$4(gobj, event, kw, src) {
11678
11763
  priv.anchor_path = nodedata && nodedata.data ? nodedata.data.path : "";
11679
11764
  priv.anchor_state = "on";
11680
11765
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
11681
- yui_graph_center_on(graph, node_id);
11766
+ set_picking_mode$1(gobj, false);
11767
+ (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CENTER_ANCHOR", {}, gobj);
11682
11768
  return 0;
11683
11769
  }
11684
11770
  if (nodedata && nodedata.data) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_JSON_ITEM_CLICKED", {
@@ -11819,6 +11905,11 @@ function create_gclass$11(gclass_name) {
11819
11905
  ac_toggle_anchor$2,
11820
11906
  null
11821
11907
  ],
11908
+ [
11909
+ "EV_CENTER_ANCHOR",
11910
+ ac_center_anchor$2,
11911
+ null
11912
+ ],
11822
11913
  [
11823
11914
  "EV_RESIZE",
11824
11915
  ac_resize$2,
@@ -11850,6 +11941,7 @@ function create_gclass$11(gclass_name) {
11850
11941
  ["EV_CENTER", 0],
11851
11942
  ["EV_NODE_CLICK", 0],
11852
11943
  ["EV_TOGGLE_ANCHOR", 0],
11944
+ ["EV_CENTER_ANCHOR", 0],
11853
11945
  ["EV_RESIZE", 0],
11854
11946
  ["EV_SHOW", 0],
11855
11947
  ["EV_HIDE", 0],
@@ -13138,6 +13230,12 @@ var attrs_table$9 = [
13138
13230
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "collapse_threshold", 0, 10, "Auto-collapse a node whose direct children exceed this number. 0 disables auto-collapse."),
13139
13231
  (0, _yuneta_gobj_js.SDATA_END)()
13140
13232
  ];
13233
+ var BEHAVIORS_READING = [
13234
+ "drag-canvas",
13235
+ "zoom-canvas",
13236
+ "drag-element"
13237
+ ];
13238
+ var BEHAVIORS_PICKING = ["drag-canvas", "zoom-canvas"];
13141
13239
  var PRIVATE_DATA$9 = {
13142
13240
  canvas_id: "",
13143
13241
  graph: null,
@@ -13619,11 +13717,7 @@ function build_graph$2(gobj) {
13619
13717
  }
13620
13718
  },
13621
13719
  layout: layout_cfg.g6_layout,
13622
- behaviors: [
13623
- "drag-canvas",
13624
- "zoom-canvas",
13625
- "drag-element"
13626
- ]
13720
+ behaviors: BEHAVIORS_READING.slice()
13627
13721
  });
13628
13722
  if (priv.theme) graph.setTheme(priv.theme);
13629
13723
  graph.on("aftertransform", () => {
@@ -14487,6 +14581,24 @@ function reanchor(gobj) {
14487
14581
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
14488
14582
  }
14489
14583
  /************************************************************
14584
+ * Centre on the anchor, one cycle after it was chosen.
14585
+ ************************************************************/
14586
+ function ac_center_anchor$1(gobj, event, kw, src) {
14587
+ let priv = gobj.priv;
14588
+ if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
14589
+ return 0;
14590
+ }
14591
+ /************************************************************
14592
+ * Reading, or picking the node to anchor.
14593
+ ************************************************************/
14594
+ function set_picking_mode(gobj, picking) {
14595
+ let graph = gobj.priv.graph;
14596
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING : BEHAVIORS_READING).slice());
14597
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
14598
+ let $mount = $container ? $container.querySelector(".gobj-tree-container") : null;
14599
+ if ($mount) $mount.classList.toggle("GOBJ_TREE_PICKING", !!picking);
14600
+ }
14601
+ /************************************************************
14490
14602
  * Arm the anchor, or let it go.
14491
14603
  *
14492
14604
  * Three states and one button, so a press ADVANCES: with no
@@ -14503,6 +14615,7 @@ function ac_toggle_anchor$1(gobj, event, kw, src) {
14503
14615
  priv.anchor_name = "";
14504
14616
  }
14505
14617
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
14618
+ set_picking_mode(gobj, priv.anchor_state === "arming");
14506
14619
  return 0;
14507
14620
  }
14508
14621
  /************************************************************
@@ -14571,7 +14684,8 @@ function ac_node_click$3(gobj, event, kw, src) {
14571
14684
  priv.anchor_name = node_data.full_name || "";
14572
14685
  priv.anchor_state = "on";
14573
14686
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
14574
- yui_graph_center_on(graph, node_id);
14687
+ set_picking_mode(gobj, false);
14688
+ (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CENTER_ANCHOR", {}, gobj);
14575
14689
  return 0;
14576
14690
  }
14577
14691
  let client_x = kw.evt && kw.evt.client ? kw.evt.client.x : 0;
@@ -14655,6 +14769,7 @@ function create_gclass$9(gclass_name) {
14655
14769
  ["EV_CENTER", 0],
14656
14770
  ["EV_NODE_CLICK", 0],
14657
14771
  ["EV_TOGGLE_ANCHOR", 0],
14772
+ ["EV_CENTER_ANCHOR", 0],
14658
14773
  ["EV_RESIZE", 0],
14659
14774
  ["EV_SHOW", 0],
14660
14775
  ["EV_HIDE", 0]
@@ -14719,6 +14834,11 @@ function create_gclass$9(gclass_name) {
14719
14834
  ac_toggle_anchor$1,
14720
14835
  null
14721
14836
  ],
14837
+ [
14838
+ "EV_CENTER_ANCHOR",
14839
+ ac_center_anchor$1,
14840
+ null
14841
+ ],
14722
14842
  [
14723
14843
  "EV_RESIZE",
14724
14844
  ac_resize$1,
@@ -37560,7 +37680,7 @@ Text.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(DisplayObject.PARS
37560
37680
  * * register property @see https://developer.mozilla.org/en-US/docs/Web/API/CSS/RegisterProperty
37561
37681
  * * CSS Layout API
37562
37682
  */
37563
- var CSS = {
37683
+ var CSS$1 = {
37564
37684
  /**
37565
37685
  * <number>
37566
37686
  * @see https://drafts.csswg.org/css-values-4/#number-value
@@ -47609,7 +47729,7 @@ var e = 1e-4;
47609
47729
  syntax: PropertySyntax.ANGLE
47610
47730
  }
47611
47731
  ].forEach(function(property) {
47612
- CSS.registerProperty(property);
47732
+ CSS$1.registerProperty(property);
47613
47733
  });
47614
47734
  var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInRadian) {
47615
47735
  return {
@@ -52931,6 +53051,14 @@ function ac_zoom_reset(gobj, event, kw, src) {
52931
53051
  /************************************************************
52932
53052
  * What the anchor button says it will do next.
52933
53053
  ************************************************************/
53054
+ function ac_center_anchor(gobj, event, kw, src) {
53055
+ let priv = gobj.priv;
53056
+ if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
53057
+ return 0;
53058
+ }
53059
+ /************************************************************
53060
+ * What the anchor button says it will do next.
53061
+ ************************************************************/
52934
53062
  function anchor_title(state) {
52935
53063
  if (state === "arming") return (0, i18next.t)("click the element to centre on");
52936
53064
  if (state === "on") return (0, i18next.t)("centred: click to release");
@@ -53149,7 +53277,7 @@ function ac_node_click$2(gobj, event, kw, src) {
53149
53277
  priv.anchor_id = node_id;
53150
53278
  priv.anchor_state = "on";
53151
53279
  update_toolbar(gobj);
53152
- yui_graph_center_on(graph, node_id);
53280
+ (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CENTER_ANCHOR", {}, gobj);
53153
53281
  return 0;
53154
53282
  }
53155
53283
  try {
@@ -53418,6 +53546,11 @@ function create_gclass$4(gclass_name) {
53418
53546
  ac_toggle_anchor,
53419
53547
  null
53420
53548
  ],
53549
+ [
53550
+ "EV_CENTER_ANCHOR",
53551
+ ac_center_anchor,
53552
+ null
53553
+ ],
53421
53554
  [
53422
53555
  "EV_TOGGLE_TOOLBARS",
53423
53556
  ac_toggle_toolbars,
@@ -53532,6 +53665,7 @@ function create_gclass$4(gclass_name) {
53532
53665
  ["EV_ZOOM_OUT", 0],
53533
53666
  ["EV_ZOOM_RESET", 0],
53534
53667
  ["EV_TOGGLE_ANCHOR", 0],
53668
+ ["EV_CENTER_ANCHOR", 0],
53535
53669
  ["EV_TOGGLE_TOOLBARS", 0],
53536
53670
  ["EV_AUTO_FIT", 0],
53537
53671
  ["EV_ZOOM_SELECTION", 0],
@@ -1,4 +1,4 @@
1
- import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
1
+ import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
2
2
  import i18next, { t } from "i18next";
3
3
  import TomSelect from "tom-select";
4
4
  import { createJSONEditor } from "vanilla-jsoneditor";
@@ -9674,7 +9674,7 @@ function yui_graph_update_anchor($container, state) {
9674
9674
  if (!$container) return;
9675
9675
  let $btn = $container.querySelector(".EV_TOGGLE_ANCHOR");
9676
9676
  if (!$btn) return;
9677
- set_pressed_state($container, ".EV_TOGGLE_ANCHOR", state !== "off");
9677
+ set_pressed_state($container, ".EV_TOGGLE_ANCHOR", state === "on");
9678
9678
  $btn.classList.toggle("color_pending_state", state === "arming");
9679
9679
  $btn.setAttribute("aria-pressed", state === "on" ? "true" : "false");
9680
9680
  let key = state === "arming" ? "click the element to centre on" : state === "on" ? "centred: click to release" : "anchor view";
@@ -9690,21 +9690,55 @@ function yui_graph_update_anchor($container, state) {
9690
9690
  * the zoom is what the reader asked for, the centring is what
9691
9691
  * keeps the thing they are reading under their eyes.
9692
9692
  *
9693
+ * The translate is computed and applied HERE rather than
9694
+ * delegated to `graph.focusElement()`, which is the obvious
9695
+ * call and does NOT work on these graphs. Measured: it
9696
+ * resolves, having computed the very offset wanted -- `[0,
9697
+ * 306]` for a card 306px above centre -- and the camera does
9698
+ * not move. `getPosition()` reads the same before and after,
9699
+ * and so does the card's box on screen. Three public calls do
9700
+ * move it, so that is what is used.
9701
+ *
9693
9702
  * Guarded on every side because it runs on every wheel notch:
9694
9703
  * a graph mid-rebuild, an anchor whose node went away with a
9695
- * fold, a G6 version without `focusElement` -- none of those is
9704
+ * fold, a G6 missing one of these calls -- none of those is
9696
9705
  * an error worth a log line per notch, they are all "there is
9697
9706
  * nothing to centre right now".
9698
9707
  ************************************************************/
9699
- function yui_graph_center_on(graph, node_id) {
9708
+ var __centring__ = /* @__PURE__ */ new WeakMap();
9709
+ async function yui_graph_center_on(graph, node_id) {
9700
9710
  if (!graph || !node_id) return false;
9711
+ let st = __centring__.get(graph);
9712
+ if (st && st.running) {
9713
+ st.again = node_id;
9714
+ return true;
9715
+ }
9716
+ __centring__.set(graph, {
9717
+ running: true,
9718
+ again: null
9719
+ });
9701
9720
  try {
9702
9721
  if (typeof graph.getElementData === "function" && !graph.getElementData(node_id)) return false;
9703
- if (typeof graph.focusElement !== "function") return false;
9704
- graph.focusElement(node_id);
9722
+ if (typeof graph.getElementPosition !== "function" || typeof graph.getViewportByCanvas !== "function" || typeof graph.getCanvasCenter !== "function" || typeof graph.translateTo !== "function") return false;
9723
+ for (let i = 0; i < 3; i++) {
9724
+ let at = graph.getViewportByCanvas(graph.getElementPosition(node_id));
9725
+ let want = graph.getCanvasCenter();
9726
+ let dx = want[0] - at[0];
9727
+ let dy = want[1] - at[1];
9728
+ if (Math.abs(dx) < 1 && Math.abs(dy) < 1) break;
9729
+ let pos = graph.getPosition();
9730
+ await graph.translateTo([pos[0] + dx, pos[1] + dy]);
9731
+ }
9705
9732
  return true;
9706
9733
  } catch (e) {
9707
9734
  return false;
9735
+ } finally {
9736
+ let pending = __centring__.get(graph);
9737
+ __centring__.set(graph, {
9738
+ running: false,
9739
+ again: null
9740
+ });
9741
+ if (pending && pending.again) yui_graph_center_on(graph, pending.again);
9708
9742
  }
9709
9743
  }
9710
9744
  /************************************************************
@@ -10919,11 +10953,7 @@ function build_graph$3(gobj) {
10919
10953
  }
10920
10954
  },
10921
10955
  layout: layout_cfg.g6_layout,
10922
- behaviors: [
10923
- "drag-canvas",
10924
- "zoom-canvas",
10925
- "drag-element"
10926
- ]
10956
+ behaviors: BEHAVIORS_READING$1.slice()
10927
10957
  });
10928
10958
  if (priv.theme) graph.setTheme(priv.theme);
10929
10959
  graph.on(NodeEvent.CLICK, (evt) => {
@@ -11030,6 +11060,12 @@ function build_cell_html(key, value, type, dark, matched, fold) {
11030
11060
  * forever. `+3` and `−` need no language.
11031
11061
  ************************************************************/
11032
11062
  var FOLD_SPACER = `<span style="width:24px; flex:0 0 auto;"></span>`;
11063
+ var BEHAVIORS_READING$1 = [
11064
+ "drag-canvas",
11065
+ "zoom-canvas",
11066
+ "drag-element"
11067
+ ];
11068
+ var BEHAVIORS_PICKING$1 = ["drag-canvas", "zoom-canvas"];
11033
11069
  var CARD_HEADER_H$1 = 24;
11034
11070
  var CARD_ROW_H$1 = 22;
11035
11071
  function fold_toggle_html(path, foldable, folded, branches, colors) {
@@ -11166,7 +11202,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
11166
11202
  let min_width = Math.max(180, label.length * 9);
11167
11203
  let card_height = CARD_HEADER_H$1 + lines.length * CARD_ROW_H$1;
11168
11204
  let node_html = `
11169
- <div style="
11205
+ <div class="JSON_CARD" data-json-path="${escapeHtml(path)}" style="
11170
11206
  min-width: ${min_width}px;
11171
11207
  background: ${colors.bg};
11172
11208
  border: ${border_width}px ${is_dict ? "dashed" : "solid"} ${border_color};
@@ -11270,6 +11306,7 @@ function load_json(gobj) {
11270
11306
  });
11271
11307
  graph.render().then(() => {
11272
11308
  reanchor$1(gobj);
11309
+ paint_anchor_mark(gobj);
11273
11310
  if (preserve_view) {
11274
11311
  yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
11275
11312
  return;
@@ -11591,6 +11628,52 @@ function reanchor$1(gobj) {
11591
11628
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11592
11629
  }
11593
11630
  /************************************************************
11631
+ * Reading, or picking the node to anchor.
11632
+ *
11633
+ * Two visible consequences and one invisible: the cursor says
11634
+ * a click is expected, the mount says which mode it is in for
11635
+ * anyone reading the DOM, and node dragging stands aside so a
11636
+ * click that drifts is still a click.
11637
+ ************************************************************/
11638
+ function set_picking_mode$1(gobj, picking) {
11639
+ let graph = gobj.priv.graph;
11640
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING$1 : BEHAVIORS_READING$1).slice());
11641
+ let $container = gobj_read_attr(gobj, "$container");
11642
+ if ($container) {
11643
+ let $mount = $container.querySelector(".json-graph-container");
11644
+ if ($mount) $mount.classList.toggle("JSON_GRAPH_PICKING", !!picking);
11645
+ }
11646
+ }
11647
+ /************************************************************
11648
+ * Mark the card the camera is holding.
11649
+ *
11650
+ * A class on the card's own element and not a G6 node state:
11651
+ * G6 paints no state style on an html node, so `setElementState`
11652
+ * would select it correctly and show nothing.
11653
+ *
11654
+ * Re-applied after every rebuild, because the cards are new DOM
11655
+ * each time and the mark would otherwise survive only until the
11656
+ * next fold.
11657
+ ************************************************************/
11658
+ function paint_anchor_mark(gobj) {
11659
+ let priv = gobj.priv;
11660
+ let $container = gobj_read_attr(gobj, "$container");
11661
+ if (!$container) return;
11662
+ for (let $card of $container.querySelectorAll(".JSON_CARD_ANCHORED")) $card.classList.remove("JSON_CARD_ANCHORED");
11663
+ if (priv.anchor_state !== "on" || !priv.anchor_path) return;
11664
+ let $it = $container.querySelector(".JSON_CARD[data-json-path=\"" + CSS.escape(priv.anchor_path) + "\"]");
11665
+ if ($it) $it.classList.add("JSON_CARD_ANCHORED");
11666
+ }
11667
+ /************************************************************
11668
+ * Centre on the anchor, one cycle after it was chosen.
11669
+ ************************************************************/
11670
+ function ac_center_anchor$2(gobj, event, kw, src) {
11671
+ let priv = gobj.priv;
11672
+ paint_anchor_mark(gobj);
11673
+ if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
11674
+ return 0;
11675
+ }
11676
+ /************************************************************
11594
11677
  * Arm the anchor, or let it go.
11595
11678
  *
11596
11679
  * Three states and one button, so a press ADVANCES: with no
@@ -11607,6 +11690,8 @@ function ac_toggle_anchor$2(gobj, event, kw, src) {
11607
11690
  priv.anchor_path = "";
11608
11691
  }
11609
11692
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11693
+ paint_anchor_mark(gobj);
11694
+ set_picking_mode$1(gobj, priv.anchor_state === "arming");
11610
11695
  return 0;
11611
11696
  }
11612
11697
  /************************************************************
@@ -11673,7 +11758,8 @@ function ac_node_click$4(gobj, event, kw, src) {
11673
11758
  priv.anchor_path = nodedata && nodedata.data ? nodedata.data.path : "";
11674
11759
  priv.anchor_state = "on";
11675
11760
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11676
- yui_graph_center_on(graph, node_id);
11761
+ set_picking_mode$1(gobj, false);
11762
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
11677
11763
  return 0;
11678
11764
  }
11679
11765
  if (nodedata && nodedata.data) gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", {
@@ -11814,6 +11900,11 @@ function create_gclass$11(gclass_name) {
11814
11900
  ac_toggle_anchor$2,
11815
11901
  null
11816
11902
  ],
11903
+ [
11904
+ "EV_CENTER_ANCHOR",
11905
+ ac_center_anchor$2,
11906
+ null
11907
+ ],
11817
11908
  [
11818
11909
  "EV_RESIZE",
11819
11910
  ac_resize$2,
@@ -11845,6 +11936,7 @@ function create_gclass$11(gclass_name) {
11845
11936
  ["EV_CENTER", 0],
11846
11937
  ["EV_NODE_CLICK", 0],
11847
11938
  ["EV_TOGGLE_ANCHOR", 0],
11939
+ ["EV_CENTER_ANCHOR", 0],
11848
11940
  ["EV_RESIZE", 0],
11849
11941
  ["EV_SHOW", 0],
11850
11942
  ["EV_HIDE", 0],
@@ -13133,6 +13225,12 @@ var attrs_table$9 = [
13133
13225
  SDATA(data_type_t.DTP_INTEGER, "collapse_threshold", 0, 10, "Auto-collapse a node whose direct children exceed this number. 0 disables auto-collapse."),
13134
13226
  SDATA_END()
13135
13227
  ];
13228
+ var BEHAVIORS_READING = [
13229
+ "drag-canvas",
13230
+ "zoom-canvas",
13231
+ "drag-element"
13232
+ ];
13233
+ var BEHAVIORS_PICKING = ["drag-canvas", "zoom-canvas"];
13136
13234
  var PRIVATE_DATA$9 = {
13137
13235
  canvas_id: "",
13138
13236
  graph: null,
@@ -13614,11 +13712,7 @@ function build_graph$2(gobj) {
13614
13712
  }
13615
13713
  },
13616
13714
  layout: layout_cfg.g6_layout,
13617
- behaviors: [
13618
- "drag-canvas",
13619
- "zoom-canvas",
13620
- "drag-element"
13621
- ]
13715
+ behaviors: BEHAVIORS_READING.slice()
13622
13716
  });
13623
13717
  if (priv.theme) graph.setTheme(priv.theme);
13624
13718
  graph.on("aftertransform", () => {
@@ -14482,6 +14576,24 @@ function reanchor(gobj) {
14482
14576
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
14483
14577
  }
14484
14578
  /************************************************************
14579
+ * Centre on the anchor, one cycle after it was chosen.
14580
+ ************************************************************/
14581
+ function ac_center_anchor$1(gobj, event, kw, src) {
14582
+ let priv = gobj.priv;
14583
+ if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
14584
+ return 0;
14585
+ }
14586
+ /************************************************************
14587
+ * Reading, or picking the node to anchor.
14588
+ ************************************************************/
14589
+ function set_picking_mode(gobj, picking) {
14590
+ let graph = gobj.priv.graph;
14591
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING : BEHAVIORS_READING).slice());
14592
+ let $container = gobj_read_attr(gobj, "$container");
14593
+ let $mount = $container ? $container.querySelector(".gobj-tree-container") : null;
14594
+ if ($mount) $mount.classList.toggle("GOBJ_TREE_PICKING", !!picking);
14595
+ }
14596
+ /************************************************************
14485
14597
  * Arm the anchor, or let it go.
14486
14598
  *
14487
14599
  * Three states and one button, so a press ADVANCES: with no
@@ -14498,6 +14610,7 @@ function ac_toggle_anchor$1(gobj, event, kw, src) {
14498
14610
  priv.anchor_name = "";
14499
14611
  }
14500
14612
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
14613
+ set_picking_mode(gobj, priv.anchor_state === "arming");
14501
14614
  return 0;
14502
14615
  }
14503
14616
  /************************************************************
@@ -14566,7 +14679,8 @@ function ac_node_click$3(gobj, event, kw, src) {
14566
14679
  priv.anchor_name = node_data.full_name || "";
14567
14680
  priv.anchor_state = "on";
14568
14681
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
14569
- yui_graph_center_on(graph, node_id);
14682
+ set_picking_mode(gobj, false);
14683
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
14570
14684
  return 0;
14571
14685
  }
14572
14686
  let client_x = kw.evt && kw.evt.client ? kw.evt.client.x : 0;
@@ -14650,6 +14764,7 @@ function create_gclass$9(gclass_name) {
14650
14764
  ["EV_CENTER", 0],
14651
14765
  ["EV_NODE_CLICK", 0],
14652
14766
  ["EV_TOGGLE_ANCHOR", 0],
14767
+ ["EV_CENTER_ANCHOR", 0],
14653
14768
  ["EV_RESIZE", 0],
14654
14769
  ["EV_SHOW", 0],
14655
14770
  ["EV_HIDE", 0]
@@ -14714,6 +14829,11 @@ function create_gclass$9(gclass_name) {
14714
14829
  ac_toggle_anchor$1,
14715
14830
  null
14716
14831
  ],
14832
+ [
14833
+ "EV_CENTER_ANCHOR",
14834
+ ac_center_anchor$1,
14835
+ null
14836
+ ],
14717
14837
  [
14718
14838
  "EV_RESIZE",
14719
14839
  ac_resize$1,
@@ -37555,7 +37675,7 @@ Text.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(DisplayObject.PARS
37555
37675
  * * register property @see https://developer.mozilla.org/en-US/docs/Web/API/CSS/RegisterProperty
37556
37676
  * * CSS Layout API
37557
37677
  */
37558
- var CSS = {
37678
+ var CSS$1 = {
37559
37679
  /**
37560
37680
  * <number>
37561
37681
  * @see https://drafts.csswg.org/css-values-4/#number-value
@@ -47604,7 +47724,7 @@ var e = 1e-4;
47604
47724
  syntax: PropertySyntax.ANGLE
47605
47725
  }
47606
47726
  ].forEach(function(property) {
47607
- CSS.registerProperty(property);
47727
+ CSS$1.registerProperty(property);
47608
47728
  });
47609
47729
  var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInRadian) {
47610
47730
  return {
@@ -52926,6 +53046,14 @@ function ac_zoom_reset(gobj, event, kw, src) {
52926
53046
  /************************************************************
52927
53047
  * What the anchor button says it will do next.
52928
53048
  ************************************************************/
53049
+ function ac_center_anchor(gobj, event, kw, src) {
53050
+ let priv = gobj.priv;
53051
+ if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
53052
+ return 0;
53053
+ }
53054
+ /************************************************************
53055
+ * What the anchor button says it will do next.
53056
+ ************************************************************/
52929
53057
  function anchor_title(state) {
52930
53058
  if (state === "arming") return t("click the element to centre on");
52931
53059
  if (state === "on") return t("centred: click to release");
@@ -53144,7 +53272,7 @@ function ac_node_click$2(gobj, event, kw, src) {
53144
53272
  priv.anchor_id = node_id;
53145
53273
  priv.anchor_state = "on";
53146
53274
  update_toolbar(gobj);
53147
- yui_graph_center_on(graph, node_id);
53275
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
53148
53276
  return 0;
53149
53277
  }
53150
53278
  try {
@@ -53413,6 +53541,11 @@ function create_gclass$4(gclass_name) {
53413
53541
  ac_toggle_anchor,
53414
53542
  null
53415
53543
  ],
53544
+ [
53545
+ "EV_CENTER_ANCHOR",
53546
+ ac_center_anchor,
53547
+ null
53548
+ ],
53416
53549
  [
53417
53550
  "EV_TOGGLE_TOOLBARS",
53418
53551
  ac_toggle_toolbars,
@@ -53527,6 +53660,7 @@ function create_gclass$4(gclass_name) {
53527
53660
  ["EV_ZOOM_OUT", 0],
53528
53661
  ["EV_ZOOM_RESET", 0],
53529
53662
  ["EV_TOGGLE_ANCHOR", 0],
53663
+ ["EV_CENTER_ANCHOR", 0],
53530
53664
  ["EV_TOGGLE_TOOLBARS", 0],
53531
53665
  ["EV_AUTO_FIT", 0],
53532
53666
  ["EV_ZOOM_SELECTION", 0],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.24",
3
+ "version": "7.23.26",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -42,6 +42,7 @@ import {
42
42
  gobj_subscribe_event,
43
43
  gobj_publish_event,
44
44
  gobj_send_event,
45
+ gobj_post_event,
45
46
  gobj_save_persistent_attrs,
46
47
  clean_name,
47
48
  sprintf,
@@ -7505,6 +7506,19 @@ function ac_zoom_reset(gobj, event, kw, src)
7505
7506
  return 0;
7506
7507
  }
7507
7508
 
7509
+ /************************************************************
7510
+ * What the anchor button says it will do next.
7511
+ ************************************************************/
7512
+ function ac_center_anchor(gobj, event, kw, src)
7513
+ {
7514
+ let priv = gobj.priv;
7515
+
7516
+ if(priv.anchor_state === "on" && priv.anchor_id) {
7517
+ yui_graph_center_on(priv.graph, priv.anchor_id);
7518
+ }
7519
+ return 0;
7520
+ }
7521
+
7508
7522
  /************************************************************
7509
7523
  * What the anchor button says it will do next.
7510
7524
  ************************************************************/
@@ -7824,7 +7838,11 @@ function ac_node_click(gobj, event, kw, src)
7824
7838
  priv.anchor_id = node_id;
7825
7839
  priv.anchor_state = "on";
7826
7840
  update_toolbar(gobj);
7827
- yui_graph_center_on(graph, node_id);
7841
+
7842
+ /* POSTED, not made here: a camera move issued inside G6's click
7843
+ * dispatch is swallowed -- see the note in yui_graph_center_on.
7844
+ * An event to itself and not a timer: a deferral is not a TIME. */
7845
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
7828
7846
  return 0;
7829
7847
  }
7830
7848
 
@@ -8140,6 +8158,7 @@ function create_gclass(gclass_name)
8140
8158
  ["EV_ZOOM_OUT", ac_zoom_out, null],
8141
8159
  ["EV_ZOOM_RESET", ac_zoom_reset, null],
8142
8160
  ["EV_TOGGLE_ANCHOR", ac_toggle_anchor, null],
8161
+ ["EV_CENTER_ANCHOR", ac_center_anchor, null],
8143
8162
  ["EV_TOGGLE_TOOLBARS", ac_toggle_toolbars, null],
8144
8163
  ["EV_AUTO_FIT", ac_auto_fit, null],
8145
8164
  ["EV_ZOOM_SELECTION", ac_zoom_selection, null],
@@ -8190,6 +8209,7 @@ function create_gclass(gclass_name)
8190
8209
  ["EV_ZOOM_OUT", 0],
8191
8210
  ["EV_ZOOM_RESET", 0],
8192
8211
  ["EV_TOGGLE_ANCHOR", 0],
8212
+ ["EV_CENTER_ANCHOR", 0],
8193
8213
  ["EV_TOGGLE_TOOLBARS", 0],
8194
8214
  ["EV_AUTO_FIT", 0],
8195
8215
  ["EV_ZOOM_SELECTION", 0],
@@ -21,6 +21,7 @@ import {
21
21
  gobj_read_attr,
22
22
  gobj_write_attr,
23
23
  gobj_send_event,
24
+ gobj_post_event,
24
25
  gobj_write_str_attr,
25
26
  gobj_read_str_attr,
26
27
  gobj_publish_event,
@@ -89,6 +90,20 @@ SDATA(data_type_t.DTP_INTEGER, "collapse_threshold", 0, 10, "Auto-collapse a no
89
90
  SDATA_END()
90
91
  ];
91
92
 
93
+ /*
94
+ * What the graph listens for while you are READING it, and while you
95
+ * are PICKING the node to anchor.
96
+ *
97
+ * The difference is `drag-element`: with it on, a click that drifts
98
+ * two pixels -- which is every click a hand makes -- becomes a drag,
99
+ * G6 fires no `node:click`, and the pick silently does not happen.
100
+ * Swapped with `setBehaviors()` and not with a behavior's `enable`,
101
+ * which REPLACES the behavior's own default test rather than adding
102
+ * to it.
103
+ */
104
+ const BEHAVIORS_READING = ['drag-canvas', 'zoom-canvas', 'drag-element'];
105
+ const BEHAVIORS_PICKING = ['drag-canvas', 'zoom-canvas'];
106
+
92
107
  let PRIVATE_DATA = {
93
108
  canvas_id: "",
94
109
  graph: null,
@@ -653,17 +668,7 @@ function build_graph(gobj)
653
668
 
654
669
  layout: layout_cfg.g6_layout,
655
670
 
656
- behaviors: [
657
- 'drag-canvas',
658
- 'zoom-canvas',
659
- /* Move a card by dragging it. The position is not kept --
660
- * this tree is rebuilt from the live gobj tree on every
661
- * refresh -- and it is still worth having: pulling two
662
- * cards apart to read the lines between them is the whole
663
- * use of it. `enable` REPLACES a behavior's default rather
664
- * than adding to it, so it is not passed. */
665
- 'drag-element',
666
- ],
671
+ behaviors: BEHAVIORS_READING.slice(),
667
672
  });
668
673
 
669
674
  if(priv.theme) {
@@ -2000,6 +2005,37 @@ function reanchor(gobj)
2000
2005
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
2001
2006
  }
2002
2007
 
2008
+ /************************************************************
2009
+ * Centre on the anchor, one cycle after it was chosen.
2010
+ ************************************************************/
2011
+ function ac_center_anchor(gobj, event, kw, src)
2012
+ {
2013
+ let priv = gobj.priv;
2014
+
2015
+ if(priv.anchor_state === "on" && priv.anchor_id) {
2016
+ yui_graph_center_on(priv.graph, priv.anchor_id);
2017
+ }
2018
+ return 0;
2019
+ }
2020
+
2021
+ /************************************************************
2022
+ * Reading, or picking the node to anchor.
2023
+ ************************************************************/
2024
+ function set_picking_mode(gobj, picking)
2025
+ {
2026
+ let graph = gobj.priv.graph;
2027
+
2028
+ if(graph && typeof graph.setBehaviors === "function") {
2029
+ graph.setBehaviors((picking? BEHAVIORS_PICKING: BEHAVIORS_READING).slice());
2030
+ }
2031
+
2032
+ let $container = gobj_read_attr(gobj, "$container");
2033
+ let $mount = $container? $container.querySelector('.gobj-tree-container'): null;
2034
+ if($mount) {
2035
+ $mount.classList.toggle('GOBJ_TREE_PICKING', !!picking);
2036
+ }
2037
+ }
2038
+
2003
2039
  /************************************************************
2004
2040
  * Arm the anchor, or let it go.
2005
2041
  *
@@ -2021,6 +2057,7 @@ function ac_toggle_anchor(gobj, event, kw, src)
2021
2057
  }
2022
2058
 
2023
2059
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
2060
+ set_picking_mode(gobj, priv.anchor_state === "arming");
2024
2061
  return 0;
2025
2062
  }
2026
2063
 
@@ -2125,7 +2162,12 @@ function ac_node_click(gobj, event, kw, src)
2125
2162
  priv.anchor_name = node_data.full_name || "";
2126
2163
  priv.anchor_state = "on";
2127
2164
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
2128
- yui_graph_center_on(graph, node_id);
2165
+ set_picking_mode(gobj, false);
2166
+
2167
+ /* POSTED, not made here: a camera move issued inside G6's click
2168
+ * dispatch is swallowed -- see the note in yui_graph_center_on.
2169
+ * An event to itself and not a timer: a deferral is not a TIME. */
2170
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
2129
2171
  return 0;
2130
2172
  }
2131
2173
 
@@ -2255,6 +2297,7 @@ function create_gclass(gclass_name)
2255
2297
  ["EV_CENTER", ac_center, null],
2256
2298
  ["EV_NODE_CLICK", ac_node_click, null],
2257
2299
  ["EV_TOGGLE_ANCHOR", ac_toggle_anchor, null],
2300
+ ["EV_CENTER_ANCHOR", ac_center_anchor, null],
2258
2301
  ["EV_RESIZE", ac_resize, null],
2259
2302
  ["EV_SHOW", ac_show, null],
2260
2303
  ["EV_HIDE", ac_hide, null],
@@ -2277,6 +2320,7 @@ function create_gclass(gclass_name)
2277
2320
  ["EV_CENTER", 0],
2278
2321
  ["EV_NODE_CLICK", 0],
2279
2322
  ["EV_TOGGLE_ANCHOR", 0],
2323
+ ["EV_CENTER_ANCHOR", 0],
2280
2324
  ["EV_RESIZE", 0],
2281
2325
  ["EV_SHOW", 0],
2282
2326
  ["EV_HIDE", 0],
@@ -293,6 +293,38 @@
293
293
  z-index: 1;
294
294
  }
295
295
 
296
+ /*
297
+ * Picking the node to anchor. The pointer says a click is expected --
298
+ * without it the only sign the graph is waiting for one is a button
299
+ * at the far end of a toolbar.
300
+ */
301
+ .C_YUI_JSON .json-graph-container.JSON_GRAPH_PICKING,
302
+ .C_YUI_JSON .json-graph-container.JSON_GRAPH_PICKING * {
303
+ cursor: crosshair;
304
+ }
305
+
306
+ /*
307
+ * The card the camera is holding.
308
+ *
309
+ * It has to say so ON THE CARD: the toolbar button knows an anchor is
310
+ * set, and knowing WHICH node it is was left to the reader remembering
311
+ * what they clicked -- which is not a mark, it is a memory test.
312
+ *
313
+ * An outline and not a fill: the card's own colours mean things (a
314
+ * dashed border is a dict, a solid one a list, the header carries the
315
+ * group's hue), and a mark that repaints any of them would be saying
316
+ * two things with one channel. `outline` also draws OUTSIDE the box,
317
+ * so it cannot move a single row.
318
+ *
319
+ * Amber, the same "this one, right now" the toolbar's arming state
320
+ * uses. It is not one of the palette's action colours because it is
321
+ * not an action.
322
+ */
323
+ .C_YUI_JSON .JSON_CARD_ANCHORED {
324
+ outline: 3px solid #fa8c16;
325
+ outline-offset: 2px;
326
+ }
327
+
296
328
  /*
297
329
  * The graph mount, once its cards can be DRAGGED.
298
330
  *
@@ -25,6 +25,7 @@ import {
25
25
  gobj_read_attr,
26
26
  gobj_write_attr,
27
27
  gobj_send_event,
28
+ gobj_post_event,
28
29
  gobj_find_service,
29
30
  gobj_short_name,
30
31
  gobj_read_str_attr,
@@ -691,24 +692,7 @@ function build_graph(gobj)
691
692
 
692
693
  layout: layout_cfg.g6_layout,
693
694
 
694
- behaviors: [
695
- 'drag-canvas',
696
- 'zoom-canvas',
697
- /*
698
- * Move a card by dragging it. The position is NOT kept:
699
- * the graph is rebuilt from the document on every refresh,
700
- * fold and layout change, and there is nothing here to
701
- * save it to -- this is a viewer over a value somebody
702
- * handed it, not a document of its own. Deliberate: being
703
- * able to pull two cards apart to read the lines between
704
- * them is worth having even when it lasts one session.
705
- *
706
- * No `enable`: a behavior's `enable` REPLACES the default,
707
- * it does not add to it, and the default here is exactly
708
- * what is wanted.
709
- */
710
- 'drag-element',
711
- ],
695
+ behaviors: BEHAVIORS_READING.slice(),
712
696
  });
713
697
 
714
698
  if(priv.theme) {
@@ -898,6 +882,23 @@ function build_cell_html(key, value, type, dark, matched, fold)
898
882
  ************************************************************/
899
883
  const FOLD_SPACER = `<span style="width:24px; flex:0 0 auto;"></span>`;
900
884
 
885
+ /*
886
+ * What the graph listens for while you are READING it, and while you
887
+ * are PICKING the node to anchor.
888
+ *
889
+ * The difference is `drag-element`, and dropping it is not a detail:
890
+ * with it on, a click that drifts two pixels -- which is every click a
891
+ * hand makes -- becomes a drag, G6 fires no `node:click`, and the pick
892
+ * silently does not happen. Measured: an instant click anchors, the
893
+ * same click with 2px of movement does not.
894
+ *
895
+ * Swapped with `setBehaviors()` and not with a behavior's `enable`,
896
+ * which REPLACES the behavior's own default test rather than adding to
897
+ * it -- the trap that made a card drag pan the canvas in 7.23.10.
898
+ */
899
+ const BEHAVIORS_READING = ['drag-canvas', 'zoom-canvas', 'drag-element'];
900
+ const BEHAVIORS_PICKING = ['drag-canvas', 'zoom-canvas'];
901
+
901
902
  /*
902
903
  * The card's geometry, in ONE place.
903
904
  *
@@ -1151,8 +1152,21 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
1151
1152
  let min_width = Math.max(180, label.length * 9);
1152
1153
  let card_height = CARD_HEADER_H + lines.length * CARD_ROW_H;
1153
1154
 
1155
+ /*
1156
+ * The card carries its NAME and its PLACE in the document.
1157
+ *
1158
+ * `JSON_CARD` because a gclass that builds DOM has to leave a tree
1159
+ * somebody can read in the Inspector, and until now every card here
1160
+ * was an anonymous `<div>` among the anonymous `<div>`s G6 stacks.
1161
+ *
1162
+ * `data-json-path` because the anchor has to be able to find the
1163
+ * card it is holding: G6 paints no state style on an html node, so
1164
+ * the mark cannot be a node state and has to be a class on this
1165
+ * element -- and the path is what survives a rebuild, where the
1166
+ * node id does not.
1167
+ */
1154
1168
  let node_html = `
1155
- <div style="
1169
+ <div class="JSON_CARD" data-json-path="${escapeHtml(path)}" style="
1156
1170
  min-width: ${min_width}px;
1157
1171
  background: ${colors.bg};
1158
1172
  border: ${border_width}px ${is_dict ? 'dashed' : 'solid'} ${border_color};
@@ -1347,6 +1361,7 @@ function load_json(gobj)
1347
1361
  /* Ids are generated per build: point the anchor at its path
1348
1362
  * again before anybody asks the camera to go there. */
1349
1363
  reanchor(gobj);
1364
+ paint_anchor_mark(gobj);
1350
1365
 
1351
1366
  if(preserve_view) {
1352
1367
  yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
@@ -1819,6 +1834,79 @@ function reanchor(gobj)
1819
1834
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
1820
1835
  }
1821
1836
 
1837
+ /************************************************************
1838
+ * Reading, or picking the node to anchor.
1839
+ *
1840
+ * Two visible consequences and one invisible: the cursor says
1841
+ * a click is expected, the mount says which mode it is in for
1842
+ * anyone reading the DOM, and node dragging stands aside so a
1843
+ * click that drifts is still a click.
1844
+ ************************************************************/
1845
+ function set_picking_mode(gobj, picking)
1846
+ {
1847
+ let priv = gobj.priv;
1848
+ let graph = priv.graph;
1849
+
1850
+ if(graph && typeof graph.setBehaviors === "function") {
1851
+ graph.setBehaviors((picking? BEHAVIORS_PICKING: BEHAVIORS_READING).slice());
1852
+ }
1853
+
1854
+ let $container = gobj_read_attr(gobj, "$container");
1855
+ if($container) {
1856
+ let $mount = $container.querySelector('.json-graph-container');
1857
+ if($mount) {
1858
+ $mount.classList.toggle('JSON_GRAPH_PICKING', !!picking);
1859
+ }
1860
+ }
1861
+ }
1862
+
1863
+ /************************************************************
1864
+ * Mark the card the camera is holding.
1865
+ *
1866
+ * A class on the card's own element and not a G6 node state:
1867
+ * G6 paints no state style on an html node, so `setElementState`
1868
+ * would select it correctly and show nothing.
1869
+ *
1870
+ * Re-applied after every rebuild, because the cards are new DOM
1871
+ * each time and the mark would otherwise survive only until the
1872
+ * next fold.
1873
+ ************************************************************/
1874
+ function paint_anchor_mark(gobj)
1875
+ {
1876
+ let priv = gobj.priv;
1877
+ let $container = gobj_read_attr(gobj, "$container");
1878
+
1879
+ if(!$container) {
1880
+ return;
1881
+ }
1882
+ for(let $card of $container.querySelectorAll('.JSON_CARD_ANCHORED')) {
1883
+ $card.classList.remove('JSON_CARD_ANCHORED');
1884
+ }
1885
+ if(priv.anchor_state !== "on" || !priv.anchor_path) {
1886
+ return;
1887
+ }
1888
+ let $it = $container.querySelector(
1889
+ '.JSON_CARD[data-json-path="' + CSS.escape(priv.anchor_path) + '"]'
1890
+ );
1891
+ if($it) {
1892
+ $it.classList.add('JSON_CARD_ANCHORED');
1893
+ }
1894
+ }
1895
+
1896
+ /************************************************************
1897
+ * Centre on the anchor, one cycle after it was chosen.
1898
+ ************************************************************/
1899
+ function ac_center_anchor(gobj, event, kw, src)
1900
+ {
1901
+ let priv = gobj.priv;
1902
+
1903
+ paint_anchor_mark(gobj);
1904
+ if(priv.anchor_state === "on" && priv.anchor_id) {
1905
+ yui_graph_center_on(priv.graph, priv.anchor_id);
1906
+ }
1907
+ return 0;
1908
+ }
1909
+
1822
1910
  /************************************************************
1823
1911
  * Arm the anchor, or let it go.
1824
1912
  *
@@ -1840,6 +1928,8 @@ function ac_toggle_anchor(gobj, event, kw, src)
1840
1928
  }
1841
1929
 
1842
1930
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
1931
+ paint_anchor_mark(gobj);
1932
+ set_picking_mode(gobj, priv.anchor_state === "arming");
1843
1933
  return 0;
1844
1934
  }
1845
1935
 
@@ -1931,7 +2021,23 @@ function ac_node_click(gobj, event, kw, src)
1931
2021
  priv.anchor_path = (nodedata && nodedata.data)? nodedata.data.path: "";
1932
2022
  priv.anchor_state = "on";
1933
2023
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
1934
- yui_graph_center_on(graph, node_id);
2024
+ set_picking_mode(gobj, false);
2025
+
2026
+ /*
2027
+ * The camera move is POSTED, not made here.
2028
+ *
2029
+ * A translate issued inside G6's click dispatch is swallowed:
2030
+ * measured, the call computes the right offset, resolves, and
2031
+ * the camera reads the same before and after -- while the very
2032
+ * same call from a zoom action centres the node exactly. So the
2033
+ * first thing an anchor did was nothing, which is what "I don't
2034
+ * see how it works" looks like.
2035
+ *
2036
+ * An event to itself and not a timer: a deferral is not a TIME,
2037
+ * and writing it as one costs the name of what happens next in
2038
+ * the trace.
2039
+ */
2040
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
1935
2041
  return 0;
1936
2042
  }
1937
2043
 
@@ -2057,6 +2163,7 @@ function create_gclass(gclass_name)
2057
2163
  ["EV_CENTER", ac_center, null],
2058
2164
  ["EV_NODE_CLICK", ac_node_click, null],
2059
2165
  ["EV_TOGGLE_ANCHOR", ac_toggle_anchor, null],
2166
+ ["EV_CENTER_ANCHOR", ac_center_anchor, null],
2060
2167
  ["EV_RESIZE", ac_resize, null],
2061
2168
  ["EV_SHOW", ac_show, null],
2062
2169
  ["EV_HIDE", ac_hide, null],
@@ -2081,6 +2188,7 @@ function create_gclass(gclass_name)
2081
2188
  ["EV_CENTER", 0],
2082
2189
  ["EV_NODE_CLICK", 0],
2083
2190
  ["EV_TOGGLE_ANCHOR", 0],
2191
+ ["EV_CENTER_ANCHOR", 0],
2084
2192
  ["EV_RESIZE", 0],
2085
2193
  ["EV_SHOW", 0],
2086
2194
  ["EV_HIDE", 0],
package/src/lib_graph.css CHANGED
@@ -14,7 +14,11 @@
14
14
  .color_create_state {
15
15
  color: #1890ff !important;
16
16
  }
17
- .color_submit_state {
17
+ /* Orange is "needs attention, waiting on you". A toggle that is armed
18
+ * and waiting for its target is exactly that and not a submit, so it
19
+ * takes the same colour under a name that says so. */
20
+ .color_submit_state,
21
+ .color_pending_state {
18
22
  color: #fa8c16 !important;
19
23
  }
20
24
  .color_active_state {
@@ -237,6 +241,36 @@
237
241
  border-radius: 4px;
238
242
  }
239
243
 
244
+ /*
245
+ * The same pressed look for a toggle in a DOM toolbar.
246
+ *
247
+ * `pressed_state` was written for the G6 plugin toolbar and styled ONLY
248
+ * there, so the two toolbars built by `yui_graph_camera.js` were given
249
+ * the class and no rule: pressing the button changed nothing on screen,
250
+ * which from the outside is indistinguishable from a button that does
251
+ * not work.
252
+ *
253
+ * `border-color` as well as the fill, or Bulma's own border stays and
254
+ * the button reads as outlined-and-filled, which is neither state.
255
+ * The `:hover` half is the same guard as above: without it, hovering a
256
+ * pressed button hands it back to Bulma's rule and the toggle appears
257
+ * to switch off under the pointer.
258
+ */
259
+ /* Picking the node to anchor, in the gobj tree: the pointer says a
260
+ * click is expected, which is otherwise only announced by a button at
261
+ * the far end of a toolbar. */
262
+ .gobj-tree-container.GOBJ_TREE_PICKING,
263
+ .gobj-tree-container.GOBJ_TREE_PICKING * {
264
+ cursor: crosshair;
265
+ }
266
+
267
+ .button.pressed_state,
268
+ .button.pressed_state:hover {
269
+ background-color: var(--bulma-text, #2e333d);
270
+ color: var(--bulma-background, #ffffff);
271
+ border-color: var(--bulma-text, #2e333d);
272
+ }
273
+
240
274
  /*
241
275
  * Touch: the context menu is a LIST OF COMMANDS, and G6 sizes its
242
276
  * rows with `padding: 8px 12px` -- 34px, which is a mouse row. The
@@ -191,7 +191,14 @@ export function yui_graph_update_anchor($container, state)
191
191
  if(!$btn) {
192
192
  return; /* a toolbar without the control: nothing to paint */
193
193
  }
194
- set_pressed_state($container, '.EV_TOGGLE_ANCHOR', state !== "off");
194
+ /*
195
+ * PRESSED is "on", and only "on": pressed is the state the control
196
+ * is IN, and while it is arming it is not in that state yet, it is
197
+ * asking for something. That gets the attention colour instead --
198
+ * which also keeps the two off each other, since an orange glyph on
199
+ * the pressed fill is orange on near-white in dark and unreadable.
200
+ */
201
+ set_pressed_state($container, '.EV_TOGGLE_ANCHOR', state === "on");
195
202
  $btn.classList.toggle('color_pending_state', state === "arming");
196
203
  $btn.setAttribute("aria-pressed", (state === "on")? "true": "false");
197
204
 
@@ -211,28 +218,107 @@ export function yui_graph_update_anchor($container, state)
211
218
  * the zoom is what the reader asked for, the centring is what
212
219
  * keeps the thing they are reading under their eyes.
213
220
  *
221
+ * The translate is computed and applied HERE rather than
222
+ * delegated to `graph.focusElement()`, which is the obvious
223
+ * call and does NOT work on these graphs. Measured: it
224
+ * resolves, having computed the very offset wanted -- `[0,
225
+ * 306]` for a card 306px above centre -- and the camera does
226
+ * not move. `getPosition()` reads the same before and after,
227
+ * and so does the card's box on screen. Three public calls do
228
+ * move it, so that is what is used.
229
+ *
214
230
  * Guarded on every side because it runs on every wheel notch:
215
231
  * a graph mid-rebuild, an anchor whose node went away with a
216
- * fold, a G6 version without `focusElement` -- none of those is
232
+ * fold, a G6 missing one of these calls -- none of those is
217
233
  * an error worth a log line per notch, they are all "there is
218
234
  * nothing to centre right now".
219
235
  ************************************************************/
220
- export function yui_graph_center_on(graph, node_id)
236
+ const __centring__ = new WeakMap();
237
+
238
+ export async function yui_graph_center_on(graph, node_id)
221
239
  {
222
240
  if(!graph || !node_id) {
223
241
  return false;
224
242
  }
243
+
244
+ /*
245
+ * ONE centring at a time, per graph.
246
+ *
247
+ * This runs on every zoom notch, and one run is several awaited
248
+ * camera moves. A wheel gesture is a BURST of notches, so without
249
+ * this the runs overlap: each one measures a position another one
250
+ * is in the middle of changing, and they walk the camera around
251
+ * instead of converging -- which reads as a graph that pans and
252
+ * never centres.
253
+ *
254
+ * A request that arrives mid-run is not dropped, it is remembered:
255
+ * the last one wins and runs once at the end, which is the only
256
+ * one whose answer is still true.
257
+ */
258
+ let st = __centring__.get(graph);
259
+ if(st && st.running) {
260
+ st.again = node_id;
261
+ return true;
262
+ }
263
+ __centring__.set(graph, {running: true, again: null});
264
+
225
265
  try {
226
266
  if(typeof graph.getElementData === "function" && !graph.getElementData(node_id)) {
227
267
  return false;
228
268
  }
229
- if(typeof graph.focusElement !== "function") {
269
+ if(typeof graph.getElementPosition !== "function" ||
270
+ typeof graph.getViewportByCanvas !== "function" ||
271
+ typeof graph.getCanvasCenter !== "function" ||
272
+ typeof graph.translateTo !== "function") {
230
273
  return false;
231
274
  }
232
- graph.focusElement(node_id);
275
+
276
+ /*
277
+ * MEASURE, MOVE, MEASURE AGAIN -- up to three times.
278
+ *
279
+ * Not the arithmetic anybody would write first, and the reason
280
+ * is worth keeping. The obvious call is `graph.focusElement()`,
281
+ * which computes the very offset wanted and then does not move
282
+ * the camera at all: measured on the JSON graph it resolves
283
+ * with `getPosition()` reading identical before and after.
284
+ * `translateBy()` (a RELATIVE transform) behaves the same way.
285
+ * `translateTo()` (ABSOLUTE) does move it -- and the units of
286
+ * its argument are not viewport pixels at any zoom but 1, so
287
+ * one shot lands short or long by a factor nobody should have
288
+ * to name.
289
+ *
290
+ * Probing for that factor gives noise: the camera settles
291
+ * asynchronously, so a position read straight after a
292
+ * translate is a value halfway through. AWAITING each move and
293
+ * measuring again needs no factor at all -- the direction is
294
+ * right, so the residual falls to nothing in two or three
295
+ * passes, and a graph whose maths change under us keeps
296
+ * working.
297
+ *
298
+ * The 1px floor is what stops it converging forever on the
299
+ * rounding.
300
+ */
301
+ for(let i = 0; i < 3; i++) {
302
+ let at = graph.getViewportByCanvas(graph.getElementPosition(node_id));
303
+ let want = graph.getCanvasCenter();
304
+ let dx = want[0] - at[0];
305
+ let dy = want[1] - at[1];
306
+
307
+ if(Math.abs(dx) < 1 && Math.abs(dy) < 1) {
308
+ break;
309
+ }
310
+ let pos = graph.getPosition();
311
+ await graph.translateTo([pos[0] + dx, pos[1] + dy]);
312
+ }
233
313
  return true;
234
314
  } catch(e) {
235
315
  return false; /* the element is gone, or the graph is between renders */
316
+ } finally {
317
+ let pending = __centring__.get(graph);
318
+ __centring__.set(graph, {running: false, again: null});
319
+ if(pending && pending.again) {
320
+ yui_graph_center_on(graph, pending.again);
321
+ }
236
322
  }
237
323
  }
238
324